Add troubleshooting, encoding, and timeout documentation

New pages:
- guides/troubleshooting: error recovery, permissions, device reconnection
- guides/timeout-tuning: baud rate timing, slow device patterns
- concepts/encoding-and-binary: UTF-8 vs Latin-1, binary protocol handling

Enhanced existing pages:
- reference/url-handlers: feature comparison table for URL schemes
- guides/file-transfers: error recovery and ZMODEM resume section
- concepts/flow-control: USB adapter compatibility and deadlock debugging
This commit is contained in:
Ryan Malloy 2026-02-03 10:06:43 -07:00
parent 38a33e38b1
commit 63a4169ffa
6 changed files with 1212 additions and 0 deletions

View File

@ -0,0 +1,270 @@
---
title: Encoding and Binary Data
description: Working with text encodings, binary protocols, and raw byte handling
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
Serial communication deals with bytes. How those bytes map to characters — or whether they represent characters at all — depends on your encoding choice. This guide covers when to use each encoding and how to handle binary protocols that have no character representation.
---
## The Encoding Parameter
Most mcserial read and write tools accept an `encoding` parameter that controls how strings convert to/from bytes:
```json
// Text with UTF-8 (default)
// write_serial(port="/dev/ttyUSB0", data="Hello, 世界\r\n", encoding="utf-8")
// Raw bytes as Latin-1
// write_serial(port="/dev/ttyUSB0", data="\x01\x03\x00\x00\x00\x01", encoding="latin-1")
```
When reading, mcserial decodes incoming bytes using the specified encoding. Invalid byte sequences are replaced with the Unicode replacement character (<28>) rather than throwing an error — this is the `errors="replace"` behavior in Python.
---
## UTF-8: The Default
UTF-8 is the default encoding for all string operations. It handles ASCII (bytes 0x000x7F) directly and encodes non-ASCII characters as multi-byte sequences.
**When to use UTF-8:**
- ASCII text protocols (AT commands, console output)
- Devices that explicitly use UTF-8 (modern systems, JSON/XML output)
- Human-readable text where you want proper Unicode support
```json
// Reading UTF-8 text
// read_serial_line(port="/dev/ttyUSB0", encoding="utf-8")
{
"line": "Temperature: 23.5°C",
"bytes_read": 21
}
```
<Aside type="caution" title="UTF-8 and binary data don't mix">
If binary data contains bytes that are invalid UTF-8 sequences (e.g., 0x800xFF by themselves), decoding replaces them with `<60>`. This corrupts binary data when you try to interpret it as text.
**Example:** The byte `0xC0` followed by `0x03` is invalid UTF-8. Reading this with UTF-8 encoding produces `<60>\x03` instead of the original bytes.
</Aside>
---
## Latin-1: The Raw Byte Passthrough
Latin-1 (ISO-8859-1) maps bytes 0x000xFF directly to Unicode code points U+0000U+00FF. This makes it a perfect passthrough for raw binary data — every possible byte value round-trips through encoding and decoding unchanged.
**When to use Latin-1:**
- Binary protocols (Modbus RTU, proprietary framing)
- Data with arbitrary byte values (firmware blobs, encrypted payloads)
- Protocol analysis where you need to see exact bytes
```json
// Writing a Modbus RTU request (address 1, read holding registers)
// write_serial(
// port="/dev/ttyUSB0",
// data="\x01\x03\x00\x00\x00\x01\x84\x0A",
// encoding="latin-1"
// )
{
"bytes_written": 8
}
```
mcserial defaults to Latin-1 for binary-oriented operations like `rs485_scan_addresses` precisely because it ensures byte-for-byte fidelity.
---
## ASCII: Strict 7-Bit
ASCII only covers bytes 0x000x7F. Bytes outside this range cause encoding errors.
**When to use ASCII:**
- Strict validation of 7-bit protocols
- Legacy systems that only accept ASCII
- When you want encoding to fail loudly on invalid data
```json
// This would fail if the device sends bytes > 0x7F
// read_serial(port="/dev/ttyUSB0", encoding="ascii")
```
<Aside type="tip">
You rarely need explicit ASCII. UTF-8 is a superset of ASCII — if your data is pure ASCII, UTF-8 handles it identically.
</Aside>
---
## Binary Data: write_serial_bytes
For precise binary control, use `write_serial_bytes` instead of `write_serial`. It accepts a list of integer byte values (0255) and writes them directly:
```json
// Write exact bytes without encoding conversion
// write_serial_bytes(port="/dev/ttyUSB0", data=[0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A])
{
"bytes_written": 8
}
```
This is clearer than escaping bytes in a string and avoids any encoding ambiguity.
---
## Reading Binary: The Hex Dump Resource
For binary analysis, use the `serial://{port}/raw` resource. It returns data as a hex dump with printable ASCII annotations:
```
Read resource: serial:///dev/ttyUSB0/raw
```
Output format:
```
00000000 01 03 02 00 64 B8 44 |....d.D|
```
This shows:
- Offset (00000000)
- Hex bytes (01 03 02 00 64 B8 44)
- ASCII representation (. for non-printable, actual character otherwise)
---
## Common Protocol Patterns
### Modbus RTU (Binary)
Modbus RTU is a binary protocol with CRC-16 error checking. Always use Latin-1 or `write_serial_bytes`:
<Tabs>
<TabItem label="Using Latin-1">
```json
// Request: Read holding register 0 from device 1
// write_serial(
// port="/dev/ttyUSB0",
// data="\x01\x03\x00\x00\x00\x01\x84\x0A",
// encoding="latin-1"
// )
// Read response
// read_serial(port="/dev/ttyUSB0", size=7, encoding="latin-1")
```
</TabItem>
<TabItem label="Using write_serial_bytes">
```json
// Same request using byte array
// write_serial_bytes(
// port="/dev/ttyUSB0",
// data=[0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A]
// )
```
</TabItem>
</Tabs>
### NMEA (GPS) — ASCII Text
NMEA sentences are pure ASCII with a simple checksum:
```json
// NMEA sentences are safe with UTF-8 or ASCII
// read_serial_line(port="/dev/ttyUSB0", encoding="utf-8")
{
"line": "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47"
}
```
### Mixed Text/Binary Protocols
Some protocols embed binary data within text framing. Handle these by:
1. Reading with Latin-1 to preserve all bytes
2. Parsing the text portions as needed
3. Extracting binary payloads by position
```json
// Read with Latin-1 to preserve everything
// read_serial(port="/dev/ttyUSB0", size=100, encoding="latin-1")
// Then parse: "DATA:" prefix + 4-byte length + binary payload + "\r\n"
```
---
## Invalid Byte Handling
When decoding with UTF-8 (or any multi-byte encoding), invalid sequences are replaced with <20> (U+FFFD). This is intentional — it prevents crashes and makes problems visible.
**Symptoms of encoding mismatch:**
| What You See | Likely Cause |
|-------------|--------------|
| Scattered <20> in output | Binary data decoded as UTF-8 |
| Truncated strings | Multi-byte sequence split across reads |
| Missing bytes | XON/XOFF stripping 0x11/0x13 |
**Diagnosis:**
```json
// Switch to Latin-1 to see raw bytes
// read_serial(port="/dev/ttyUSB0", encoding="latin-1")
// Or use the hex dump resource for full visibility
// Read resource: serial:///dev/ttyUSB0/raw
```
---
## Encoding Quick Reference
| Encoding | Byte Range | Use Case |
|----------|-----------|----------|
| `utf-8` | Multi-byte | Text protocols, console I/O, JSON (default) |
| `latin-1` | 0x000xFF → U+0000U+00FF | Binary protocols, raw byte passthrough |
| `ascii` | 0x000x7F | Strict 7-bit validation |
| (bytes) | 0255 | `write_serial_bytes` for explicit binary |
**Rules of thumb:**
1. **Text you can read?** Use UTF-8 (default)
2. **Binary protocol?** Use Latin-1 or `write_serial_bytes`
3. **Seeing <20> characters?** You're decoding binary as UTF-8 — switch to Latin-1
4. **Need to analyze raw bytes?** Use the `serial://{port}/raw` resource
---
## CRC and Checksum Calculation
Many binary protocols include error-checking bytes (CRC, checksum). When constructing frames:
1. Build the data portion as a byte array
2. Calculate the check value
3. Append the check bytes
4. Send via `write_serial_bytes` or Latin-1
**Example: Simple XOR checksum**
```python
# In your MCP client or preprocessing
data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x01]
checksum = 0
for b in data:
checksum ^= b
frame = data + [checksum]
# frame = [0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03]
```
Then send:
```json
// write_serial_bytes(port="/dev/ttyUSB0", data=[1, 3, 0, 0, 0, 1, 3])
```
<Aside type="note">
mcserial does not calculate CRCs or checksums for you. Protocol-specific framing is the responsibility of the calling code. mcserial provides the transport layer — reliable byte delivery — not protocol encoding.
</Aside>

View File

@ -190,3 +190,87 @@ If you suspect flow control issues, look for these symptoms:
- **Corrupted binary data with 0x11 or 0x13 bytes missing**: XON/XOFF is enabled on a binary stream. Switch to RTS/CTS or disable flow control.
Use `get_connection_status` to check the current flow control configuration and modem line states for all open ports.
---
## USB Adapter Compatibility
Not all USB-to-serial adapters support all flow control features. The chipset determines what's available:
| Chipset | RTS/CTS | DSR/DTR | XON/XOFF | Notes |
|---------|---------|---------|----------|-------|
| FTDI FT232R | ✅ | ✅ | ✅ | Full support, reliable |
| FTDI FT2232H | ✅ | ✅ | ✅ | Dual port, same quality |
| CP2102 | ✅ | ⚠️ | ✅ | DTR may need jumper |
| CP2104 | ✅ | ✅ | ✅ | Full support |
| CH340/CH341 | ⚠️ | ❌ | ✅ | RTS timing may be poor |
| PL2303 | ⚠️ | ⚠️ | ✅ | Varies by clone quality |
**Legend:** ✅ = reliable, ⚠️ = works but may have issues, ❌ = not supported or unreliable
<Aside type="caution" title="Cheap adapters and flow control">
Budget CH340 and PL2303 clone adapters often have poor flow control timing or don't implement hardware handshaking correctly. If you experience:
- **Dropped data at high baud rates** — the adapter isn't responding to CTS fast enough
- **RTS/CTS not affecting behavior** — lines may not be physically connected
Consider upgrading to FTDI-based adapters for critical applications requiring flow control.
</Aside>
### Debugging with modem lines
Use `get_modem_lines` to verify flow control lines are behaving as expected:
```json
// get_modem_lines(port="/dev/ttyUSB0")
{
"success": true,
"input_lines": {
"cts": false,
"dsr": false,
"ri": false,
"cd": false
},
"output_lines": {
"rts": true,
"dtr": true
}
}
```
If CTS is always false when you expect it to be asserted:
1. **Check the cable** — CTS may not be wired through
2. **Check the remote device** — it may not be asserting CTS
3. **Check the adapter** — cheap adapters may not report CTS accurately
### Deadlock scenarios
Flow control can cause communication to stall if both ends wait on each other:
**XON/XOFF deadlock:**
1. You send XOFF (buffer full)
2. Remote stops sending
3. Your code processes data and clears buffer
4. You forget to send XON
5. Remote waits forever
**RTS/CTS deadlock:**
1. You deassert RTS (not ready to receive)
2. Remote stops sending and waits for RTS
3. Your code waits for data
4. Nothing happens
**Prevention:**
- Always re-enable flow after processing (XON or reassert RTS)
- Use timeouts to detect stalls
- Check `get_modem_lines` to diagnose which side is blocking
```json
// If data stops flowing, check line states
// get_modem_lines(port="/dev/ttyUSB0")
// If CTS is false, the remote is blocking our sends
// If we've deasserted RTS, the remote is waiting for us
```

View File

@ -221,3 +221,81 @@ A streaming protocol that does not wait for per-block acknowledgments. It sends
- **Resume**: interrupted transfers can pick up where they left off
- **CRC-32**: stronger error detection than XMODEM/YMODEM's CRC-16
- **Variable block size**: adapts to line quality
---
## Error Recovery
### Interrupted Transfers
Transfers can fail due to cable disconnection, power loss, or communication errors. Each protocol handles recovery differently:
| Protocol | Recovery Method | Resume Supported |
|----------|----------------|------------------|
| XMODEM | Restart from beginning | No |
| XMODEM-1K | Restart from beginning | No |
| YMODEM | Restart from beginning | No |
| ZMODEM | Automatic resume from interruption point | Yes |
**ZMODEM resume:** When a ZMODEM transfer is interrupted and restarted, the receiver compares the incoming file header with any partial file on disk. If the file matches (same name, size, and modification time), it requests the sender to skip already-received data:
```json
// Transfer interrupted at 50% — just retry
// file_transfer_send(
// port="/dev/ttyUSB0",
// file_path="/home/user/large_firmware.bin",
// protocol="zmodem"
// )
{
"success": true,
"protocol": "zmodem",
"bytes_sent": 32768,
"resumed_at": 32768,
"total_size": 65536
}
```
For XMODEM and YMODEM, you must delete the partial file on the receiver and restart the entire transfer.
### Detecting Partial Transfers
ZMODEM and YMODEM include file size metadata in their headers. XMODEM does not — it pads the final block to 128 or 1024 bytes, so the received file may be slightly larger than the original.
**Verify transfer integrity:**
1. **Compare file sizes** — YMODEM/ZMODEM receivers know the expected size
2. **Check CRC/hash** — calculate a checksum on both ends
3. **Look for padding** — XMODEM files end with padding bytes (typically 0x1A or 0x00)
### Block Corruption
All protocols detect corruption via checksum or CRC. When corruption is detected:
1. **Receiver sends NAK** — requests retransmission
2. **Sender resends block** — the same block, not the entire file
3. **Retry limit** — after several failed attempts, the transfer aborts
Excessive retries indicate:
- **Electrical noise** — use shielded cables, shorter runs
- **Baud rate too high** — reduce speed for better reliability
- **Flow control mismatch** — enable RTS/CTS if available
```json
// Conservative settings for unreliable links
// configure_serial(port="/dev/ttyUSB0", baudrate=9600, rtscts=true)
```
### Common Transfer Errors
| Error | Likely Cause | Solution |
|-------|--------------|----------|
| "Receiver not ready" | Receiver not started | Start receiver before sender |
| Immediate timeout | Wrong protocol | Match protocol on both ends |
| Repeated NAKs | Line noise or baud mismatch | Lower baud rate, check cables |
| File exists error | Overwrite protection | Pass `overwrite=true` or delete existing |
| Size mismatch | XMODEM padding | Use YMODEM/ZMODEM for exact sizes |
<Aside type="tip" title="When in doubt, use ZMODEM">
ZMODEM handles almost every edge case gracefully: resume, corruption recovery, batch transfers, and accurate file sizes. Only fall back to XMODEM when the receiver doesn't support anything newer.
</Aside>

View File

@ -0,0 +1,283 @@
---
title: Timeout Tuning
description: Configuring read timeouts, inter-byte timeouts, and handling slow devices
sidebar:
order: 6
---
import { Aside } from '@astrojs/starlight/components';
Timeouts control how long mcserial waits for data before giving up. Too short, and you miss valid responses. Too long, and your workflow stalls waiting for devices that will never respond. This guide helps you find the right balance.
---
## Timeout Types
mcserial supports three timeout parameters, each serving a different purpose:
| Parameter | Where Set | What It Controls |
|-----------|-----------|------------------|
| `timeout` | `open_serial_port`, `configure_serial`, per-read | How long to wait for *any* data to arrive |
| `inter_byte_timeout` | `open_serial_port`, `configure_serial` | Max gap between consecutive bytes during a read |
| `write_timeout` | `open_serial_port`, `configure_serial` | How long to wait for the write buffer to drain |
---
## Read Timeout (timeout)
The read timeout is how long `read_serial`, `read_serial_line`, and similar operations wait before returning with whatever data has arrived (possibly nothing).
**Default:** 1.0 second
### Setting at open time
```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200, timeout=5.0)
{
"success": true,
"timeout": 5.0
}
```
### Changing on an open port
```json
// configure_serial(port="/dev/ttyUSB0", timeout=10.0)
{
"success": true,
"timeout": 10.0
}
```
### Per-read override
For one-off long waits without changing the port configuration:
```json
// read_serial(port="/dev/ttyUSB0", timeout=30.0)
```
This overrides the port's timeout for this single read, then reverts.
---
## Inter-Byte Timeout (inter_byte_timeout)
Inter-byte timeout specifies the maximum allowed gap between bytes *during* a read. If data starts arriving but then pauses longer than this threshold, the read completes with what has been received so far.
**Default:** None (disabled)
This is useful for protocols where the message length is unknown but messages end with a pause:
```json
// open_serial_port(
// port="/dev/ttyUSB0",
// baudrate=9600,
// timeout=5.0, // Wait up to 5s for first byte
// inter_byte_timeout=0.1 // Complete if 100ms gap between bytes
// )
```
**How it interacts with `timeout`:**
1. `timeout` controls waiting for the *first* byte
2. Once data starts arriving, `inter_byte_timeout` controls gaps between subsequent bytes
3. If either expires, the read returns
<Aside type="tip" title="When to use inter_byte_timeout">
Inter-byte timeout is most useful when:
- Message length varies and there's no explicit terminator
- The device sends data in bursts with gaps
- You want to capture complete responses without knowing their size
For protocols with terminators (newlines, specific byte sequences), prefer `read_until` or `read_serial_line` instead.
</Aside>
---
## Write Timeout (write_timeout)
Write timeout controls how long `write_serial` waits for data to be accepted by the OS buffer. Under normal conditions, writes complete immediately. Write timeout only matters when:
- The output buffer is full (flow control blocking)
- The driver is waiting on hardware handshaking
- The serial adapter is overwhelmed
**Default:** None (blocking — wait indefinitely)
```json
// configure_serial(port="/dev/ttyUSB0", write_timeout=2.0)
```
If a write times out, the operation returns an error and partial data may have been sent.
---
## Baud Rate and Minimum Timeout
Lower baud rates transmit bytes slower. Your timeout must account for transmission time:
| Baud Rate | Bytes/Second | Time for 100 bytes |
|-----------|--------------|-------------------|
| 9600 | ~960 | ~104ms |
| 19200 | ~1920 | ~52ms |
| 115200 | ~11520 | ~9ms |
| 1000000 | ~100000 | ~1ms |
**Rule of thumb:** Minimum timeout should be at least `(expected_bytes / bytes_per_second) * 2` to allow for processing time on the remote device.
For a 100-byte response at 9600 baud:
```
100 bytes / 960 bytes/sec = 0.104 sec
Minimum timeout = 0.104 * 2 = 0.2 sec
```
Add more margin for devices that need processing time before responding.
---
## Slow Device Patterns
### GPS Cold Start
GPS receivers can take 3060 seconds to acquire satellites and start outputting valid NMEA sentences during a cold start:
```json
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, timeout=60.0)
// Or use a moderate default and override per-read
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, timeout=1.0)
// read_serial_line(port="/dev/ttyUSB0", timeout=60.0)
```
### Industrial Sensors with Sample Cycles
Sensors that sample on a fixed interval may not respond until the next sample:
```json
// Sensor samples every 10 seconds
// configure_serial(port="/dev/ttyUSB0", timeout=12.0)
```
### Command-Response with Processing Delay
Devices that perform computation or flash writes before responding:
```json
// Write a command that triggers firmware flash
// write_serial(port="/dev/ttyUSB0", data="FLASH 0x1000\r\n")
// Wait for completion (could take several seconds)
// read_serial_line(port="/dev/ttyUSB0", timeout=30.0)
```
---
## Long Cables and Signal Degradation
Long serial cables introduce:
- **Propagation delay** — typically negligible (nanoseconds)
- **Signal degradation** — causes bit errors, retries, and timeouts
If you experience intermittent timeouts on long cable runs:
1. **Reduce baud rate** — lower rates are more tolerant of noise
2. **Enable hardware flow control** — RTS/CTS helps with buffering
3. **Check cable quality** — use shielded cables, avoid parallel power runs
4. **Add termination** — RS-485 buses need proper termination resistors
```json
// Conservative settings for a long cable
// open_serial_port(
// port="/dev/ttyUSB0",
// baudrate=9600, // Lower baud for reliability
// timeout=5.0, // Extra time for retries
// rtscts=true // Hardware flow control
// )
```
---
## Auto-Baud Timeout (autobaud_timeout)
When opening a port without specifying a baud rate, mcserial runs auto-detection. The `autobaud_timeout` parameter controls how long to wait at each candidate rate:
```json
// open_serial_port(
// port="/dev/ttyUSB0",
// autobaud_timeout=1.0 // Wait up to 1 second per baud rate candidate
// )
```
**Default:** 0.3 seconds
Increase this if:
- The device sends data infrequently
- You're also sending a probe string that needs time to be processed
```json
// Slow device that echoes with delay
// open_serial_port(
// port="/dev/ttyUSB0",
// autobaud_probe="AT\r\n",
// autobaud_timeout=0.5
// )
```
---
## Timeout Tuning Workflow
When you're not sure what timeout to use:
1. **Start conservative** — use a long timeout (1030 seconds)
2. **Verify communication works** — can you send and receive?
3. **Measure actual response times** — how long do real responses take?
4. **Add margin** — set timeout to 23× typical response time
5. **Handle timeouts gracefully** — zero bytes or timeout errors should trigger retry or user notification
**Example measurement:**
```json
// Time a typical exchange
// write_serial(port="/dev/ttyUSB0", data="STATUS\r\n")
// read_serial_line(port="/dev/ttyUSB0", timeout=30.0)
// If response arrives in 0.5 seconds, set timeout to 1.52.0 seconds
// configure_serial(port="/dev/ttyUSB0", timeout=2.0)
```
---
## Timeout Quick Reference
| Scenario | Recommended Timeout | Notes |
|----------|-------------------|-------|
| Fast device, known protocol | 0.51.0 s | Just enough for transmission + processing |
| Unknown device | 5.0 s | Conservative starting point |
| GPS cold start | 60.0 s | Satellite acquisition takes time |
| Firmware flash commands | 30.0+ s | Flash write cycles are slow |
| Modbus RTU | 0.10.5 s | Per-transaction, short timeout for bus response |
| Auto-baud detection | 0.31.0 s | Per candidate baud rate |
| Long cable (>15m) | 2× normal | Account for potential retries |
<Aside type="note" title="Timeouts and blocking">
A timeout of `0` means non-blocking — return immediately with whatever is in the buffer.
A timeout of `None` (the default for write_timeout) means blocking — wait forever.
For read operations, prefer a finite timeout to avoid hanging indefinitely if a device fails.
</Aside>
---
## Environment Variable Defaults
Set server-wide defaults via environment variables:
```bash
# Default read timeout for all new connections
export MCSERIAL_DEFAULT_TIMEOUT=5.0
```
Individual tool calls can still override these defaults. See [Environment Variables](/reference/environment/) for the complete list.

View File

@ -0,0 +1,470 @@
---
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]` |

View File

@ -170,6 +170,33 @@ For port discovery without opening, use `list_serial_ports(grep="FTDI")` to see
---
## Feature Comparison
Different URL schemes support different mcserial capabilities. Use this table to choose the right scheme for your needs:
| Scheme | Exclusive Access | Flow Control | Auto-Baud | Modem Lines | RS-485 | Notes |
|--------|-----------------|--------------|-----------|-------------|--------|-------|
| Device path | ✅ | ✅ | ✅ | ✅ | ✅ | Full feature set |
| `socket://` | ❌ | ❌ | ❌ | ❌ | ❌ | Raw TCP only |
| `rfc2217://` | ❌ | ✅ | ❌ | ✅ | ❌ | Via Telnet negotiation |
| `loop://` | ✅ | N/A | N/A | N/A | N/A | Testing only |
| `spy://` | ✅ | ✅ | ✅ | ✅ | ✅ | Wraps underlying device |
| `cp2110://` | ✅ | ❌ | ❌ | ❌ | ❌ | HID interface limitations |
| `hwgrep://` | ✅ | ✅ | ✅ | ✅ | ✅ | Opens matched device |
<Aside type="note" title="URL scheme limitations">
When opening via URL schemes (except `spy://` and `hwgrep://`, which wrap physical devices):
- `inter_byte_timeout` is not passed to URL-based connections
- `exclusive` access cannot be enforced
- Auto-baud detection is skipped
- Some operations may silently fail or return defaults
For full functionality, prefer physical device paths when possible.
</Aside>
---
## Summary
| Scheme | Use Case | Remote Config | Hardware Required |