diff --git a/docs/src/content/docs/concepts/encoding-and-binary.mdx b/docs/src/content/docs/concepts/encoding-and-binary.mdx new file mode 100644 index 0000000..6d6f29e --- /dev/null +++ b/docs/src/content/docs/concepts/encoding-and-binary.mdx @@ -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 (�) 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 0x00–0x7F) 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 +} +``` + + + +--- + +## Latin-1: The Raw Byte Passthrough + +Latin-1 (ISO-8859-1) maps bytes 0x00–0xFF directly to Unicode code points U+0000–U+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 0x00–0x7F. 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") +``` + + + +--- + +## 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 (0–255) 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`: + + + + ```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") + ``` + + + ```json + // Same request using byte array + // write_serial_bytes( + // port="/dev/ttyUSB0", + // data=[0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A] + // ) + ``` + + + +### 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 � (U+FFFD). This is intentional — it prevents crashes and makes problems visible. + +**Symptoms of encoding mismatch:** + +| What You See | Likely Cause | +|-------------|--------------| +| Scattered � 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` | 0x00–0xFF → U+0000–U+00FF | Binary protocols, raw byte passthrough | +| `ascii` | 0x00–0x7F | Strict 7-bit validation | +| (bytes) | 0–255 | `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 � 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]) +``` + + diff --git a/docs/src/content/docs/concepts/flow-control.mdx b/docs/src/content/docs/concepts/flow-control.mdx index e11e6a3..f42c954 100644 --- a/docs/src/content/docs/concepts/flow-control.mdx +++ b/docs/src/content/docs/concepts/flow-control.mdx @@ -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 + + + +### 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 +``` diff --git a/docs/src/content/docs/guides/file-transfers.mdx b/docs/src/content/docs/guides/file-transfers.mdx index 3912d20..e35e254 100644 --- a/docs/src/content/docs/guides/file-transfers.mdx +++ b/docs/src/content/docs/guides/file-transfers.mdx @@ -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 | + + diff --git a/docs/src/content/docs/guides/timeout-tuning.mdx b/docs/src/content/docs/guides/timeout-tuning.mdx new file mode 100644 index 0000000..96cc8ef --- /dev/null +++ b/docs/src/content/docs/guides/timeout-tuning.mdx @@ -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 + + + +--- + +## 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 30–60 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 (10–30 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 2–3× 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.5–2.0 seconds +// configure_serial(port="/dev/ttyUSB0", timeout=2.0) +``` + +--- + +## Timeout Quick Reference + +| Scenario | Recommended Timeout | Notes | +|----------|-------------------|-------| +| Fast device, known protocol | 0.5–1.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.1–0.5 s | Per-transaction, short timeout for bus response | +| Auto-baud detection | 0.3–1.0 s | Per candidate baud rate | +| Long cable (>15m) | 2× normal | Account for potential retries | + + + +--- + +## 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. diff --git a/docs/src/content/docs/guides/troubleshooting.mdx b/docs/src/content/docs/guides/troubleshooting.mdx new file mode 100644 index 0000000..e5b284b --- /dev/null +++ b/docs/src/content/docs/guides/troubleshooting.mdx @@ -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. + + + + ```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 + ``` + + + ```bash + # Check your groups + groups + # Should include: dialout + + # Check device permissions + ls -la /dev/ttyUSB0 + # Should show: crw-rw---- 1 root dialout ... + ``` + + + +### 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" + } + ] +} +``` + + + +### Graceful reconnection workflow + + + +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) + ``` + + + +--- + +## 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) +``` + + + +--- + +## 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]` | diff --git a/docs/src/content/docs/reference/url-handlers.mdx b/docs/src/content/docs/reference/url-handlers.mdx index cc90559..c066d08 100644 --- a/docs/src/content/docs/reference/url-handlers.mdx +++ b/docs/src/content/docs/reference/url-handlers.mdx @@ -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 | + + + +--- + ## Summary | Scheme | Use Case | Remote Config | Hardware Required |