Add Starlight documentation site with full content
Astro 5 + Starlight docs site with 17 pages covering all 30 mcserial tools. Structured using Diataxis framework: tutorials, guides, reference, and concepts. Includes Docker + caddy-docker-proxy deployment for mcserial.l.zmesh.systems with HMR support behind reverse proxy. Content pages: - Landing page with feature overview and quick start - Tutorials: getting started, loopback testing (loop://) - Guides: RS-232 basics, RS-485/Modbus, file transfers, network ports - Reference: common tools, RS-232 tools, RS-485 tools, file transfer tools, URL schemes, MCP resources, environment variables - Concepts: RS-232 vs RS-485, flow control
This commit is contained in:
parent
5c655fe743
commit
dc8caddb8f
64
README.md
64
README.md
@ -4,10 +4,11 @@ FastMCP server for serial port access via Model Context Protocol.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **RS-232 Mode**: Full modem control (RTS, DTR, CTS, DSR, RI, CD)
|
- **RS-232 Mode**: Full modem control (RTS, DTR, CTS, DSR, RI, CD, break condition)
|
||||||
- **RS-485 Mode**: Half-duplex bus communication with auto direction control
|
- **RS-485 Mode**: Half-duplex bus communication with auto direction control
|
||||||
- **File Transfer**: X/Y/ZMODEM protocols for reliable file transfers
|
- **File Transfer**: X/Y/ZMODEM protocols for reliable file transfers
|
||||||
- **Auto-baud Detection**: Smart detection using 0x55 sync pattern analysis
|
- **Auto-baud Detection**: Smart detection using 0x55 sync pattern analysis
|
||||||
|
- **URL Handlers**: Open remote/virtual ports (`socket://`, `rfc2217://`, `loop://`, `spy://`, `cp2110://`)
|
||||||
- **Dynamic Resources**: Read data via `serial://{port}/data`
|
- **Dynamic Resources**: Read data via `serial://{port}/data`
|
||||||
- Full pyserial support (baudrate, parity, stop bits, flow control)
|
- Full pyserial support (baudrate, parity, stop bits, flow control)
|
||||||
|
|
||||||
@ -19,6 +20,9 @@ uvx mcserial
|
|||||||
|
|
||||||
# Or install directly
|
# Or install directly
|
||||||
uv pip install mcserial
|
uv pip install mcserial
|
||||||
|
|
||||||
|
# With CP2110 HID-to-UART support
|
||||||
|
uv pip install mcserial[cp2110]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage with Claude Code
|
## Usage with Claude Code
|
||||||
@ -43,17 +47,22 @@ Ports open in **RS-232 mode** by default. Switch with `set_port_mode()`:
|
|||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `list_serial_ports` | Discover available serial ports |
|
| `list_serial_ports` | Discover available ports (supports `grep` regex filtering) |
|
||||||
| `open_serial_port` | Open connection (auto-detects baud if not specified) |
|
| `open_serial_port` | Open connection — local device or URL scheme (auto-detects baud if not specified) |
|
||||||
| `close_serial_port` | Close a connection |
|
| `close_serial_port` | Close a connection |
|
||||||
| `set_port_mode` | Switch between RS-232 and RS-485 modes |
|
| `set_port_mode` | Switch between RS-232 and RS-485 modes |
|
||||||
| `write_serial` | Send text data |
|
| `write_serial` | Send text data |
|
||||||
| `write_serial_bytes` | Send raw bytes |
|
| `write_serial_bytes` | Send raw bytes (atomic single-syscall write) |
|
||||||
| `read_serial` | Read available data |
|
| `read_serial` | Read available data |
|
||||||
| `read_serial_line` | Read until newline |
|
| `read_serial_line` | Read until newline |
|
||||||
|
| `read_serial_lines` | Batch read multiple lines |
|
||||||
| `read_until` | Read until custom terminator |
|
| `read_until` | Read until custom terminator |
|
||||||
| `configure_serial` | Change port settings |
|
| `configure_serial` | Change port settings |
|
||||||
| `flush_serial` | Clear buffers |
|
| `flush_serial` | Clear buffers |
|
||||||
|
| `cancel_read` | Interrupt pending read operation |
|
||||||
|
| `cancel_write` | Interrupt pending write operation |
|
||||||
|
| `set_flow_control` | Manually gate XON/XOFF or RTS/CTS flow |
|
||||||
|
| `set_low_latency_mode` | Enable kernel low-latency mode (Linux) |
|
||||||
| `get_connection_status` | List open connections with mode |
|
| `get_connection_status` | List open connections with mode |
|
||||||
| `detect_baud_rate` | Auto-detect baud rate |
|
| `detect_baud_rate` | Auto-detect baud rate |
|
||||||
|
|
||||||
@ -61,7 +70,7 @@ Ports open in **RS-232 mode** by default. Switch with `set_port_mode()`:
|
|||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `get_modem_lines` | Read CTS, DSR, RI, CD, RTS, DTR states |
|
| `get_modem_lines` | Read CTS, DSR, RI, CD, RTS, DTR states + break condition |
|
||||||
| `set_modem_lines` | Control RTS and DTR outputs |
|
| `set_modem_lines` | Control RTS and DTR outputs |
|
||||||
| `pulse_line` | Pulse RTS/DTR for reset sequences |
|
| `pulse_line` | Pulse RTS/DTR for reset sequences |
|
||||||
| `send_break` | Send timed break signal |
|
| `send_break` | Send timed break signal |
|
||||||
@ -90,6 +99,22 @@ Ports open in **RS-232 mode** by default. Switch with `set_port_mode()`:
|
|||||||
- `ymodem` - Batch mode with filename/size
|
- `ymodem` - Batch mode with filename/size
|
||||||
- `zmodem` - Streaming, auto-resume (recommended)
|
- `zmodem` - Streaming, auto-resume (recommended)
|
||||||
|
|
||||||
|
## URL Handlers
|
||||||
|
|
||||||
|
The `open_serial_port` tool accepts URL schemes in addition to local device paths:
|
||||||
|
|
||||||
|
| Scheme | Description | Example |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `socket://` | Raw TCP socket — serial-to-ethernet bridges | `socket://192.168.1.100:4001` |
|
||||||
|
| `rfc2217://` | Telnet COM Port Control — remote baud/flow config | `rfc2217://192.168.1.100:2217` |
|
||||||
|
| `loop://` | Loopback — writes echo back as reads (testing) | `loop://` |
|
||||||
|
| `spy://` | Debug wrapper — logs all traffic to stderr | `spy:///dev/ttyUSB0` |
|
||||||
|
| `cp2110://` | Silicon Labs HID-to-UART (requires `[cp2110]` extra) | `cp2110://` |
|
||||||
|
| `hwgrep://` | Open first port matching hardware pattern | `hwgrep://FTDI` |
|
||||||
|
| `alt://` | Alternate port backend | `alt:///dev/ttyUSB0` |
|
||||||
|
|
||||||
|
URL-opened ports skip auto-baud detection and exclusive access (not applicable to virtual/network ports).
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
| URI | Description |
|
| URI | Description |
|
||||||
@ -114,11 +139,19 @@ Ports open in **RS-232 mode** by default. Switch with `set_port_mode()`:
|
|||||||
```
|
```
|
||||||
1. list_serial_ports() → find /dev/ttyUSB0
|
1. list_serial_ports() → find /dev/ttyUSB0
|
||||||
2. open_serial_port(port="/dev/ttyUSB0") # auto-detects baud
|
2. open_serial_port(port="/dev/ttyUSB0") # auto-detects baud
|
||||||
3. write_serial(port="/dev/ttyUSB0", data="AT\r\n")
|
3. write_serial_bytes(port="/dev/ttyUSB0", data=[65, 84, 13, 10]) # AT\r\n
|
||||||
4. Read resource: serial:///dev/ttyUSB0/data
|
4. read_serial_lines(port="/dev/ttyUSB0") # batch read response
|
||||||
5. close_serial_port(port="/dev/ttyUSB0")
|
5. close_serial_port(port="/dev/ttyUSB0")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Filter Ports by Hardware
|
||||||
|
|
||||||
|
```
|
||||||
|
1. list_serial_ports(grep="FTDI") # find all FTDI devices
|
||||||
|
2. list_serial_ports(grep="CP210") # find Silicon Labs adapters
|
||||||
|
3. list_serial_ports(grep="VID:PID=0403:6001") # exact USB ID match
|
||||||
|
```
|
||||||
|
|
||||||
### RS-485 Modbus
|
### RS-485 Modbus
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -128,6 +161,23 @@ Ports open in **RS-232 mode** by default. Switch with `set_port_mode()`:
|
|||||||
4. rs485_transact(port="/dev/ttyUSB0", data="\x01\x03...")
|
4. rs485_transact(port="/dev/ttyUSB0", data="\x01\x03...")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Network Serial (serial-to-ethernet bridge)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. open_serial_port(port="socket://192.168.1.100:4001", baudrate=115200)
|
||||||
|
2. write_serial(port="socket://192.168.1.100:4001", data="AT\r\n")
|
||||||
|
3. read_serial(port="socket://192.168.1.100:4001")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loopback Testing (no hardware needed)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
2. write_serial(port="loop://", data="hello")
|
||||||
|
3. read_serial(port="loop://") # → "hello"
|
||||||
|
4. close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
### File Transfer
|
### File Transfer
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
17
docs/.gitignore
vendored
Normal file
17
docs/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
48
docs/Dockerfile
Normal file
48
docs/Dockerfile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Base stage — Node 22 slim
|
||||||
|
# ============================================
|
||||||
|
FROM node:22-slim AS base
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Development — hot reload
|
||||||
|
# ============================================
|
||||||
|
FROM base AS dev
|
||||||
|
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 4321
|
||||||
|
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Build — static site generation
|
||||||
|
# ============================================
|
||||||
|
FROM base AS build
|
||||||
|
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Production — Caddy serves static files
|
||||||
|
# ============================================
|
||||||
|
FROM caddy:2-alpine AS prod
|
||||||
|
|
||||||
|
COPY --from=build /app/dist /usr/share/caddy
|
||||||
|
|
||||||
|
RUN echo ':80 { \
|
||||||
|
root * /usr/share/caddy \
|
||||||
|
encode gzip \
|
||||||
|
try_files {path} {path}/ /index.html \
|
||||||
|
file_server \
|
||||||
|
}' > /etc/caddy/Caddyfile
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]
|
||||||
50
docs/Makefile
Normal file
50
docs/Makefile
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
.PHONY: dev prod build logs stop clean restart shell local local-build
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := dev
|
||||||
|
|
||||||
|
# Development mode with hot reload
|
||||||
|
dev:
|
||||||
|
@echo "Starting mcserial docs in development mode..."
|
||||||
|
APP_ENV=dev docker compose up -d --build
|
||||||
|
@echo "Site available at https://$(shell grep PUBLIC_DOMAIN .env | cut -d= -f2)"
|
||||||
|
@sleep 2
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Production mode with static build
|
||||||
|
prod:
|
||||||
|
@echo "Starting mcserial docs in production mode..."
|
||||||
|
APP_ENV=prod docker compose up -d --build
|
||||||
|
@echo "Site available at https://$(shell grep PUBLIC_DOMAIN .env | cut -d= -f2)"
|
||||||
|
@sleep 2
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Build without starting
|
||||||
|
build:
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
logs:
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Stop containers
|
||||||
|
stop:
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Clean up containers, images, volumes
|
||||||
|
clean:
|
||||||
|
docker compose down -v --rmi local
|
||||||
|
|
||||||
|
# Restart containers
|
||||||
|
restart: stop dev
|
||||||
|
|
||||||
|
# Shell into running container
|
||||||
|
shell:
|
||||||
|
docker compose exec docs sh
|
||||||
|
|
||||||
|
# Local development without Docker
|
||||||
|
local:
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Production build locally
|
||||||
|
local-build:
|
||||||
|
npm run build && npm run preview
|
||||||
76
docs/astro.config.mjs
Normal file
76
docs/astro.config.mjs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: process.env.PUBLIC_DOMAIN
|
||||||
|
? `https://${process.env.PUBLIC_DOMAIN}`
|
||||||
|
: 'http://localhost:4321',
|
||||||
|
|
||||||
|
telemetry: false,
|
||||||
|
|
||||||
|
devToolbar: { enabled: false },
|
||||||
|
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: process.env.PUBLIC_DOMAIN
|
||||||
|
? [process.env.PUBLIC_DOMAIN, 'localhost']
|
||||||
|
: ['localhost'],
|
||||||
|
...(process.env.VITE_HMR_HOST && {
|
||||||
|
hmr: {
|
||||||
|
host: process.env.VITE_HMR_HOST,
|
||||||
|
protocol: 'wss',
|
||||||
|
clientPort: 443,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'mcserial',
|
||||||
|
description: 'MCP server for serial port communication — RS-232, RS-485, and file transfers via Model Context Protocol',
|
||||||
|
|
||||||
|
logo: {
|
||||||
|
src: './src/assets/logo.svg',
|
||||||
|
replacesTitle: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
social: [
|
||||||
|
{
|
||||||
|
icon: 'github',
|
||||||
|
label: 'GitHub',
|
||||||
|
href: 'https://github.com/supported-systems/mcserial',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
customCss: ['./src/styles/custom.css'],
|
||||||
|
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
label: 'Overview',
|
||||||
|
items: [{ label: 'Introduction', link: '/' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tutorials',
|
||||||
|
autogenerate: { directory: 'tutorials' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Guides',
|
||||||
|
autogenerate: { directory: 'guides' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reference',
|
||||||
|
autogenerate: { directory: 'reference' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Concepts',
|
||||||
|
autogenerate: { directory: 'concepts' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
tableOfContents: { minHeadingLevel: 2, maxHeadingLevel: 3 },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
35
docs/docker-compose.yml
Normal file
35
docs/docker-compose.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
docs:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: ${APP_ENV:-dev}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- PUBLIC_DOMAIN=${PUBLIC_DOMAIN:-mcserial.l.zmesh.systems}
|
||||||
|
- VITE_HMR_HOST=${PUBLIC_DOMAIN:-mcserial.l.zmesh.systems}
|
||||||
|
- ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
volumes:
|
||||||
|
# Dev mode: mount source for hot reload
|
||||||
|
- ./src:/app/src:ro
|
||||||
|
- ./public:/app/public:ro
|
||||||
|
- ./astro.config.mjs:/app/astro.config.mjs:ro
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
labels:
|
||||||
|
# Caddy reverse proxy
|
||||||
|
caddy: ${PUBLIC_DOMAIN:-mcserial.l.zmesh.systems}
|
||||||
|
caddy.reverse_proxy: "{{upstreams 4321}}"
|
||||||
|
|
||||||
|
# WebSocket/HMR support for dev mode
|
||||||
|
caddy.reverse_proxy.flush_interval: "-1"
|
||||||
|
caddy.reverse_proxy.transport: "http"
|
||||||
|
caddy.reverse_proxy.transport.read_timeout: "0"
|
||||||
|
caddy.reverse_proxy.transport.write_timeout: "0"
|
||||||
|
caddy.reverse_proxy.transport.keepalive: "5m"
|
||||||
|
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
|
||||||
|
caddy.reverse_proxy.stream_timeout: "24h"
|
||||||
|
caddy.reverse_proxy.stream_close_delay: "5s"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
6394
docs/package-lock.json
generated
Normal file
6394
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
docs/package.json
Normal file
17
docs/package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "mcserial-docs",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"start": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/starlight": "^0.37.4",
|
||||||
|
"astro": "5.16.15",
|
||||||
|
"sharp": "^0.34.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
docs/public/favicon.svg
Normal file
12
docs/public/favicon.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect x="3" y="8" width="26" height="16" rx="3" stroke="#0d9488" stroke-width="2" fill="none"/>
|
||||||
|
<circle cx="9" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="13" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="17" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="21" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="25" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="11" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="15" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="19" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="23" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 636 B |
17
docs/src/assets/logo.svg
Normal file
17
docs/src/assets/logo.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<!-- DB-9 connector outline -->
|
||||||
|
<rect x="3" y="8" width="26" height="16" rx="3" stroke="#0d9488" stroke-width="2" fill="none"/>
|
||||||
|
<!-- Trapezoidal shape suggestion -->
|
||||||
|
<path d="M6 10 L26 10 L24 22 L8 22 Z" stroke="#0d9488" stroke-width="1.5" fill="none" opacity="0.4"/>
|
||||||
|
<!-- Pin row 1 (5 pins) -->
|
||||||
|
<circle cx="9" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="13" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="17" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="21" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="25" cy="14" r="1.5" fill="#0d9488"/>
|
||||||
|
<!-- Pin row 2 (4 pins) -->
|
||||||
|
<circle cx="11" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="15" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="19" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
<circle cx="23" cy="19" r="1.5" fill="#0d9488"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 874 B |
7
docs/src/content.config.ts
Normal file
7
docs/src/content.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||||
|
import { docsSchema } from '@astrojs/starlight/schema';
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||||
|
};
|
||||||
192
docs/src/content/docs/concepts/flow-control.mdx
Normal file
192
docs/src/content/docs/concepts/flow-control.mdx
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
---
|
||||||
|
title: Flow Control
|
||||||
|
description: Understanding software and hardware flow control for serial communication
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Flow control prevents data loss when one side of a serial connection sends data faster than the other side can process it. Without flow control, the receiver's buffer fills up, and incoming bytes are silently dropped.
|
||||||
|
|
||||||
|
There are two approaches: software flow control using in-band control characters, and hardware flow control using dedicated signal lines.
|
||||||
|
|
||||||
|
## Software flow control (XON/XOFF)
|
||||||
|
|
||||||
|
Software flow control uses two special byte values embedded in the data stream:
|
||||||
|
|
||||||
|
| Character | Hex | ASCII | Meaning |
|
||||||
|
|-----------|-----|-------|---------|
|
||||||
|
| XON | `0x11` | Ctrl-Q | "Resume sending -- I can accept data" |
|
||||||
|
| XOFF | `0x13` | Ctrl-S | "Stop sending -- my buffer is full" |
|
||||||
|
|
||||||
|
When the receiver's buffer fills up, it sends XOFF to the sender. The sender pauses until it receives XON, then resumes transmission.
|
||||||
|
|
||||||
|
### Enabling XON/XOFF
|
||||||
|
|
||||||
|
Enable it when opening a port:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, xonxoff=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or toggle it on an open port:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// configure_serial(port="/dev/ttyUSB0", xonxoff=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"xonxoff": true,
|
||||||
|
"rtscts": false,
|
||||||
|
"dsrdtr": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual flow gating
|
||||||
|
|
||||||
|
The `set_flow_control` tool lets you manually send XON/XOFF signals or pause/resume your own output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Tell the remote device to stop sending
|
||||||
|
// set_flow_control(port="/dev/ttyUSB0", input_flow=false)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"changes": {"input_flow": false},
|
||||||
|
"flow_control_enabled": {"xonxoff": true, "rtscts": false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the remote device to resume sending
|
||||||
|
// set_flow_control(port="/dev/ttyUSB0", input_flow=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `input_flow=true` sends XON (allow the remote device to send to us)
|
||||||
|
- `input_flow=false` sends XOFF (tell the remote device to pause)
|
||||||
|
- `output_flow=true` resumes our outgoing data
|
||||||
|
- `output_flow=false` pauses our outgoing data
|
||||||
|
|
||||||
|
Flow control must already be enabled (via `xonxoff=true` or `rtscts=true`) for these signals to have effect.
|
||||||
|
|
||||||
|
### Advantages and limitations
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- Works on any serial connection, including 3-wire (TX, RX, GND) cables with no additional signal lines
|
||||||
|
- Works over network serial connections (`socket://`, `rfc2217://`)
|
||||||
|
- No extra hardware required
|
||||||
|
|
||||||
|
<Aside type="caution" title="Binary data and XON/XOFF">
|
||||||
|
XON/XOFF reserves the byte values 0x11 and 0x13 as control characters. If your data stream naturally contains these values -- binary files, compressed data, encrypted payloads, protocol frames with arbitrary byte values -- the flow control logic will misinterpret data bytes as flow signals.
|
||||||
|
|
||||||
|
This causes two problems: the receiver strips the "control" bytes from your data (silent corruption), and the sender may pause unexpectedly when it encounters what it thinks is an XOFF signal.
|
||||||
|
|
||||||
|
If you are transferring binary data, use hardware flow control (RTS/CTS) instead, or disable flow control entirely and manage buffer sizes at the application layer.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Hardware flow control (RTS/CTS)
|
||||||
|
|
||||||
|
Hardware flow control uses dedicated signal lines rather than in-band bytes. The most common pair is **RTS** (Request To Send) and **CTS** (Clear To Send).
|
||||||
|
|
||||||
|
When the receiver's buffer is getting full, it deasserts CTS. The sender watches CTS and pauses transmission until CTS is asserted again. Because the signaling happens on separate wires, it works with any data content -- there are no reserved byte values.
|
||||||
|
|
||||||
|
### Enabling RTS/CTS
|
||||||
|
|
||||||
|
Enable it when opening a port:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200, rtscts=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or toggle it on an open port:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// configure_serial(port="/dev/ttyUSB0", rtscts=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"xonxoff": false,
|
||||||
|
"rtscts": true,
|
||||||
|
"dsrdtr": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advantages and limitations
|
||||||
|
|
||||||
|
**Advantages:**
|
||||||
|
- Works with binary data -- no reserved byte values
|
||||||
|
- Faster response than XON/XOFF (hardware signal vs. byte transmission delay)
|
||||||
|
- Handled by the UART hardware, not software
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- Requires a cable with RTS and CTS lines connected (some cheap cables omit them)
|
||||||
|
- Not available on 3-wire serial connections
|
||||||
|
- Not meaningful on `socket://` network connections (no physical lines to assert)
|
||||||
|
|
||||||
|
## Hardware flow control (DSR/DTR)
|
||||||
|
|
||||||
|
An alternative hardware flow control pair. DSR (Data Set Ready) and DTR (Data Terminal Ready) can serve as flow control signals when RTS/CTS is unavailable or already used for another purpose.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600, dsrdtr=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
DSR/DTR flow control is less common than RTS/CTS. Most devices that support hardware flow control use RTS/CTS.
|
||||||
|
|
||||||
|
<Aside type="tip" title="RS-485 and RTS">
|
||||||
|
In RS-485 mode, the RTS line is repurposed for TX direction control -- it enables or disables the bus driver, not flow control. If you need flow control on an RS-485 bus, use XON/XOFF (software flow control) or handle buffering at the application protocol level.
|
||||||
|
|
||||||
|
Do not enable `rtscts=true` on a port that is in RS-485 mode. The RTS line toggling for flow control would interfere with the direction control toggling, causing data corruption on the bus.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Choosing the right flow control
|
||||||
|
|
||||||
|
| Scenario | Recommended | Reason |
|
||||||
|
|----------|-------------|--------|
|
||||||
|
| Text/ASCII protocols (AT commands, console) | XON/XOFF | Simple, no extra wires needed |
|
||||||
|
| Binary protocols (Modbus RTU, firmware transfer) | RTS/CTS | No byte value conflicts |
|
||||||
|
| Network serial (`socket://`) | None | TCP handles flow control at the transport layer |
|
||||||
|
| High-speed transfers (115200+) | RTS/CTS | Faster response, less latency than XON/XOFF |
|
||||||
|
| 3-wire cable (TX, RX, GND only) | XON/XOFF | RTS/CTS lines not available |
|
||||||
|
| RS-485 bus | XON/XOFF or None | RTS is used for direction control |
|
||||||
|
| Unknown device | Start with None | Add flow control if you see buffer overruns |
|
||||||
|
|
||||||
|
## Flow control in mcserial
|
||||||
|
|
||||||
|
All flow control options are available in two places:
|
||||||
|
|
||||||
|
**At open time:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// baudrate=9600,
|
||||||
|
// xonxoff=false,
|
||||||
|
// rtscts=false,
|
||||||
|
// dsrdtr=false
|
||||||
|
// )
|
||||||
|
```
|
||||||
|
|
||||||
|
**On an already-open port:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// configure_serial(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// xonxoff=true,
|
||||||
|
// rtscts=false,
|
||||||
|
// dsrdtr=false
|
||||||
|
// )
|
||||||
|
```
|
||||||
|
|
||||||
|
You can enable multiple flow control methods simultaneously, though this is unusual. The most common configurations are:
|
||||||
|
|
||||||
|
- **No flow control** (all false) -- suitable when you control the data rate, or when using protocols that handle their own flow management
|
||||||
|
- **XON/XOFF only** -- text terminals, console access, simple ASCII protocols
|
||||||
|
- **RTS/CTS only** -- binary transfers, high-speed communication, GPS receivers
|
||||||
|
|
||||||
|
### Detecting flow control problems
|
||||||
|
|
||||||
|
If you suspect flow control issues, look for these symptoms:
|
||||||
|
|
||||||
|
- **Missing data in the middle of a transfer**: the receiver's buffer overflowed and bytes were dropped. Enable flow control.
|
||||||
|
- **Data stops flowing and never resumes**: the sender received XOFF but never got XON (or CTS was deasserted and never reasserted). Check cable connections and remote device state.
|
||||||
|
- **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.
|
||||||
107
docs/src/content/docs/concepts/rs232-vs-rs485.mdx
Normal file
107
docs/src/content/docs/concepts/rs232-vs-rs485.mdx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: RS-232 vs RS-485
|
||||||
|
description: Understanding the differences between RS-232 and RS-485 serial communication
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
RS-232 and RS-485 are two serial communication standards that solve different problems. RS-232 connects one device to another over short distances. RS-485 connects many devices on a shared bus over long distances. Understanding when to use each one determines which mcserial tools you reach for.
|
||||||
|
|
||||||
|
## Physical layer
|
||||||
|
|
||||||
|
The fundamental difference is how voltage signals are represented on the wire.
|
||||||
|
|
||||||
|
**RS-232** uses single-ended signaling: each signal is measured as a voltage relative to ground. A logic HIGH is -3V to -15V, and a logic LOW is +3V to +15V (inverted from what you might expect). This means every signal needs its own wire plus a shared ground, and the voltage swings are large.
|
||||||
|
|
||||||
|
**RS-485** uses differential signaling: each signal is carried on a pair of wires (typically labeled A and B). The receiver reads the voltage *difference* between the two wires, ignoring any common-mode noise that affects both wires equally. This is what gives RS-485 its noise immunity.
|
||||||
|
|
||||||
|
| Property | RS-232 | RS-485 |
|
||||||
|
|----------|--------|--------|
|
||||||
|
| Signaling | Single-ended (voltage to ground) | Differential (voltage between A and B) |
|
||||||
|
| Voltage levels | +/-3V to +/-15V | +/-1.5V to +/-6V differential |
|
||||||
|
| Max cable length | ~15 meters (50 feet) | ~1200 meters (4000 feet) |
|
||||||
|
| Max devices | 2 (point-to-point) | 32 standard, 256 with high-impedance receivers |
|
||||||
|
| Duplex | Full-duplex (separate TX and RX lines) | Half-duplex on 2 wires, full-duplex on 4 wires |
|
||||||
|
| Noise immunity | Low (susceptible to ground loops) | High (differential rejection of common-mode noise) |
|
||||||
|
| Typical connectors | DB-9, DB-25 | Screw terminals, RJ-45, DB-9 (repurposed) |
|
||||||
|
|
||||||
|
## Communication models
|
||||||
|
|
||||||
|
### RS-232: point-to-point
|
||||||
|
|
||||||
|
RS-232 is always a conversation between two devices. One device (the DTE, Data Terminal Equipment -- originally a terminal or computer) talks to another device (the DCE, Data Communications Equipment -- originally a modem). They have separate transmit and receive lines, so both can send data simultaneously (full-duplex).
|
||||||
|
|
||||||
|
RS-232 also defines modem control lines -- dedicated signals for hardware flow control, device presence, and status:
|
||||||
|
|
||||||
|
- **DTR / DSR**: "I'm here and ready" handshake
|
||||||
|
- **RTS / CTS**: "I want to send" / "Go ahead" flow control
|
||||||
|
- **RI**: "There's an incoming call" (modems)
|
||||||
|
- **CD**: "I have a connection" (modems)
|
||||||
|
|
||||||
|
These extra lines are what make RS-232 useful for tasks beyond raw data transfer: you can detect whether a device is connected, pause data flow when a buffer fills up, or pulse DTR to reset a microcontroller.
|
||||||
|
|
||||||
|
### RS-485: multi-drop bus
|
||||||
|
|
||||||
|
RS-485 is a shared bus. Multiple devices connect to the same pair of wires. Only one device transmits at a time while all others listen. This requires a protocol layer on top of the electrical standard to manage who talks when -- Modbus RTU being the most common example.
|
||||||
|
|
||||||
|
Because the bus is shared, RS-485 devices need a way to switch between transmitting and receiving. The transceiver chip has a Driver Enable (DE) and Receiver Enable (RE) pin. When a device wants to transmit, it asserts DE to drive the bus, sends its data, then deasserts DE to go back to listening. Some adapters handle this toggling in hardware; others require software control via the RTS line.
|
||||||
|
|
||||||
|
## When to use RS-232
|
||||||
|
|
||||||
|
RS-232 is the right choice when:
|
||||||
|
|
||||||
|
- **You are connecting directly to a single device** -- a sensor, a microcontroller, a modem, or a piece of lab equipment.
|
||||||
|
- **You need modem control lines** -- DTR for resetting an Arduino, CTS for hardware flow control, DSR for detecting device presence.
|
||||||
|
- **Cable runs are short** -- within the same desk, rack, or room (under 15 meters).
|
||||||
|
- **You are using a USB-serial adapter** -- the vast majority default to RS-232 behavior.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Most USB-serial adapters (FTDI, CP210x, CH340, PL2303) present as RS-232 ports by default. Even adapters marketed as "RS-485" typically include an RS-485 transceiver chip on the board but still appear as a standard serial port to the operating system. The difference is in the electrical output, not the software interface.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## When to use RS-485
|
||||||
|
|
||||||
|
RS-485 is the right choice when:
|
||||||
|
|
||||||
|
- **Multiple devices share one bus** -- sensor networks, building automation, industrial control. RS-485 supports up to 32 devices (standard) or 256 (with high-impedance receivers) on a single pair of wires.
|
||||||
|
- **Long cable runs** -- RS-485's differential signaling works reliably over 1200 meters. RS-232 becomes unreliable beyond 15 meters.
|
||||||
|
- **Industrial environments** -- factories, power plants, and outdoor installations where electrical noise from motors, inverters, and power supplies would corrupt RS-232 signals. Differential signaling rejects common-mode noise.
|
||||||
|
- **Industrial protocols** -- Modbus RTU, PROFIBUS, and DMX-512 all run over RS-485.
|
||||||
|
- **Noisy environments** -- even in non-industrial settings, long cable runs near power wiring benefit from differential signaling.
|
||||||
|
|
||||||
|
## How mcserial handles the difference
|
||||||
|
|
||||||
|
mcserial tracks which mode each port is in and restricts tools accordingly. This prevents accidentally sending RS-232 modem control commands on an RS-485 bus (which would disrupt communication by toggling the direction control line).
|
||||||
|
|
||||||
|
### Default: RS-232 mode
|
||||||
|
|
||||||
|
Every port opens in RS-232 mode. The following tools are available:
|
||||||
|
|
||||||
|
- `get_modem_lines` -- read CTS, DSR, RI, CD, RTS, DTR
|
||||||
|
- `set_modem_lines` -- set RTS and DTR
|
||||||
|
- `pulse_line` -- pulse RTS or DTR for reset sequences
|
||||||
|
- `send_break` -- send a timed break signal
|
||||||
|
- `set_break_condition` -- hold or release sustained break
|
||||||
|
|
||||||
|
### After switching: RS-485 mode
|
||||||
|
|
||||||
|
Call `set_port_mode(port, "rs485")` to switch. The RS-232 tools become unavailable and these tools become available:
|
||||||
|
|
||||||
|
- `set_rs485_mode` -- configure hardware DE/RE direction control
|
||||||
|
- `rs485_transact` -- send data and receive response with automatic turnaround
|
||||||
|
- `rs485_scan_addresses` -- discover devices on the bus
|
||||||
|
- `check_rs485_support` -- determine hardware capabilities
|
||||||
|
|
||||||
|
### Shared tools
|
||||||
|
|
||||||
|
These tools work in both modes:
|
||||||
|
|
||||||
|
- `open_serial_port` / `close_serial_port`
|
||||||
|
- `read_serial` / `write_serial` / `write_serial_bytes`
|
||||||
|
- `read_serial_line` / `read_serial_lines` / `read_until`
|
||||||
|
- `configure_serial` / `flush_serial`
|
||||||
|
- `detect_baud_rate`
|
||||||
|
- `set_flow_control` / `set_low_latency_mode`
|
||||||
|
- `file_transfer_send` / `file_transfer_receive` / `file_transfer_send_batch`
|
||||||
|
|
||||||
|
You can switch modes on an open port at any time with `set_port_mode`. No reconnection is needed.
|
||||||
221
docs/src/content/docs/guides/file-transfers.mdx
Normal file
221
docs/src/content/docs/guides/file-transfers.mdx
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
---
|
||||||
|
title: File Transfers
|
||||||
|
description: Sending and receiving files over serial using X/Y/ZMODEM protocols
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Serial file transfers are useful when you need to move files to or from devices that lack network connectivity: embedded systems running a bootloader, legacy industrial equipment, air-gapped machines, or remote hardware accessed through a serial console.
|
||||||
|
|
||||||
|
mcserial implements four protocol variants, all built in without external dependencies.
|
||||||
|
|
||||||
|
## Protocol comparison
|
||||||
|
|
||||||
|
| Protocol | Block Size | Batch | Resume | Error Detection | Best For |
|
||||||
|
|----------|-----------|-------|--------|-----------------|----------|
|
||||||
|
| XMODEM | 128 bytes | No | No | Checksum or CRC-16 | Legacy compatibility, simple devices |
|
||||||
|
| XMODEM-1K | 1024 bytes | No | No | CRC-16 | Faster XMODEM with wider block size |
|
||||||
|
| YMODEM | 1024 bytes | Yes | No | CRC-16 | Multiple files, preserves filenames |
|
||||||
|
| ZMODEM | Streaming | Yes | Yes | CRC-32 | Everything else (recommended) |
|
||||||
|
|
||||||
|
**ZMODEM** is the recommended protocol in nearly all cases. It streams data without waiting for per-block acknowledgment, supports batch transfers, can resume interrupted transfers, and uses 32-bit CRC for reliable error detection.
|
||||||
|
|
||||||
|
Use XMODEM only when the remote device does not support anything newer -- some bootloaders and legacy equipment only speak XMODEM.
|
||||||
|
|
||||||
|
## Sending a file
|
||||||
|
|
||||||
|
Use `file_transfer_send` to transmit a file to the remote device.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// file_transfer_send(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// file_path="/home/user/firmware.bin",
|
||||||
|
// protocol="zmodem"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"protocol": "zmodem",
|
||||||
|
"file": "/home/user/firmware.bin",
|
||||||
|
"bytes_sent": 65536,
|
||||||
|
"blocks_sent": 64
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note" title="Receiver must be ready first">
|
||||||
|
The remote device must be in receive mode before you call `file_transfer_send`. For ZMODEM, many receivers auto-start when they detect the initialization sequence, but XMODEM and YMODEM receivers must be explicitly started.
|
||||||
|
|
||||||
|
If you are working with a device that has a command-line interface, send the receive command first (e.g., `rz` for ZMODEM on Linux), then call `file_transfer_send`.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Receiving a file
|
||||||
|
|
||||||
|
Use `file_transfer_receive` to download a file from the remote device.
|
||||||
|
|
||||||
|
The meaning of `save_path` depends on the protocol:
|
||||||
|
|
||||||
|
- **XMODEM**: `save_path` is the full file path (e.g., `/tmp/received.bin`). XMODEM does not transmit filenames.
|
||||||
|
- **YMODEM / ZMODEM**: `save_path` is a directory. The filename comes from the sender's metadata.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Receiving with XMODEM (save_path is a file path)
|
||||||
|
// file_transfer_receive(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// save_path="/tmp/received.bin",
|
||||||
|
// protocol="xmodem"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"protocol": "xmodem",
|
||||||
|
"file": "/tmp/received.bin",
|
||||||
|
"bytes_received": 8192
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receiving with ZMODEM (save_path is a directory)
|
||||||
|
// file_transfer_receive(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// save_path="/tmp/downloads/",
|
||||||
|
// protocol="zmodem"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"protocol": "zmodem",
|
||||||
|
"files_received": 1,
|
||||||
|
"bytes_received": 65536
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `overwrite=true` if you want to replace existing files at the destination. By default, the transfer fails if a file already exists.
|
||||||
|
|
||||||
|
## Batch transfers
|
||||||
|
|
||||||
|
YMODEM and ZMODEM support sending multiple files in a single session. Use `file_transfer_send_batch`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// file_transfer_send_batch(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// file_paths=[
|
||||||
|
// "/home/user/firmware.bin",
|
||||||
|
// "/home/user/config.json",
|
||||||
|
// "/home/user/certs/device.pem"
|
||||||
|
// ],
|
||||||
|
// protocol="zmodem"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"protocol": "zmodem",
|
||||||
|
"files_sent": 3,
|
||||||
|
"total_bytes": 98304
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
XMODEM does not support batch transfers -- it has no concept of filenames or session boundaries. If you try to use `file_transfer_send_batch` with XMODEM, you will get an error.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
mcserial validates all file paths during transfers:
|
||||||
|
|
||||||
|
- **Directory traversal prevention**: Filenames received from the remote side are sanitized. Paths containing `..` or absolute path components are rejected.
|
||||||
|
- **Overwrite protection**: By default, receiving a file that already exists on disk returns an error. Pass `overwrite=true` to allow replacement.
|
||||||
|
- **Parent directory creation**: When receiving, mcserial creates parent directories as needed using `mkdir -p` behavior.
|
||||||
|
|
||||||
|
## Practical example: uploading firmware
|
||||||
|
|
||||||
|
This example walks through uploading a firmware binary to an embedded device over its serial debug port.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the serial port at the bootloader's baud rate**
|
||||||
|
|
||||||
|
Most bootloaders use 115200 baud. Check your device's documentation.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"baudrate": 115200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Reset the device into bootloader mode**
|
||||||
|
|
||||||
|
For an Arduino-style device, pulse DTR. For other devices, you may need to hold a specific button or send a command.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pulse_line(port="/dev/ttyUSB0", line="dtr", duration_ms=100, active_low=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Wait for the bootloader prompt**
|
||||||
|
|
||||||
|
Read lines until you see the bootloader's ready message.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// read_serial_lines(port="/dev/ttyUSB0", max_lines=10)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"lines": [
|
||||||
|
"Bootloader v2.1",
|
||||||
|
"Waiting for XMODEM transfer..."
|
||||||
|
],
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Send the firmware file**
|
||||||
|
|
||||||
|
Match the protocol to what the bootloader expects. Many embedded bootloaders support XMODEM; more capable ones may support YMODEM or ZMODEM.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// file_transfer_send(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// file_path="/home/user/build/firmware.bin",
|
||||||
|
// protocol="xmodem"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"protocol": "xmodem",
|
||||||
|
"file": "/home/user/build/firmware.bin",
|
||||||
|
"bytes_sent": 32768,
|
||||||
|
"blocks_sent": 256
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Verify the device booted with the new firmware**
|
||||||
|
|
||||||
|
After the transfer completes, the bootloader typically flashes the firmware and reboots. Read the startup output to confirm.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// read_serial_lines(port="/dev/ttyUSB0", max_lines=10)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"lines": [
|
||||||
|
"Firmware v3.0.1 loaded",
|
||||||
|
"CRC OK",
|
||||||
|
"Booting..."
|
||||||
|
],
|
||||||
|
"count": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Protocol details
|
||||||
|
|
||||||
|
### XMODEM
|
||||||
|
|
||||||
|
The oldest and simplest protocol (1977). Data is sent in 128-byte blocks with a 1-byte checksum or 16-bit CRC. The receiver must acknowledge each block before the next is sent, which makes it slow on high-latency connections.
|
||||||
|
|
||||||
|
**XMODEM-1K** is identical except it uses 1024-byte blocks, reducing overhead.
|
||||||
|
|
||||||
|
### YMODEM
|
||||||
|
|
||||||
|
An extension of XMODEM that adds batch capability. The first block of each file contains the filename and size as metadata. After all files are sent, a null filename block signals the end of the batch.
|
||||||
|
|
||||||
|
### ZMODEM
|
||||||
|
|
||||||
|
A streaming protocol that does not wait for per-block acknowledgments. It sends data continuously and only pauses if the receiver reports an error. Features include:
|
||||||
|
|
||||||
|
- **Auto-start**: receivers detect the ZMODEM init sequence and begin automatically
|
||||||
|
- **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
|
||||||
213
docs/src/content/docs/guides/network-ports.mdx
Normal file
213
docs/src/content/docs/guides/network-ports.mdx
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
---
|
||||||
|
title: Network Serial Ports
|
||||||
|
description: Connecting to serial devices over TCP, RFC 2217, and other network protocols
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Serial devices are not always physically attached to the machine running mcserial. Serial device servers, terminal servers, and networked test equipment expose serial ports over TCP. mcserial supports these through URL schemes passed to `open_serial_port` -- the same read, write, configure, and file transfer tools work regardless of how the port is connected.
|
||||||
|
|
||||||
|
## Why network serial
|
||||||
|
|
||||||
|
- **Serial device servers** (Digi, Moxa, Lantronix) bridge RS-232/RS-485 ports to your LAN
|
||||||
|
- **Remote equipment** in server rooms, factory floors, or field sites
|
||||||
|
- **Terminal servers** providing shared console access to routers, switches, and PDUs
|
||||||
|
- **Virtual serial ports** for testing and development without physical hardware
|
||||||
|
|
||||||
|
## URL schemes
|
||||||
|
|
||||||
|
All URL schemes are passed directly to `open_serial_port` in the `port` parameter. Once opened, the connection is used identically to a local serial port.
|
||||||
|
|
||||||
|
### Raw TCP socket -- `socket://`
|
||||||
|
|
||||||
|
The simplest network serial option. Bytes are forwarded as-is between the TCP socket and your tools, with no protocol framing, baud rate control, or flow control signals.
|
||||||
|
|
||||||
|
### RFC 2217 -- `rfc2217://`
|
||||||
|
|
||||||
|
Full serial port emulation over a Telnet connection (RFC 2217 / Telnet COM Port Control). The remote device server translates baud rate changes, flow control, and modem line signals into commands that control the physical serial port.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Raw TCP">
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Connect to a Digi PortServer on port 4001
|
||||||
|
// open_serial_port(
|
||||||
|
// port="socket://192.168.1.100:4001",
|
||||||
|
// baudrate=115200
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "socket://192.168.1.100:4001",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"url_scheme": "socket",
|
||||||
|
"hint": "Opened via URL handler. Some features (exclusive, auto-baud) are not available."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- No baud rate negotiation -- the device server must be pre-configured to match
|
||||||
|
- No flow control or modem line signals
|
||||||
|
- Lowest overhead; good for simple byte-stream forwarding
|
||||||
|
- Works with any serial-to-ethernet bridge
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="RFC 2217">
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Connect to a device server with full serial emulation
|
||||||
|
// open_serial_port(
|
||||||
|
// port="rfc2217://192.168.1.100:2217",
|
||||||
|
// baudrate=9600
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "rfc2217://192.168.1.100:2217",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"url_scheme": "rfc2217",
|
||||||
|
"hint": "Opened via URL handler. Some features (exclusive, auto-baud) are not available."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Baud rate, data bits, parity, and stop bits can be changed remotely via `configure_serial`
|
||||||
|
- Flow control (RTS/CTS, XON/XOFF) is supported and forwarded to the physical port
|
||||||
|
- Modem line states (DTR, RTS, CTS, DSR) can be read and set
|
||||||
|
- Higher overhead than raw TCP due to Telnet negotiation
|
||||||
|
- Requires a device server that implements RFC 2217
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
After opening, all tools work the same way:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// write_serial(port="socket://192.168.1.100:4001", data="AT\r\n")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_written": 4,
|
||||||
|
"port": "socket://192.168.1.100:4001"
|
||||||
|
}
|
||||||
|
|
||||||
|
// read_serial_lines(port="socket://192.168.1.100:4001", max_lines=5)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"lines": ["OK"],
|
||||||
|
"count": 1,
|
||||||
|
"port": "socket://192.168.1.100:4001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debug wrapper -- `spy://`
|
||||||
|
|
||||||
|
<Aside type="tip" title="Debugging misbehaving devices">
|
||||||
|
Wrap any connection with `spy://` to log all transmitted and received bytes to stderr. This is invaluable when a device is not responding as expected and you need to see the raw traffic. The spy wrapper adds zero latency to the connection -- it simply logs a copy of every byte that passes through.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
The `spy://` scheme wraps another serial connection and logs all traffic. It works with local ports and other URL schemes.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Debug a local serial port
|
||||||
|
// open_serial_port(port="spy:///dev/ttyUSB0", baudrate=9600)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "spy:///dev/ttyUSB0",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"url_scheme": "spy"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug a network connection
|
||||||
|
// open_serial_port(port="spy://socket://192.168.1.100:4001", baudrate=115200)
|
||||||
|
```
|
||||||
|
|
||||||
|
All reads and writes are logged to stderr in the mcserial server process. Check the server's stderr output to see the traffic.
|
||||||
|
|
||||||
|
## HID-to-UART -- `cp2110://`
|
||||||
|
|
||||||
|
Silicon Labs CP2110 chips present as USB HID devices rather than standard serial ports. They do not appear as `/dev/ttyUSB*` devices, so normal serial access does not work.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="cp2110://", baudrate=9600)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "cp2110://",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"url_scheme": "cp2110"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This requires the `hidapi` package. Install it with the optional extra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install mcserial[cp2110]
|
||||||
|
```
|
||||||
|
|
||||||
|
If `hidapi` is not installed and you try to open a `cp2110://` port, mcserial returns a clear error:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "cp2110:// requires the hidapi package. Install with: pip install mcserial[cp2110]",
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hardware grep -- `hwgrep://`
|
||||||
|
|
||||||
|
`hwgrep://` opens the first serial port whose hardware information matches a pattern. This is useful when device paths change between reboots or when multiple adapters are connected.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Find and open an FTDI device by USB VID:PID
|
||||||
|
// open_serial_port(port="hwgrep://VID:PID=0403:6001", baudrate=115200)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "hwgrep://VID:PID=0403:6001",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"url_scheme": "hwgrep"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find by description string
|
||||||
|
// open_serial_port(port="hwgrep://FTDI", baudrate=9600)
|
||||||
|
|
||||||
|
// Find by manufacturer
|
||||||
|
// open_serial_port(port="hwgrep://Silicon Labs", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
The pattern is matched against the port's device path, description, hardware ID, manufacturer, product, and serial number. The first matching port is opened.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
`hwgrep://` uses pyserial's built-in hardware grep URL handler, which is separate from the `grep` parameter on `list_serial_ports`. Both search the same hardware fields, but `hwgrep://` opens the first match directly while `list_serial_ports(grep=...)` returns all matches for you to choose from.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Loopback -- `loop://`
|
||||||
|
|
||||||
|
The `loop://` scheme creates a virtual loopback port where everything written is immediately available to read. No hardware is needed. This is useful for testing and development.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
// write_serial(port="loop://", data="hello")
|
||||||
|
// read_serial(port="loop://")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "hello",
|
||||||
|
"bytes_read": 5,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations of URL-opened ports
|
||||||
|
|
||||||
|
URL-opened ports differ from local device ports in a few ways:
|
||||||
|
|
||||||
|
| Feature | Local port | URL port |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| Auto-baud detection | Yes | No (not applicable) |
|
||||||
|
| Exclusive access lock | Yes | No |
|
||||||
|
| Low-latency mode | Yes (Linux) | No |
|
||||||
|
| `set_rs485_mode` hardware ioctl | Yes | No |
|
||||||
|
| Read/write/configure | Yes | Yes |
|
||||||
|
| File transfers | Yes | Yes |
|
||||||
|
|
||||||
|
Baud rate auto-detection is skipped for URL ports because it requires reopening the port at different speeds, which does not apply to TCP or virtual connections. For `socket://`, the baud rate parameter is informational only -- the device server must be configured separately.
|
||||||
|
|
||||||
|
For `rfc2217://`, baud rate and serial parameters are sent to the remote device server, which applies them to the physical port.
|
||||||
257
docs/src/content/docs/guides/rs232-basics.mdx
Normal file
257
docs/src/content/docs/guides/rs232-basics.mdx
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
---
|
||||||
|
title: RS-232 Communication
|
||||||
|
description: Working with modem control lines, break signals, and device resets
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
RS-232 is the default mode when you open a port with mcserial. It provides point-to-point serial communication with modem control lines -- the signals that let you detect device presence, manage hardware flow control, and trigger resets.
|
||||||
|
|
||||||
|
Every port opens in RS-232 mode automatically. No additional configuration is needed unless you want to switch to RS-485.
|
||||||
|
|
||||||
|
## Modem control lines
|
||||||
|
|
||||||
|
RS-232 defines six modem control lines. Four are **inputs** (read from the remote device) and two are **outputs** (set by you).
|
||||||
|
|
||||||
|
### Input lines
|
||||||
|
|
||||||
|
These reflect the state of the remote device. Read them with `get_modem_lines`:
|
||||||
|
|
||||||
|
| Line | Full Name | What it means |
|
||||||
|
|------|-----------|---------------|
|
||||||
|
| CTS | Clear To Send | Remote device is ready to receive data |
|
||||||
|
| DSR | Data Set Ready | Remote device is powered and present |
|
||||||
|
| RI | Ring Indicator | Incoming call (modems) or attention signal |
|
||||||
|
| CD | Carrier Detect | Active connection established |
|
||||||
|
|
||||||
|
```json
|
||||||
|
// get_modem_lines(port="/dev/ttyUSB0")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"input_lines": {
|
||||||
|
"cts": true,
|
||||||
|
"dsr": true,
|
||||||
|
"ri": false,
|
||||||
|
"cd": false
|
||||||
|
},
|
||||||
|
"output_lines": {
|
||||||
|
"rts": true,
|
||||||
|
"dtr": true
|
||||||
|
},
|
||||||
|
"break_condition": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output lines
|
||||||
|
|
||||||
|
These are signals you send to the remote device. Set them with `set_modem_lines`:
|
||||||
|
|
||||||
|
| Line | Full Name | Common uses |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| RTS | Request To Send | Hardware flow control, TX enable for RS-485 converters |
|
||||||
|
| DTR | Data Terminal Ready | Signal our presence, trigger device reset |
|
||||||
|
|
||||||
|
```json
|
||||||
|
// set_modem_lines(port="/dev/ttyUSB0", rts=true, dtr=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"rts": true,
|
||||||
|
"dtr": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set one or both lines in a single call. Pass `null` (omit the parameter) to leave a line unchanged.
|
||||||
|
|
||||||
|
## Device reset sequences
|
||||||
|
|
||||||
|
Many development boards use DTR or RTS to trigger a hardware reset. The `pulse_line` tool handles the timing for you.
|
||||||
|
|
||||||
|
### Arduino reset
|
||||||
|
|
||||||
|
Arduino boards use DTR to reset the microcontroller. When DTR goes low, the reset capacitor pulls the RESET pin low briefly, rebooting the chip.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pulse_line(port="/dev/ttyUSB0", line="dtr", duration_ms=100, active_low=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"line": "dtr",
|
||||||
|
"duration_ms": 100,
|
||||||
|
"active_low": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `active_low=true` parameter (the default) means the line pulses LOW then returns HIGH -- exactly what Arduino expects.
|
||||||
|
|
||||||
|
<Aside type="tip" title="ESP32 bootloader entry">
|
||||||
|
ESP32 and ESP8266 boards use a more complex sequence involving both DTR and RTS to enter the bootloader. The typical sequence is:
|
||||||
|
|
||||||
|
1. Set DTR low, RTS high (hold EN high, pull GPIO0 low)
|
||||||
|
2. Set DTR high, RTS low (release GPIO0, pull EN low to reset)
|
||||||
|
3. Release both lines
|
||||||
|
|
||||||
|
You can achieve this with two `set_modem_lines` calls with a short delay between them, or use the `pulse_line` tool for each line in sequence. The exact timing depends on the board's RC circuit.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### Practical example: connect to Arduino, reset, read boot output
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the serial port**
|
||||||
|
|
||||||
|
Open the port at 115200 baud (common for Arduino). If you are unsure of the baud rate, omit it and mcserial will attempt auto-detection.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyACM0", baudrate=115200)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyACM0",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"resource_uri": "serial:///dev/ttyACM0/data"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Flush any stale data in the buffer**
|
||||||
|
|
||||||
|
Clear out anything sitting in the receive buffer from before the reset.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// flush_serial(port="/dev/ttyACM0")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyACM0",
|
||||||
|
"flushed_input": true,
|
||||||
|
"flushed_output": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Pulse DTR to reset the Arduino**
|
||||||
|
|
||||||
|
A 100ms DTR pulse is enough for any Arduino board.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pulse_line(port="/dev/ttyACM0", line="dtr", duration_ms=100, active_low=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyACM0",
|
||||||
|
"line": "dtr",
|
||||||
|
"duration_ms": 100,
|
||||||
|
"active_low": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Read the bootloader and startup output**
|
||||||
|
|
||||||
|
After reset, the Arduino bootloader prints a brief message, then your sketch starts. Read multiple lines to capture everything.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// read_serial_lines(port="/dev/ttyACM0", max_lines=20)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"lines": [
|
||||||
|
"",
|
||||||
|
"Sketch starting...",
|
||||||
|
"Initializing sensors...",
|
||||||
|
"Ready."
|
||||||
|
],
|
||||||
|
"count": 4,
|
||||||
|
"bytes_read": 52,
|
||||||
|
"port": "/dev/ttyACM0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Interact with the running sketch**
|
||||||
|
|
||||||
|
Send commands and read responses as needed.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// write_serial(port="/dev/ttyACM0", data="STATUS\r\n")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_written": 8,
|
||||||
|
"port": "/dev/ttyACM0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Break signals
|
||||||
|
|
||||||
|
A break signal is a sustained LOW state on the TX line that lasts longer than a normal character frame. Some devices use break signals to enter configuration mode, trigger a reset, or synchronize communication.
|
||||||
|
|
||||||
|
mcserial provides two ways to send breaks:
|
||||||
|
|
||||||
|
### send_break -- timed pulse
|
||||||
|
|
||||||
|
`send_break` holds the line LOW for a fixed duration, then releases it. This is the most common usage.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// send_break(port="/dev/ttyUSB0", duration_ms=250)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"duration_ms": 250
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The default duration is 250ms, which is long enough for most devices. The valid range is 1ms to 5000ms.
|
||||||
|
|
||||||
|
### set_break_condition -- sustained hold
|
||||||
|
|
||||||
|
`set_break_condition` holds the TX line LOW indefinitely until you explicitly release it. Use this when a protocol requires a sustained break state.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Assert break (hold TX low)
|
||||||
|
// set_break_condition(port="/dev/ttyUSB0", enabled=true)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"break_condition": true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release break (return to normal)
|
||||||
|
// set_break_condition(port="/dev/ttyUSB0", enabled=false)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"break_condition": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The current break state is also reported in the `get_modem_lines` response under `break_condition`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Both `send_break` and `set_break_condition` require RS-232 mode (the default). If you have switched to RS-485 mode, switch back with `set_port_mode(port, "rs232")` first.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Checking line states
|
||||||
|
|
||||||
|
You can verify the state of all modem lines at any time with `get_modem_lines`. This is useful for:
|
||||||
|
|
||||||
|
- **Detecting device presence** -- check DSR before attempting communication
|
||||||
|
- **Debugging connections** -- verify CTS is asserted if hardware flow control is in use
|
||||||
|
- **Monitoring** -- poll RI or CD for incoming events on modem-style devices
|
||||||
|
|
||||||
|
```json
|
||||||
|
// get_modem_lines(port="/dev/ttyUSB0")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"input_lines": {
|
||||||
|
"cts": true,
|
||||||
|
"dsr": false,
|
||||||
|
"ri": false,
|
||||||
|
"cd": false
|
||||||
|
},
|
||||||
|
"output_lines": {
|
||||||
|
"rts": true,
|
||||||
|
"dtr": true
|
||||||
|
},
|
||||||
|
"break_condition": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, CTS is asserted (the device can receive data) but DSR is low (the device may not be powered or connected). This is a common state for USB-serial adapters that loop CTS back internally but do not connect DSR.
|
||||||
277
docs/src/content/docs/guides/rs485-modbus.mdx
Normal file
277
docs/src/content/docs/guides/rs485-modbus.mdx
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
title: RS-485 and Modbus
|
||||||
|
description: Multi-drop bus communication, address scanning, and request-response transactions
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
RS-485 uses a shared differential bus where multiple devices communicate over two wires. Unlike RS-232's point-to-point model, RS-485 is half-duplex -- only one device transmits at a time while all others listen. This makes it the standard for industrial protocols like Modbus RTU.
|
||||||
|
|
||||||
|
## Switching to RS-485 mode
|
||||||
|
|
||||||
|
Ports open in RS-232 mode by default. Switch to RS-485 mode before using any RS-485 tools.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600)
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 9600
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_port_mode(port="/dev/ttyUSB0", mode="rs485")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"previous_mode": "rs232",
|
||||||
|
"current_mode": "rs485",
|
||||||
|
"mode_tools": [
|
||||||
|
"set_rs485_mode",
|
||||||
|
"rs485_transact",
|
||||||
|
"rs485_scan_addresses",
|
||||||
|
"check_rs485_support"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Once in RS-485 mode, the RS-232-specific tools (`get_modem_lines`, `pulse_line`, etc.) are unavailable. Common tools like `read_serial`, `write_serial`, and `configure_serial` work in both modes.
|
||||||
|
|
||||||
|
## Configuring RS-485 hardware
|
||||||
|
|
||||||
|
If your USB-serial adapter or UART has hardware RS-485 support, `set_rs485_mode` configures the driver to automatically toggle the TX enable (DE/RE) line during transmission. This eliminates the need for manual RTS toggling.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// set_rs485_mode(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// enabled=true,
|
||||||
|
// rts_level_for_tx=true,
|
||||||
|
// rts_level_for_rx=false,
|
||||||
|
// delay_before_tx=0.0,
|
||||||
|
// delay_before_rx=0.0
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"rs485_enabled": true,
|
||||||
|
"rts_level_for_tx": true,
|
||||||
|
"rts_level_for_rx": false,
|
||||||
|
"delay_before_tx": 0.0,
|
||||||
|
"delay_before_rx": 0.0,
|
||||||
|
"loopback": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters explained:**
|
||||||
|
|
||||||
|
| Parameter | Default | Purpose |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| `rts_level_for_tx` | `true` | RTS state when transmitting (HIGH enables the driver on most RS-485 transceivers) |
|
||||||
|
| `rts_level_for_rx` | `false` | RTS state when receiving (LOW disables the driver, allows listening) |
|
||||||
|
| `delay_before_tx` | `0.0` | Seconds to wait after asserting TX enable before sending data |
|
||||||
|
| `delay_before_rx` | `0.0` | Seconds to wait after transmission before switching to receive |
|
||||||
|
| `loopback` | `false` | Echo transmitted data back (for testing) |
|
||||||
|
|
||||||
|
## Checking hardware support
|
||||||
|
|
||||||
|
Not all USB-serial adapters support hardware RS-485. Use `check_rs485_support` to find out what your hardware can do before configuring.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// check_rs485_support(port="/dev/ttyUSB0")
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"driver": "ftdi_sio",
|
||||||
|
"chip": "FT232R",
|
||||||
|
"hardware_rs485": true,
|
||||||
|
"software_fallback": true,
|
||||||
|
"kernel_rs485_ioctl": true,
|
||||||
|
"notes": [
|
||||||
|
"FTDI chips have hardware RS-485 auto-direction",
|
||||||
|
"Kernel TIOCSRS485 ioctl supported"
|
||||||
|
],
|
||||||
|
"recommendation": "Use set_rs485_mode() for automatic DE/RE control"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This tool queries the kernel driver and USB device information to determine:
|
||||||
|
|
||||||
|
- Which driver is loaded (ftdi_sio, cp210x, ch341, pl2303)
|
||||||
|
- Whether the chip supports hardware DE/RE toggling
|
||||||
|
- Whether the kernel TIOCSRS485 ioctl is available
|
||||||
|
- A recommendation for how to proceed
|
||||||
|
|
||||||
|
### Hardware support by adapter
|
||||||
|
|
||||||
|
| Chip | Hardware RS-485 | Notes |
|
||||||
|
|------|----------------|-------|
|
||||||
|
| FTDI (FT232R, FT2232, etc.) | Yes | Automatic direction control via driver |
|
||||||
|
| CP2105 / CP2108 | Yes | Hardware support in these models |
|
||||||
|
| CP2102 | No | Software RTS control required |
|
||||||
|
| CH340 / CH341 | No | Timing may be unreliable at high baud rates |
|
||||||
|
| PL2303 | No | Software RTS control required |
|
||||||
|
| Native UART (ttyS, ttyAMA) | Depends | Check if RS-485 transceiver is connected |
|
||||||
|
|
||||||
|
## Half-duplex transactions
|
||||||
|
|
||||||
|
The `rs485_transact` tool handles the complete send-then-receive cycle that RS-485 communication requires. It manages TX/RX turnaround timing automatically, whether your hardware supports it natively or needs software RTS toggling.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// rs485_transact(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// data="\x01\x03\x00\x00\x00\x01\x84\x0A",
|
||||||
|
// response_timeout=1.0,
|
||||||
|
// response_length=7
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"bytes_sent": 8,
|
||||||
|
"data_sent": "\u0001\u0003\u0000\u0000\u0000\u0001\u0084\n",
|
||||||
|
"response": "\u0001\u0003\u0002\u0000\u0005\u0085\u0085",
|
||||||
|
"response_bytes": 7,
|
||||||
|
"response_hex": "01030200058585",
|
||||||
|
"hardware_rs485": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When hardware RS-485 is **not** configured, `rs485_transact` falls back to manual RTS control:
|
||||||
|
|
||||||
|
1. Asserts RTS HIGH (enable driver / TX mode)
|
||||||
|
2. Sends the data and waits for transmission to complete
|
||||||
|
3. Drops RTS LOW (disable driver / RX mode)
|
||||||
|
4. Waits for the turnaround delay
|
||||||
|
5. Reads the response
|
||||||
|
|
||||||
|
The `turnaround_delay` parameter (default 5ms) controls the pause between sending and listening. Increase it for slow devices or long cable runs.
|
||||||
|
|
||||||
|
## Scanning for devices
|
||||||
|
|
||||||
|
`rs485_scan_addresses` probes a range of addresses and reports which ones respond. This is the fastest way to discover what is connected to a bus.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// rs485_scan_addresses(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// start_address=1,
|
||||||
|
// end_address=30,
|
||||||
|
// probe_template="{addr:02x}03000001",
|
||||||
|
// response_timeout=0.1
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"addresses_scanned": 30,
|
||||||
|
"devices_found": 3,
|
||||||
|
"responding_addresses": [
|
||||||
|
{"address": 1, "response_length": 7, "response_hex": "01030200058585"},
|
||||||
|
{"address": 5, "response_length": 7, "response_hex": "05030200124478"},
|
||||||
|
{"address": 16, "response_length": 7, "response_hex": "10030200009930"}
|
||||||
|
],
|
||||||
|
"hardware_rs485": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `probe_template` uses Python's string formatting. The placeholder `{addr}` is replaced with the current address being scanned. The default template `{addr:02x}03000001` formats the address as a two-character hex string followed by a Modbus "read holding register" frame.
|
||||||
|
|
||||||
|
<Aside type="caution" title="Bus termination and biasing">
|
||||||
|
RS-485 buses longer than a few meters need proper termination resistors (120 ohm) at both ends of the cable. Without termination, reflections cause bit errors, especially at higher baud rates.
|
||||||
|
|
||||||
|
The bus also requires biasing resistors to hold the differential lines in a known state when no device is transmitting. Without biasing, the idle bus floats and receivers may interpret noise as valid data. Many RS-485 adapters include built-in biasing -- check your hardware documentation.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Practical Modbus RTU workflow
|
||||||
|
|
||||||
|
This example walks through a complete Modbus RTU session: discovering devices, then reading a holding register.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the port and switch to RS-485 mode**
|
||||||
|
|
||||||
|
Modbus RTU typically runs at 9600 baud with 8N1 framing.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// open_serial_port(port="/dev/ttyUSB0", baudrate=9600)
|
||||||
|
// set_port_mode(port="/dev/ttyUSB0", mode="rs485")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure hardware RS-485 (if supported)**
|
||||||
|
|
||||||
|
If your adapter supports hardware direction control, enable it. If not, `rs485_transact` handles RTS toggling in software.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// set_rs485_mode(port="/dev/ttyUSB0", enabled=true, rts_level_for_tx=true, rts_level_for_rx=false)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Scan the bus for responding devices**
|
||||||
|
|
||||||
|
Scan addresses 1 through 247 (the Modbus valid range). Use a short timeout per address to keep the scan fast.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// rs485_scan_addresses(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// start_address=1,
|
||||||
|
// end_address=247,
|
||||||
|
// response_timeout=0.1
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"addresses_scanned": 247,
|
||||||
|
"devices_found": 2,
|
||||||
|
"responding_addresses": [
|
||||||
|
{"address": 1, "response_length": 7, "response_hex": "01030200058585"},
|
||||||
|
{"address": 10, "response_length": 7, "response_hex": "0a030200003071"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Read a holding register from a device**
|
||||||
|
|
||||||
|
Construct a Modbus RTU "Read Holding Registers" frame for address 1, register 0, quantity 1. The frame format is: `[address][function 0x03][start_hi][start_lo][qty_hi][qty_lo][CRC_lo][CRC_hi]`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// rs485_transact(
|
||||||
|
// port="/dev/ttyUSB0",
|
||||||
|
// data="\x01\x03\x00\x00\x00\x01\x84\x0A",
|
||||||
|
// response_length=7,
|
||||||
|
// encoding="latin-1"
|
||||||
|
// )
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_sent": 8,
|
||||||
|
"response_hex": "01030200058585",
|
||||||
|
"response_bytes": 7,
|
||||||
|
"hardware_rs485": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The response `01 03 02 00 05 85 85` decodes as: address 1, function 3, 2 bytes of data, value 0x0005 (decimal 5), followed by a CRC.
|
||||||
|
|
||||||
|
5. **Close the port when finished**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// close_serial_port(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
### Understanding probe_template format
|
||||||
|
|
||||||
|
The `probe_template` parameter in `rs485_scan_addresses` is a Python format string. It supports the `{addr}` placeholder with standard format specifiers:
|
||||||
|
|
||||||
|
| Template | Address 16 becomes | Use case |
|
||||||
|
|----------|-------------------|----------|
|
||||||
|
| `{addr:02x}03000001` | `1003000001` | Modbus-style hex address prefix |
|
||||||
|
| `{addr:d}` | `16` | Decimal address prefix |
|
||||||
|
| `{addr:c}` | (ASCII char 16) | Single-byte binary address |
|
||||||
|
|
||||||
|
If the template does **not** contain `{addr`, the raw address byte is prepended automatically. This means a template of `03000001` with address 16 sends byte 0x10 followed by `03000001`.
|
||||||
|
|
||||||
|
## Software vs hardware RS-485
|
||||||
|
|
||||||
|
When `check_rs485_support` reports that your adapter lacks hardware RS-485, mcserial uses **software emulation**:
|
||||||
|
|
||||||
|
- Before transmitting, RTS is asserted HIGH to enable the transceiver's driver
|
||||||
|
- After transmission completes, RTS is dropped LOW to switch back to receive mode
|
||||||
|
- A configurable turnaround delay prevents missed response bytes
|
||||||
|
|
||||||
|
Software emulation works reliably at baud rates up to about 115200. At higher speeds, the timing of the RTS toggle may not be precise enough, causing the first byte of a response to be corrupted or missed. If you need high-speed RS-485, use an adapter with hardware support (FTDI-based adapters are widely available and well-supported).
|
||||||
80
docs/src/content/docs/index.mdx
Normal file
80
docs/src/content/docs/index.mdx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: mcserial
|
||||||
|
description: MCP server for serial port communication -- RS-232, RS-485, and file transfers via Model Context Protocol.
|
||||||
|
template: splash
|
||||||
|
hero:
|
||||||
|
tagline: Serial ports meet the Model Context Protocol. Discover, configure, read, write, and transfer files -- RS-232, RS-485, and 7 URL schemes, all callable as MCP tools.
|
||||||
|
image:
|
||||||
|
file: ../../assets/logo.svg
|
||||||
|
actions:
|
||||||
|
- text: Get Started
|
||||||
|
link: /tutorials/getting-started/
|
||||||
|
icon: right-arrow
|
||||||
|
variant: primary
|
||||||
|
- text: Tool Reference
|
||||||
|
link: /reference/tools-common/
|
||||||
|
variant: minimal
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Card, CardGrid, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## What mcserial does
|
||||||
|
|
||||||
|
<CardGrid stagger>
|
||||||
|
<Card title="RS-232 and RS-485" icon="setting">
|
||||||
|
Full RS-232 modem line control -- RTS, DTR, CTS, DSR, break signals, device reset pulses. Switch to RS-485 mode for half-duplex bus communication with automatic TX/RX direction, device scanning, and Modbus transactions.
|
||||||
|
</Card>
|
||||||
|
<Card title="File Transfers" icon="document">
|
||||||
|
Send and receive files over serial using XMODEM (128B blocks), XMODEM-1K, YMODEM (batch mode), or ZMODEM (streaming with auto-resume). Batch transfers supported for YMODEM and ZMODEM.
|
||||||
|
</Card>
|
||||||
|
<Card title="Network and Virtual Ports" icon="external">
|
||||||
|
Connect to serial-over-ethernet bridges via `socket://`, configure remote ports with `rfc2217://`, test without hardware on `loop://`, or debug traffic with `spy://`. Seven URL schemes total.
|
||||||
|
</Card>
|
||||||
|
<Card title="Baud Rate Detection" icon="random">
|
||||||
|
Omit the baud rate when opening a port and mcserial will detect it automatically by analyzing data patterns, sync bytes, and byte distributions. Returns a confidence score and ranked candidates.
|
||||||
|
</Card>
|
||||||
|
</CardGrid>
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to Claude Code
|
||||||
|
claude mcp add mcserial -- uvx mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the tools in a natural sequence -- discover, open, read/write, close:
|
||||||
|
|
||||||
|
```
|
||||||
|
list_serial_ports() # find available ports
|
||||||
|
open_serial_port(port="loop://", baudrate=9600) # open (loop:// = no hardware needed)
|
||||||
|
write_serial(port="loop://", data="hello\n") # send data
|
||||||
|
read_serial(port="loop://") # read response
|
||||||
|
close_serial_port(port="loop://") # release the port
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Getting Started tutorial](/tutorials/getting-started/) for the full walkthrough with example responses.
|
||||||
|
|
||||||
|
## Supported URL schemes
|
||||||
|
|
||||||
|
In addition to local device paths (`/dev/ttyUSB0`, `COM3`), `open_serial_port` accepts these URL schemes:
|
||||||
|
|
||||||
|
| Scheme | Description | Example |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `loop://` | In-memory loopback -- writes echo back as reads | `loop://` |
|
||||||
|
| `socket://` | Raw TCP socket for serial-to-ethernet bridges | `socket://192.168.1.100:4001` |
|
||||||
|
| `rfc2217://` | Telnet COM Port Control with remote baud/flow config | `rfc2217://192.168.1.100:2217` |
|
||||||
|
| `spy://` | Debug wrapper that logs all traffic to stderr | `spy:///dev/ttyUSB0` |
|
||||||
|
| `hwgrep://` | Open first port matching a hardware pattern | `hwgrep://FTDI` |
|
||||||
|
| `cp2110://` | Silicon Labs HID-to-UART (requires `[cp2110]` extra) | `cp2110://` |
|
||||||
|
| `alt://` | Alternate port backend | `alt:///dev/ttyUSB0` |
|
||||||
|
|
||||||
|
## 30 tools across 4 categories
|
||||||
|
|
||||||
|
mcserial is a [FastMCP](https://gofastmcp.com) server wrapping [pyserial](https://pyserial.readthedocs.io/) into organized tool groups:
|
||||||
|
|
||||||
|
- **[Common tools](/reference/tools-common/)** (18) -- port discovery, open/close, read/write, configuration, baud detection
|
||||||
|
- **[RS-232 tools](/reference/tools-rs232/)** (5) -- modem line control, break signals, reset pulses
|
||||||
|
- **[RS-485 tools](/reference/tools-rs485/)** (4) -- bus configuration, transactions, address scanning
|
||||||
|
- **[File transfer tools](/reference/tools-file-transfer/)** (3) -- X/Y/ZMODEM send, receive, batch send
|
||||||
|
|
||||||
|
Ports open in RS-232 mode by default. Use `set_port_mode()` to switch to RS-485 when working with multi-drop buses. Common tools work in both modes.
|
||||||
110
docs/src/content/docs/reference/environment.mdx
Normal file
110
docs/src/content/docs/reference/environment.mdx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
title: Environment Variables
|
||||||
|
description: Configuration via environment variables
|
||||||
|
sidebar:
|
||||||
|
order: 7
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
mcserial reads configuration from environment variables at startup. These variables set server-wide defaults that apply to all connections unless overridden by tool parameters.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Server Configuration
|
||||||
|
|
||||||
|
### MCSERIAL_DEFAULT_BAUDRATE
|
||||||
|
|
||||||
|
Default baud rate used when `open_serial_port` is called without a `baudrate` parameter and auto-detection either fails or is not applicable (e.g., URL-based connections).
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Default** | `9600` |
|
||||||
|
| **Type** | Integer |
|
||||||
|
| **Example** | `MCSERIAL_DEFAULT_BAUDRATE=115200` |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set a higher default for modern devices
|
||||||
|
export MCSERIAL_DEFAULT_BAUDRATE=115200
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCSERIAL_DEFAULT_TIMEOUT
|
||||||
|
|
||||||
|
Default read timeout in seconds for new connections. Applied as the `timeout` parameter when `open_serial_port` is called without an explicit timeout.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Default** | `1.0` |
|
||||||
|
| **Type** | Float (seconds) |
|
||||||
|
| **Example** | `MCSERIAL_DEFAULT_TIMEOUT=2.0` |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Longer timeout for slow devices
|
||||||
|
export MCSERIAL_DEFAULT_TIMEOUT=5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCSERIAL_MAX_CONNECTIONS
|
||||||
|
|
||||||
|
Maximum number of simultaneously open serial port connections. Attempts to open more ports than this limit return an error.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Default** | `10` |
|
||||||
|
| **Type** | Integer |
|
||||||
|
| **Example** | `MCSERIAL_MAX_CONNECTIONS=20` |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Allow more concurrent connections
|
||||||
|
export MCSERIAL_MAX_CONNECTIONS=20
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Each open connection holds a file descriptor and kernel buffer memory. The default of 10 is sufficient for most use cases. Increase it only if you genuinely need many simultaneous connections.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setting Environment Variables
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
When using mcserial with Claude Code, set environment variables before adding the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Inline with the command
|
||||||
|
claude mcp add mcserial "env MCSERIAL_DEFAULT_BAUDRATE=115200 uvx mcserial"
|
||||||
|
|
||||||
|
# Option 2: Export in your shell profile
|
||||||
|
export MCSERIAL_DEFAULT_BAUDRATE=115200
|
||||||
|
claude mcp add mcserial "uvx mcserial"
|
||||||
|
```
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
For other MCP clients, consult their documentation for how to pass environment variables to server processes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation Extras
|
||||||
|
|
||||||
|
mcserial supports optional dependency groups for specialized hardware.
|
||||||
|
|
||||||
|
### mcserial[cp2110]
|
||||||
|
|
||||||
|
Adds the `hidapi` package for Silicon Labs CP2110 HID-to-UART bridge support. Required to use the `cp2110://` URL scheme with `open_serial_port`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install with CP2110 support
|
||||||
|
uv pip install mcserial[cp2110]
|
||||||
|
|
||||||
|
# Or with uvx
|
||||||
|
uvx --with hidapi mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
Without this extra installed, attempting to open a `cp2110://` URL returns an error:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp2110:// requires the hidapi package. Install with: pip install mcserial[cp2110]
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
The `hidapi` package has system-level dependencies on some platforms. On Linux, you may need `libhidapi-dev` or equivalent. On macOS, it installs cleanly via pip. On Windows, no additional system packages are needed.
|
||||||
|
</Aside>
|
||||||
143
docs/src/content/docs/reference/resources.mdx
Normal file
143
docs/src/content/docs/reference/resources.mdx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
title: MCP Resources
|
||||||
|
description: Read-only data endpoints exposed as MCP resources
|
||||||
|
sidebar:
|
||||||
|
order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
MCP resources are read-only data endpoints that clients can access without calling tools. They provide a way to passively observe port state and data, separate from the action-oriented tool interface.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Resources are read-only. They do not modify port state, open connections, or send data. To interact with ports, use the [Common Tools](/reference/tools-common/).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## serial://ports
|
||||||
|
|
||||||
|
List all available serial ports on the system. Returns a Markdown-formatted list with device paths, descriptions, and open/closed status.
|
||||||
|
|
||||||
|
**URI:** `serial://ports`
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```markdown
|
||||||
|
# Available Serial Ports
|
||||||
|
|
||||||
|
- /dev/ttyUSB0 [OPEN]: USB-Serial Controller D
|
||||||
|
- /dev/ttyUSB1 [closed]: FT232R USB UART
|
||||||
|
- /dev/ttyACM0 [closed]: Arduino Mega 2560
|
||||||
|
```
|
||||||
|
|
||||||
|
This resource calls `list_serial_ports()` internally with default parameters (`usb_only=True`). For filtered results, use the `list_serial_ports` tool directly with the `grep` parameter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `serial://{port}/data`
|
||||||
|
|
||||||
|
Read available data from an open serial port. Returns the data decoded as UTF-8 with a byte count prefix.
|
||||||
|
|
||||||
|
**URI pattern:** `serial://{port}/data`
|
||||||
|
|
||||||
|
**Example URIs:**
|
||||||
|
```
|
||||||
|
serial:///dev/ttyUSB0/data
|
||||||
|
serial://COM3/data
|
||||||
|
serial://loop:///data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
[42 bytes from /dev/ttyUSB0]
|
||||||
|
AT+GMR
|
||||||
|
OK
|
||||||
|
ESP8266 firmware v2.1
|
||||||
|
```
|
||||||
|
|
||||||
|
If no data is available in the port's input buffer, returns:
|
||||||
|
```
|
||||||
|
[No data available on /dev/ttyUSB0]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the port is not open, returns an error message prompting you to use `open_serial_port` first.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Reading data from a resource consumes the bytes from the serial buffer. Once read, the data is no longer available for subsequent reads (via either resources or tools).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `serial://{port}/status`
|
||||||
|
|
||||||
|
Get the current configuration and connection status for an open serial port. Returns a Markdown-formatted status report.
|
||||||
|
|
||||||
|
**URI pattern:** `serial://{port}/status`
|
||||||
|
|
||||||
|
**Example URIs:**
|
||||||
|
```
|
||||||
|
serial:///dev/ttyUSB0/status
|
||||||
|
serial://COM3/status
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```markdown
|
||||||
|
# Serial Port Status: /dev/ttyUSB0
|
||||||
|
- Mode: RS232
|
||||||
|
- Open: True
|
||||||
|
- Baudrate: 115200
|
||||||
|
- Bytesize: 8
|
||||||
|
- Parity: N
|
||||||
|
- Stopbits: 1
|
||||||
|
- Timeout: 1.0s
|
||||||
|
- Bytes waiting (in): 0
|
||||||
|
- Bytes waiting (out): 0
|
||||||
|
- CTS: True
|
||||||
|
- DSR: True
|
||||||
|
- RI: False
|
||||||
|
- CD: False
|
||||||
|
- RTS: True
|
||||||
|
- DTR: True
|
||||||
|
```
|
||||||
|
|
||||||
|
This resource provides a snapshot of all port settings, buffer counts, and modem line states in a single read. Useful for diagnostics without calling multiple tools.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `serial://{port}/raw`
|
||||||
|
|
||||||
|
Read available data from an open serial port as a hex dump. Same as `serial://{port}/data` but returns raw bytes in hexadecimal format instead of decoded text.
|
||||||
|
|
||||||
|
**URI pattern:** `serial://{port}/raw`
|
||||||
|
|
||||||
|
**Example URIs:**
|
||||||
|
```
|
||||||
|
serial:///dev/ttyUSB0/raw
|
||||||
|
serial://COM3/raw
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
[8 bytes from /dev/ttyUSB0]
|
||||||
|
01 03 04 00 64 00 c8 fa
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this resource when working with binary protocols (Modbus, proprietary framing) where hex representation is more useful than decoded text.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Like the data resource, reading raw data consumes the bytes from the serial buffer.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## URI Encoding
|
||||||
|
|
||||||
|
The `{port}` segment in resource URIs uses the device path directly. For paths containing special characters (like forward slashes in Linux device paths), the MCP client handles URL encoding:
|
||||||
|
|
||||||
|
| Device Path | Resource URI |
|
||||||
|
|-------------|-------------|
|
||||||
|
| `/dev/ttyUSB0` | `serial:///dev/ttyUSB0/data` |
|
||||||
|
| `COM3` | `serial://COM3/data` |
|
||||||
|
| `loop://` | `serial://loop:///data` |
|
||||||
|
|
||||||
|
The server URL-decodes the port parameter internally, so both encoded and unencoded paths work correctly.
|
||||||
455
docs/src/content/docs/reference/tools-common.mdx
Normal file
455
docs/src/content/docs/reference/tools-common.mdx
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
---
|
||||||
|
title: Common Tools
|
||||||
|
description: Core serial port tools available in all modes
|
||||||
|
sidebar:
|
||||||
|
order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
These 18 tools work in both RS-232 and RS-485 modes. They cover port discovery, connection management, reading and writing data, configuration, flow control, and diagnostics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Port Discovery
|
||||||
|
|
||||||
|
### list_serial_ports
|
||||||
|
|
||||||
|
List available serial ports on the system. Call this first to discover what ports are available before opening one.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `usb_only` | `bool` | `True` | Only show USB serial devices. Set `False` to include legacy/phantom ttyS ports. |
|
||||||
|
| `grep` | `str \| None` | `None` | Regex pattern to filter ports. Matches against device path, description, hardware ID, manufacturer, product, and serial number. |
|
||||||
|
|
||||||
|
**Returns:** List of port objects with `device`, `description`, `hwid`, `manufacturer`, `product`, `serial_number`, and `is_open` fields. Sorted with USB ports (ttyUSB, ttyACM) first.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# List all USB serial devices
|
||||||
|
list_serial_ports()
|
||||||
|
|
||||||
|
# Include legacy ttyS ports
|
||||||
|
list_serial_ports(usb_only=False)
|
||||||
|
|
||||||
|
# Find FTDI adapters
|
||||||
|
list_serial_ports(grep="FTDI")
|
||||||
|
|
||||||
|
# Match specific USB VID:PID
|
||||||
|
list_serial_ports(grep="VID:PID=0403:6001")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connection Management
|
||||||
|
|
||||||
|
### open_serial_port
|
||||||
|
|
||||||
|
Open a serial port connection with optional auto-baud detection. If `baudrate` is not specified, the server automatically detects the baud rate by analyzing incoming data patterns.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path (`/dev/ttyUSB0`, `COM3`) or URL scheme (`socket://`, `rfc2217://`, `loop://`, `spy://`, `cp2110://`). See [URL Schemes](/reference/url-handlers/) for details. |
|
||||||
|
| `baudrate` | `int \| None` | `None` | Baud rate. If `None`, auto-detect is attempted (recommended for unknown devices). Falls back to `MCSERIAL_DEFAULT_BAUDRATE` if detection fails. |
|
||||||
|
| `bytesize` | `Literal[5, 6, 7, 8]` | `8` | Number of data bits. |
|
||||||
|
| `parity` | `Literal["N", "E", "O", "M", "S"]` | `"N"` | Parity checking: None, Even, Odd, Mark, or Space. |
|
||||||
|
| `stopbits` | `Literal[1, 1.5, 2]` | `1` | Number of stop bits. |
|
||||||
|
| `timeout` | `float` | `1.0` | Read timeout in seconds. Default comes from `MCSERIAL_DEFAULT_TIMEOUT`. |
|
||||||
|
| `write_timeout` | `float \| None` | `None` | Write timeout in seconds. `None` means blocking writes. |
|
||||||
|
| `inter_byte_timeout` | `float \| None` | `None` | Timeout between bytes during read. `None` disables inter-byte timeout. |
|
||||||
|
| `xonxoff` | `bool` | `False` | Enable software flow control (XON/XOFF). |
|
||||||
|
| `rtscts` | `bool` | `False` | Enable hardware RTS/CTS flow control. |
|
||||||
|
| `dsrdtr` | `bool` | `False` | Enable hardware DSR/DTR flow control. |
|
||||||
|
| `exclusive` | `bool` | `False` | Request exclusive access (lock port from other processes). |
|
||||||
|
| `autobaud_probe` | `str \| None` | `None` | String to send during auto-detection (e.g., `"UUUUU"` for sync). |
|
||||||
|
| `autobaud_timeout` | `float` | `0.3` | Timeout per rate during auto-detection in seconds. |
|
||||||
|
|
||||||
|
**Returns:** Connection status dict with `success`, `port`, `mode`, `baudrate`, `bytesize`, `parity`, `stopbits`, flow control settings, and `resource_uri`. When auto-baud is used, includes an `autobaud` object with detection details.
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
When `baudrate` is `None` and the port is a local device (not a URL scheme), auto-baud detection runs automatically. For best results with devices that only respond to input, pass an `autobaud_probe` string like `"UUUUU"` or `"AT\r\n"`.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Auto-detect baud rate
|
||||||
|
open_serial_port(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Explicit baud rate with 8N1 (most common settings)
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
|
||||||
|
|
||||||
|
# RS-232 with hardware flow control
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=9600, rtscts=True)
|
||||||
|
|
||||||
|
# Exclusive access prevents other processes from using the port
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=9600, exclusive=True)
|
||||||
|
|
||||||
|
# Auto-detect with sync probe
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", autobaud_probe="UUUUU")
|
||||||
|
|
||||||
|
# Open a network serial port
|
||||||
|
open_serial_port(port="socket://192.168.1.100:4001", baudrate=115200)
|
||||||
|
|
||||||
|
# Loopback for testing (no hardware needed)
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
URL-opened ports skip auto-baud detection and exclusive access. The `inter_byte_timeout` and `exclusive` parameters are not passed to URL-based connections.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### close_serial_port
|
||||||
|
|
||||||
|
Close an open serial port connection and release it for other processes.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to close. |
|
||||||
|
|
||||||
|
**Returns:** Status dict with `success`, `port`, and `message` fields.
|
||||||
|
|
||||||
|
```python
|
||||||
|
close_serial_port(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
### set_port_mode
|
||||||
|
|
||||||
|
Switch a serial port between RS-232 and RS-485 modes. Mode determines which mode-specific tools are available.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to configure. |
|
||||||
|
| `mode` | `Literal["rs232", "rs485"]` | required | Target mode. |
|
||||||
|
|
||||||
|
**Returns:** Status dict with `success`, `previous_mode`, `current_mode`, and `mode_tools` (list of tools available in the new mode).
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Switch to RS-485 mode for Modbus communication
|
||||||
|
set_port_mode(port="/dev/ttyUSB0", mode="rs485")
|
||||||
|
|
||||||
|
# Switch back to RS-232
|
||||||
|
set_port_mode(port="/dev/ttyUSB0", mode="rs232")
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Ports open in RS-232 mode by default. You must call `set_port_mode` before using RS-485 specific tools like `rs485_transact` or `rs485_scan_addresses`.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### get_connection_status
|
||||||
|
|
||||||
|
Get status of all open serial connections. Takes no parameters.
|
||||||
|
|
||||||
|
**Returns:** Dict with `connections` (map of port to status details) and `count`. Each connection includes `is_open`, `mode`, `baudrate`, `bytesize`, `parity`, `stopbits`, `timeout`, `write_timeout`, `inter_byte_timeout`, flow control flags, buffer counts (`in_waiting`, `out_waiting`), modem line states (`cts`, `dsr`, `ri`, `cd`, `rts`, `dtr`), and `resource_uri`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_connection_status()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Data
|
||||||
|
|
||||||
|
### write_serial
|
||||||
|
|
||||||
|
Write string data to an open serial port. Data is encoded using the specified encoding before sending.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to write to. |
|
||||||
|
| `data` | `str` | required | String data to send. |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for the data. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `bytes_written`, and `port`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Send an AT command
|
||||||
|
write_serial(port="/dev/ttyUSB0", data="AT\r\n")
|
||||||
|
|
||||||
|
# Send with specific encoding
|
||||||
|
write_serial(port="/dev/ttyUSB0", data="Hello", encoding="ascii")
|
||||||
|
```
|
||||||
|
|
||||||
|
### write_serial_bytes
|
||||||
|
|
||||||
|
Write raw bytes to an open serial port. Use this when you need to send binary data or specific byte sequences.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to write to. |
|
||||||
|
| `data` | `list[int]` | required | List of byte values (0-255). Values outside this range return an error. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `bytes_written`, and `port`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Send AT\r\n as raw bytes
|
||||||
|
write_serial_bytes(port="/dev/ttyUSB0", data=[65, 84, 13, 10])
|
||||||
|
|
||||||
|
# Send a Modbus frame
|
||||||
|
write_serial_bytes(port="/dev/ttyUSB0", data=[0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A])
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Byte values are validated before conversion. Any value outside 0-255 returns an error listing the invalid values, rather than silently truncating.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reading Data
|
||||||
|
|
||||||
|
### read_serial
|
||||||
|
|
||||||
|
Read data from an open serial port. Returns both decoded text and raw hex representation.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to read from. |
|
||||||
|
| `size` | `int \| None` | `None` | Maximum bytes to read. `None` reads all available data. |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |
|
||||||
|
| `timeout` | `float \| None` | `None` | Override the port's read timeout for this call. `None` uses the port default. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `data` (decoded string), `bytes_read`, `raw_hex`, and `port`. Decoding errors are replaced rather than raising exceptions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Read all available data
|
||||||
|
read_serial(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Read exactly 10 bytes
|
||||||
|
read_serial(port="/dev/ttyUSB0", size=10)
|
||||||
|
|
||||||
|
# Read with a longer timeout
|
||||||
|
read_serial(port="/dev/ttyUSB0", timeout=5.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### read_serial_line
|
||||||
|
|
||||||
|
Read a single line (until newline character) from an open serial port.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to read from. |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `line` (decoded string with trailing CR/LF stripped), `bytes_read`, and `port`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
read_serial_line(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
### read_serial_lines
|
||||||
|
|
||||||
|
Read multiple lines from an open serial port. Reads up to `max_lines`, stopping early if no more data is available (readline returns empty due to timeout).
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to read from. |
|
||||||
|
| `max_lines` | `int` | `10` | Maximum number of lines to read. Must be 1-1000. |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for decoding. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `lines` (list of decoded strings), `count`, `bytes_read`, and `port`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Read up to 10 lines (default)
|
||||||
|
read_serial_lines(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Read up to 50 lines of buffered output
|
||||||
|
read_serial_lines(port="/dev/ttyUSB0", max_lines=50)
|
||||||
|
```
|
||||||
|
|
||||||
|
### read_until
|
||||||
|
|
||||||
|
Read data until a specific terminator sequence is received. More flexible than `read_serial_line` because it supports any terminator string, not just newline.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to read from. |
|
||||||
|
| `terminator` | `str` | `"\n"` | Byte sequence to stop at. |
|
||||||
|
| `size` | `int \| None` | `None` | Maximum bytes to read. `None` means no limit (uses timeout). |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for the terminator and result. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `data` (decoded string), `bytes_read`, `raw_hex`, `port`, and `terminator_found` (bool indicating whether the terminator was actually found or the read timed out).
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Read until "OK\r\n"
|
||||||
|
read_until(port="/dev/ttyUSB0", terminator="OK\r\n")
|
||||||
|
|
||||||
|
# Read until a prompt character
|
||||||
|
read_until(port="/dev/ttyUSB0", terminator=">")
|
||||||
|
|
||||||
|
# Read until terminator with a size limit
|
||||||
|
read_until(port="/dev/ttyUSB0", terminator="\r\n", size=256)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### configure_serial
|
||||||
|
|
||||||
|
Modify settings on an already-open serial port. Only the parameters you provide are changed; everything else stays the same.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to configure. |
|
||||||
|
| `baudrate` | `int \| None` | `None` | New baud rate. `None` = no change. |
|
||||||
|
| `timeout` | `float \| None` | `None` | New read timeout in seconds. `None` = no change. |
|
||||||
|
| `write_timeout` | `float \| None` | `None` | New write timeout in seconds. `None` = no change. |
|
||||||
|
| `inter_byte_timeout` | `float \| None` | `None` | Timeout between bytes. `None` = no change. |
|
||||||
|
| `xonxoff` | `bool \| None` | `None` | Software flow control XON/XOFF. `None` = no change. |
|
||||||
|
| `rtscts` | `bool \| None` | `None` | Hardware RTS/CTS flow control. `None` = no change. |
|
||||||
|
| `dsrdtr` | `bool \| None` | `None` | Hardware DSR/DTR flow control. `None` = no change. |
|
||||||
|
| `rts` | `bool \| None` | `None` | Set RTS line state. `None` = no change. |
|
||||||
|
| `dtr` | `bool \| None` | `None` | Set DTR line state. `None` = no change. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and all current settings (`baudrate`, `timeout`, `write_timeout`, `inter_byte_timeout`, `xonxoff`, `rtscts`, `dsrdtr`, `rts`, `dtr`).
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Change baud rate on the fly
|
||||||
|
configure_serial(port="/dev/ttyUSB0", baudrate=115200)
|
||||||
|
|
||||||
|
# Increase read timeout
|
||||||
|
configure_serial(port="/dev/ttyUSB0", timeout=5.0)
|
||||||
|
|
||||||
|
# Enable hardware flow control
|
||||||
|
configure_serial(port="/dev/ttyUSB0", rtscts=True)
|
||||||
|
|
||||||
|
# Set DTR low (useful for reset sequences)
|
||||||
|
configure_serial(port="/dev/ttyUSB0", dtr=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
### flush_serial
|
||||||
|
|
||||||
|
Clear serial port input and/or output buffers. Discards any pending data.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to flush. |
|
||||||
|
| `input_buffer` | `bool` | `True` | Clear the input/receive buffer. |
|
||||||
|
| `output_buffer` | `bool` | `True` | Clear the output/transmit buffer. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `flushed_input`, and `flushed_output`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Flush both buffers (default)
|
||||||
|
flush_serial(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Flush only the input buffer
|
||||||
|
flush_serial(port="/dev/ttyUSB0", input_buffer=True, output_buffer=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flow Control
|
||||||
|
|
||||||
|
### set_flow_control
|
||||||
|
|
||||||
|
Manually gate input/output flow control signals on a serial port. Flow control must already be enabled via `configure_serial` (set `xonxoff=True` or `rtscts=True`) for these signals to have effect.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `input_flow` | `bool \| None` | `None` | `True` sends XON (allow remote to send), `False` sends XOFF (tell remote to stop). `None` = no change. |
|
||||||
|
| `output_flow` | `bool \| None` | `None` | `True` resumes outgoing data, `False` pauses it. `None` = no change. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `changes` (what was set), and `flow_control_enabled` (current `xonxoff` and `rtscts` state).
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
This function is not portable across all platforms. Behavior depends on the OS and driver support.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Pause incoming data (send XOFF)
|
||||||
|
set_flow_control(port="/dev/ttyUSB0", input_flow=False)
|
||||||
|
|
||||||
|
# Resume incoming data (send XON)
|
||||||
|
set_flow_control(port="/dev/ttyUSB0", input_flow=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### cancel_read
|
||||||
|
|
||||||
|
Cancel any pending read operation on a serial port. Interrupts a blocking read by calling `cancel_read()` on the underlying pyserial connection.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and `cancelled`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Only effective on platforms that support it (POSIX with select-based reads).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
cancel_read(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
### cancel_write
|
||||||
|
|
||||||
|
Cancel any pending write operation on a serial port. Interrupts a blocking write by calling `cancel_write()` on the underlying pyserial connection.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and `cancelled`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Only effective on platforms that support it (POSIX with select-based writes).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
cancel_write(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
### set_low_latency_mode
|
||||||
|
|
||||||
|
Enable or disable low-latency mode on a serial port. Reduces latency by changing how the kernel buffers data. Linux only.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `enabled` | `bool` | `True` | Enable or disable low latency mode. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and `low_latency`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
This is a Linux-specific feature. It may not be available on all hardware or kernel versions. Returns an error on unsupported systems rather than silently failing.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Enable low latency for time-critical communication
|
||||||
|
set_low_latency_mode(port="/dev/ttyUSB0", enabled=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### detect_baud_rate
|
||||||
|
|
||||||
|
Auto-detect baud rate using multiple heuristics: 0x55 sync pattern analysis, byte distribution analysis, ASCII readability scoring, and framing indicators.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path. Opened temporarily if not already open. If open, the baud rate is changed during testing and restored afterward. |
|
||||||
|
| `probe` | `str \| None` | `None` | String to send to trigger a response. Use `"U"` or `"UUUUU"` for sync-based detection on echo-enabled devices. |
|
||||||
|
| `timeout_per_rate` | `float` | `0.5` | Seconds to wait for data at each baud rate. |
|
||||||
|
| `baudrates` | `list[int] \| None` | `None` | Custom list of rates to try. Default tries common rates: 115200, 9600, 57600, 38400, 19200, 230400, 460800, 921600, 4800, 2400, 1200, 300, 500000, 576000, 1000000, 1500000. |
|
||||||
|
| `use_sync_pattern` | `bool` | `True` | Analyze 0x55 sync patterns for improved detection accuracy. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `detected_baudrate` (or `None` if confidence is below 50), `confidence` (0-100 score), `results` (top 5 candidates with detailed scoring), and `rates_tested`.
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Detection works best when the remote device is actively sending data. For devices that only respond to input, use the `probe` parameter to send a string that triggers a response at each candidate baud rate.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Detect baud rate on an active device
|
||||||
|
detect_baud_rate(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Use a sync probe for echo devices
|
||||||
|
detect_baud_rate(port="/dev/ttyUSB0", probe="UUUUU")
|
||||||
|
|
||||||
|
# Test only common embedded rates
|
||||||
|
detect_baud_rate(port="/dev/ttyUSB0", baudrates=[9600, 115200, 57600])
|
||||||
|
|
||||||
|
# Longer wait time for slow devices
|
||||||
|
detect_baud_rate(port="/dev/ttyUSB0", timeout_per_rate=1.0)
|
||||||
|
```
|
||||||
174
docs/src/content/docs/reference/tools-file-transfer.mdx
Normal file
174
docs/src/content/docs/reference/tools-file-transfer.mdx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
---
|
||||||
|
title: File Transfer Tools
|
||||||
|
description: Send and receive files using X/Y/ZMODEM protocols
|
||||||
|
sidebar:
|
||||||
|
order: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Transfer files over serial connections using classic X/Y/ZMODEM protocols. These tools work in both RS-232 and RS-485 modes.
|
||||||
|
|
||||||
|
## Protocol Comparison
|
||||||
|
|
||||||
|
| Protocol | Block Size | Batch | Resume | Error Correction | Recommended |
|
||||||
|
|----------|-----------|-------|--------|------------------|-------------|
|
||||||
|
| XMODEM | 128B | No | No | Checksum/CRC | Legacy only |
|
||||||
|
| XMODEM-1K| 1024B | No | No | CRC-16 | - |
|
||||||
|
| YMODEM | 1024B | Yes | No | CRC-16 | Batch needs |
|
||||||
|
| ZMODEM | Streaming | Yes | Yes | CRC-32 | Yes |
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Use ZMODEM unless you have a specific reason not to. It is the fastest protocol, supports streaming (no stop-and-wait per block), handles batch transfers, and can resume interrupted transfers.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## file_transfer_send
|
||||||
|
|
||||||
|
Send a single file over serial using a selected protocol. The receiver must be waiting in receive mode before calling this tool. ZMODEM receivers typically auto-start when they detect the init sequence.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the open serial port. |
|
||||||
|
| `file_path` | `str` | required | Path to the file to send. |
|
||||||
|
| `protocol` | `Literal["xmodem", "xmodem1k", "ymodem", "zmodem"]` | `"zmodem"` | Transfer protocol to use. |
|
||||||
|
|
||||||
|
**Returns:** Transfer statistics dict with protocol-specific details including bytes sent and any errors. The dict also includes `protocol` and `file` fields.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Send a firmware file using ZMODEM (default)
|
||||||
|
file_transfer_send(port="/dev/ttyUSB0", file_path="/home/user/firmware.bin")
|
||||||
|
|
||||||
|
# Send using XMODEM for legacy compatibility
|
||||||
|
file_transfer_send(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
file_path="/home/user/config.txt",
|
||||||
|
protocol="xmodem",
|
||||||
|
)
|
||||||
|
|
||||||
|
# XMODEM-1K for faster transfers on legacy systems
|
||||||
|
file_transfer_send(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
file_path="/home/user/data.bin",
|
||||||
|
protocol="xmodem1k",
|
||||||
|
)
|
||||||
|
|
||||||
|
# YMODEM preserves filename and size metadata
|
||||||
|
file_transfer_send(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
file_path="/home/user/update.bin",
|
||||||
|
protocol="ymodem",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
The file must exist on the local filesystem. The tool validates the path before starting the transfer and returns an error if the file is not found.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## file_transfer_receive
|
||||||
|
|
||||||
|
Receive a file over serial using a selected protocol. The behavior of `save_path` depends on the protocol.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the open serial port. |
|
||||||
|
| `save_path` | `str` | required | For XMODEM: full file path. For YMODEM/ZMODEM: directory where received files are saved. |
|
||||||
|
| `protocol` | `Literal["xmodem", "ymodem", "zmodem"]` | `"zmodem"` | Transfer protocol to use. |
|
||||||
|
| `overwrite` | `bool` | `False` | Whether to overwrite existing files. |
|
||||||
|
|
||||||
|
**Returns:** Transfer statistics dict with protocol-specific details including bytes received and file paths. Includes the `protocol` field.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
XMODEM does not transmit the filename -- the sender and receiver must agree on it beforehand. Pass the full destination file path as `save_path`. YMODEM and ZMODEM carry the filename in the protocol, so `save_path` should be a directory.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Receive via ZMODEM into a directory
|
||||||
|
file_transfer_receive(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
save_path="/home/user/downloads/",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Receive a single file via XMODEM
|
||||||
|
file_transfer_receive(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
save_path="/home/user/received_file.bin",
|
||||||
|
protocol="xmodem",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Receive via YMODEM, allowing overwrite
|
||||||
|
file_transfer_receive(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
save_path="/home/user/downloads/",
|
||||||
|
protocol="ymodem",
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
When `overwrite` is `False` (the default), the tool returns an error if the destination file already exists. For XMODEM this is checked before the transfer starts. For YMODEM/ZMODEM, the check happens as each file is received.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## file_transfer_send_batch
|
||||||
|
|
||||||
|
Send multiple files in a single batch transfer. Only YMODEM and ZMODEM support batch mode.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the open serial port. |
|
||||||
|
| `file_paths` | `list[str]` | required | List of file paths to send. |
|
||||||
|
| `protocol` | `Literal["ymodem", "zmodem"]` | `"zmodem"` | Transfer protocol. Must be `ymodem` or `zmodem`. |
|
||||||
|
|
||||||
|
**Returns:** Transfer statistics dict covering all files, including the `protocol` field.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
All files are validated before the transfer begins. If any file in the list does not exist, the entire batch is rejected with an error identifying the missing file.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Batch send with ZMODEM (default)
|
||||||
|
file_transfer_send_batch(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
file_paths=[
|
||||||
|
"/home/user/config.json",
|
||||||
|
"/home/user/firmware.bin",
|
||||||
|
"/home/user/README.txt",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Batch send with YMODEM
|
||||||
|
file_transfer_send_batch(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
file_paths=["/home/user/file1.txt", "/home/user/file2.txt"],
|
||||||
|
protocol="ymodem",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical Workflow
|
||||||
|
|
||||||
|
A complete file transfer session looks like this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. Open the port at a suitable baud rate
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
|
||||||
|
|
||||||
|
# 2. Send a file
|
||||||
|
file_transfer_send(port="/dev/ttyUSB0", file_path="/home/user/firmware.bin")
|
||||||
|
|
||||||
|
# 3. Or receive a file
|
||||||
|
file_transfer_receive(port="/dev/ttyUSB0", save_path="/home/user/downloads/")
|
||||||
|
|
||||||
|
# 4. Close when done
|
||||||
|
close_serial_port(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Higher baud rates significantly improve transfer speed. For file transfers, use the highest baud rate both endpoints support -- 115200 is a common safe choice, but many adapters support 921600 or higher.
|
||||||
|
</Aside>
|
||||||
170
docs/src/content/docs/reference/tools-rs232.mdx
Normal file
170
docs/src/content/docs/reference/tools-rs232.mdx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
title: RS-232 Tools
|
||||||
|
description: Modem control lines, break signals, and RS-232 specific operations
|
||||||
|
sidebar:
|
||||||
|
order: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
RS-232 is the default mode for all newly opened ports. These five tools provide control over modem signal lines and break conditions -- the hardware handshaking and signaling layer of point-to-point serial communication.
|
||||||
|
|
||||||
|
<Aside type="note" title="RS-232 mode required">
|
||||||
|
All tools on this page require the port to be in RS-232 mode. Since RS-232 is the default mode when a port is opened, no extra setup is needed unless you previously switched to RS-485 mode. To switch back, call `set_port_mode(port, "rs232")`.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
<Aside type="tip" title="Modem control lines explained">
|
||||||
|
RS-232 defines six signal lines beyond TX/RX data:
|
||||||
|
|
||||||
|
**Output lines** (controlled by you):
|
||||||
|
- **RTS** (Request To Send) -- signals that you are ready to receive data
|
||||||
|
- **DTR** (Data Terminal Ready) -- signals that your terminal is present and active
|
||||||
|
|
||||||
|
**Input lines** (read-only, set by the remote device):
|
||||||
|
- **CTS** (Clear To Send) -- remote device is ready to receive
|
||||||
|
- **DSR** (Data Set Ready) -- remote device is present and powered
|
||||||
|
- **RI** (Ring Indicator) -- incoming call (modem legacy)
|
||||||
|
- **CD** (Carrier Detect) -- connection is established
|
||||||
|
|
||||||
|
In practice, many embedded devices repurpose these lines for reset, bootloader entry, or power control.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## get_modem_lines
|
||||||
|
|
||||||
|
Read all RS-232 modem control and status line states, including the current break condition.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port to query. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `input_lines` (object with `cts`, `dsr`, `ri`, `cd`), `output_lines` (object with `rts`, `dtr`), and `break_condition` (bool).
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_modem_lines(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Example response:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "port": "/dev/ttyUSB0",
|
||||||
|
# "input_lines": {"cts": true, "dsr": true, "ri": false, "cd": false},
|
||||||
|
# "output_lines": {"rts": true, "dtr": true},
|
||||||
|
# "break_condition": false
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## set_modem_lines
|
||||||
|
|
||||||
|
Set the RS-232 output control lines (RTS and DTR). Only the lines you specify are changed; pass `None` (or omit the parameter) to leave a line unchanged.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `rts` | `bool \| None` | `None` | Set RTS (Request To Send) state. `None` = no change. |
|
||||||
|
| `dtr` | `bool \| None` | `None` | Set DTR (Data Terminal Ready) state. `None` = no change. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `rts`, and `dtr` (current states after change).
|
||||||
|
|
||||||
|
These lines are commonly used for:
|
||||||
|
- Hardware flow control
|
||||||
|
- Device reset sequences (many boards use DTR for reset)
|
||||||
|
- Power control on some devices
|
||||||
|
- Custom signaling protocols
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Assert DTR to signal "terminal ready"
|
||||||
|
set_modem_lines(port="/dev/ttyUSB0", dtr=True)
|
||||||
|
|
||||||
|
# Drop RTS to signal "stop sending"
|
||||||
|
set_modem_lines(port="/dev/ttyUSB0", rts=False)
|
||||||
|
|
||||||
|
# Set both lines at once
|
||||||
|
set_modem_lines(port="/dev/ttyUSB0", rts=True, dtr=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## pulse_line
|
||||||
|
|
||||||
|
Pulse an RS-232 control line high-then-low or low-then-high. Commonly used for device reset sequences.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `line` | `Literal["rts", "dtr"]` | required | Which line to pulse. |
|
||||||
|
| `duration_ms` | `int` | `100` | Pulse duration in milliseconds. Must be 1-5000. |
|
||||||
|
| `active_low` | `bool` | `True` | If `True`, pulse LOW then HIGH. If `False`, pulse HIGH then LOW. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `line`, `duration_ms`, and `active_low`.
|
||||||
|
|
||||||
|
The line is set to its initial state, held for `duration_ms`, pulsed to the active state for `duration_ms`, then restored to its original value.
|
||||||
|
|
||||||
|
Typical reset sequences:
|
||||||
|
- **Arduino**: DTR pulse triggers auto-reset
|
||||||
|
- **ESP32/ESP8266**: DTR + RTS sequence enters bootloader
|
||||||
|
- **Custom hardware**: Various reset/trigger signals
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Arduino-style reset (DTR pulse, active low)
|
||||||
|
pulse_line(port="/dev/ttyUSB0", line="dtr", duration_ms=100)
|
||||||
|
|
||||||
|
# Active-high pulse on RTS
|
||||||
|
pulse_line(port="/dev/ttyUSB0", line="rts", duration_ms=200, active_low=False)
|
||||||
|
|
||||||
|
# Short trigger pulse
|
||||||
|
pulse_line(port="/dev/ttyUSB0", line="dtr", duration_ms=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## send_break
|
||||||
|
|
||||||
|
Send a timed serial break signal -- a sustained low state on the TX line longer than a character frame. Used to get the attention of a remote device or trigger special modes.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `duration_ms` | `int` | `250` | Break duration in milliseconds. Must be 1-5000. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and `duration_ms`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Standard break signal
|
||||||
|
send_break(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Short break (100ms)
|
||||||
|
send_break(port="/dev/ttyUSB0", duration_ms=100)
|
||||||
|
|
||||||
|
# Long break to force attention
|
||||||
|
send_break(port="/dev/ttyUSB0", duration_ms=1000)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## set_break_condition
|
||||||
|
|
||||||
|
Set or clear the break condition on a serial port. Unlike `send_break()` which sends a timed pulse, this holds the break condition indefinitely until explicitly cleared. Useful for protocols that require sustained break states.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `enabled` | `bool` | required | `True` to assert break (hold TX low), `False` to release. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, and `break_condition`.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
The break condition holds the TX line low continuously. The remote device will not receive valid data while break is asserted. Always clear the condition with `enabled=False` when done.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Assert break condition
|
||||||
|
set_break_condition(port="/dev/ttyUSB0", enabled=True)
|
||||||
|
|
||||||
|
# ... wait for device to respond ...
|
||||||
|
|
||||||
|
# Release break
|
||||||
|
set_break_condition(port="/dev/ttyUSB0", enabled=False)
|
||||||
|
```
|
||||||
218
docs/src/content/docs/reference/tools-rs485.mdx
Normal file
218
docs/src/content/docs/reference/tools-rs485.mdx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
---
|
||||||
|
title: RS-485 Tools
|
||||||
|
description: Half-duplex bus communication, transactions, and address scanning
|
||||||
|
sidebar:
|
||||||
|
order: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
RS-485 enables half-duplex multi-drop bus communication where multiple devices share a single pair of wires. These tools handle direction control, request/response transactions, and bus scanning.
|
||||||
|
|
||||||
|
<Aside type="caution" title="Switch to RS-485 mode first">
|
||||||
|
Ports open in RS-232 mode by default. Before using any tool on this page, switch the port to RS-485 mode:
|
||||||
|
|
||||||
|
```python
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=9600)
|
||||||
|
set_port_mode(port="/dev/ttyUSB0", mode="rs485")
|
||||||
|
```
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## set_rs485_mode
|
||||||
|
|
||||||
|
Configure the hardware RS-485 settings for half-duplex communication. This sets up automatic TX/RX direction switching via the RTS line (DE/RE control) on hardware that supports it.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path of the port. |
|
||||||
|
| `enabled` | `bool` | `True` | Enable or disable RS-485 hardware mode. |
|
||||||
|
| `delay_before_tx` | `float` | `0.0` | Delay in seconds before enabling TX. Allows bus settling time. |
|
||||||
|
| `delay_before_rx` | `float` | `0.0` | Delay in seconds before enabling RX after TX completes. |
|
||||||
|
| `rts_level_for_tx` | `bool` | `True` | RTS level when transmitting. `True` = high. |
|
||||||
|
| `rts_level_for_rx` | `bool` | `False` | RTS level when receiving. `True` = high. |
|
||||||
|
| `loopback` | `bool` | `False` | Enable RS-485 loopback mode (see your own transmissions). |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `rs485_enabled`, and the configured settings (`rts_level_for_tx`, `rts_level_for_rx`, `delay_before_tx`, `delay_before_rx`, `loopback`). Settings are `None` when RS-485 is disabled.
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
The `rts_level_for_tx` and `rts_level_for_rx` parameters control the polarity of the DE/RE enable pin. Most RS-485 transceivers use active-high for TX enable and active-low for RX enable, which is the default configuration.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Enable hardware RS-485 with defaults
|
||||||
|
set_rs485_mode(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Add bus settling delays for long cable runs
|
||||||
|
set_rs485_mode(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
delay_before_tx=0.001,
|
||||||
|
delay_before_rx=0.001,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inverted RTS polarity
|
||||||
|
set_rs485_mode(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
rts_level_for_tx=False,
|
||||||
|
rts_level_for_rx=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Disable hardware RS-485 (revert to software control)
|
||||||
|
set_rs485_mode(port="/dev/ttyUSB0", enabled=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## check_rs485_support
|
||||||
|
|
||||||
|
Detect whether a serial port's hardware and driver support automatic RS-485 direction control, or whether software RTS toggling is needed. This tool queries `udevadm` for USB device information and tests the kernel RS-485 ioctl.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path to check. The port does not need to be open. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `driver` (e.g., `ftdi_sio`, `cp210x`, `ch341`), `chip` (product string if detected), `hardware_rs485` (bool), `software_fallback` (bool), `kernel_rs485_ioctl` (bool), `notes` (list of informational strings), and `recommendation`.
|
||||||
|
|
||||||
|
Known hardware support:
|
||||||
|
|
||||||
|
| Driver | Chip | Hardware RS-485 |
|
||||||
|
|--------|------|-----------------|
|
||||||
|
| `ftdi_sio` | FT232, FT2232, etc. | Yes -- auto-direction control |
|
||||||
|
| `cp210x` | CP2105, CP2108 | Yes |
|
||||||
|
| `cp210x` | CP2102 | No -- use software RTS control |
|
||||||
|
| `ch341` | CH340, CH341 | No -- timing may be unreliable |
|
||||||
|
| `pl2303` | PL2303 | No -- use software RTS control |
|
||||||
|
| native | ttyS, ttyAMA | Yes -- check transceiver wiring |
|
||||||
|
|
||||||
|
```python
|
||||||
|
check_rs485_support(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Example response:
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "port": "/dev/ttyUSB0",
|
||||||
|
# "driver": "ftdi_sio",
|
||||||
|
# "chip": "FT232R",
|
||||||
|
# "hardware_rs485": true,
|
||||||
|
# "software_fallback": true,
|
||||||
|
# "kernel_rs485_ioctl": true,
|
||||||
|
# "notes": ["FTDI chips have hardware RS-485 auto-direction"],
|
||||||
|
# "recommendation": "Use set_rs485_mode() for automatic DE/RE control"
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
This tool uses `udevadm` for USB device detection. If `udevadm` is not installed, detection falls back to basic checks and a note is added to the response.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## rs485_transact
|
||||||
|
|
||||||
|
Send data and receive a response on the RS-485 bus in a single half-duplex transaction. Handles TX-to-RX turnaround timing automatically, including manual RTS control when hardware RS-485 is not available.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path (must be open in RS-485 mode). |
|
||||||
|
| `data` | `str` | required | Data string to send. |
|
||||||
|
| `response_timeout` | `float` | `1.0` | Maximum time in seconds to wait for a response. |
|
||||||
|
| `response_terminator` | `str \| None` | `None` | Stop reading when this sequence is received. |
|
||||||
|
| `response_length` | `int \| None` | `None` | Expected response length in bytes. Alternative to `response_terminator`. |
|
||||||
|
| `encoding` | `str` | `"utf-8"` | Character encoding for send and receive data. |
|
||||||
|
| `turnaround_delay` | `float` | `0.005` | Delay in seconds after TX completes before switching to RX. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `bytes_sent`, `data_sent`, `response` (decoded string), `response_bytes`, `response_hex`, and `hardware_rs485` (whether hardware RS-485 was used).
|
||||||
|
|
||||||
|
The transaction flow:
|
||||||
|
1. Clear the input buffer
|
||||||
|
2. Assert RTS for TX (if no hardware RS-485)
|
||||||
|
3. Send encoded data and flush
|
||||||
|
4. Wait for TX completion + turnaround delay
|
||||||
|
5. De-assert RTS for RX (if no hardware RS-485)
|
||||||
|
6. Read response using terminator, length, or timeout
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Simple query with timeout-based response
|
||||||
|
rs485_transact(port="/dev/ttyUSB0", data="?ID\r\n")
|
||||||
|
|
||||||
|
# Modbus-style with known response length
|
||||||
|
rs485_transact(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
data="\x01\x03\x00\x00\x00\x01",
|
||||||
|
response_length=7,
|
||||||
|
encoding="latin-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for a specific terminator
|
||||||
|
rs485_transact(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
data="READ\r\n",
|
||||||
|
response_terminator="\r\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adjust turnaround for slow devices
|
||||||
|
rs485_transact(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
data="PING",
|
||||||
|
turnaround_delay=0.01,
|
||||||
|
response_timeout=2.0,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
When neither `response_terminator` nor `response_length` is set, the tool waits for half the `response_timeout`, then reads all available bytes. For protocols with predictable framing, always specify a terminator or length for faster, more reliable reads.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## rs485_scan_addresses
|
||||||
|
|
||||||
|
Scan the RS-485 bus for responding devices by sending a probe message to each address in a range. Useful for discovering Modbus or similar addressed devices.
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `port` | `str` | required | Device path (must be open in RS-485 mode). |
|
||||||
|
| `start_address` | `int` | `1` | First address to scan. |
|
||||||
|
| `end_address` | `int` | `247` | Last address to scan. Default 247 is the Modbus maximum. |
|
||||||
|
| `probe_template` | `str` | `"{addr:02x}03000001"` | Message template with `{addr}` placeholder for the address. Default is a Modbus "read holding register" frame. Customize for your protocol. |
|
||||||
|
| `response_timeout` | `float` | `0.1` | Time in seconds to wait for a response per address. |
|
||||||
|
| `encoding` | `str` | `"latin-1"` | Encoding for the probe string. Use `latin-1` for raw byte protocols. |
|
||||||
|
|
||||||
|
**Returns:** Dict with `success`, `port`, `addresses_scanned`, `devices_found`, `responding_addresses` (list of objects with `address`, `response_length`, `response_hex`), and `hardware_rs485`.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Scanning 247 addresses at 0.1s per address takes approximately 25 seconds. Narrow the address range when possible, or reduce `response_timeout` for faster scans on responsive buses.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Scan the full Modbus range
|
||||||
|
rs485_scan_addresses(port="/dev/ttyUSB0")
|
||||||
|
|
||||||
|
# Scan a known range of addresses
|
||||||
|
rs485_scan_addresses(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
start_address=1,
|
||||||
|
end_address=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom probe template for a different protocol
|
||||||
|
rs485_scan_addresses(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
probe_template="{addr:c}ID?\r\n",
|
||||||
|
response_timeout=0.2,
|
||||||
|
encoding="ascii",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Faster scan with short timeout
|
||||||
|
rs485_scan_addresses(
|
||||||
|
port="/dev/ttyUSB0",
|
||||||
|
start_address=1,
|
||||||
|
end_address=32,
|
||||||
|
response_timeout=0.05,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
If the `probe_template` contains the `{addr}` placeholder, it is formatted with the current address as an integer. If no placeholder is found, the address is prepended as a single character (`chr(addr)`) to the template string.
|
||||||
|
</Aside>
|
||||||
183
docs/src/content/docs/reference/url-handlers.mdx
Normal file
183
docs/src/content/docs/reference/url-handlers.mdx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
---
|
||||||
|
title: URL Schemes
|
||||||
|
description: Serial port URL schemes for physical, network, and virtual connections
|
||||||
|
sidebar:
|
||||||
|
order: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
The `port` parameter of `open_serial_port` accepts both local device paths and URL schemes. URL schemes let you connect to network serial devices, virtual loopbacks, debug wrappers, and specialized hardware without changing your read/write workflow.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
URL-opened ports skip auto-baud detection and exclusive access. The `inter_byte_timeout` and `exclusive` parameters are not passed to URL-based connections.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Device Paths
|
||||||
|
|
||||||
|
Standard local serial port paths. These are the most common way to connect to physical hardware.
|
||||||
|
|
||||||
|
**Linux:**
|
||||||
|
```
|
||||||
|
/dev/ttyUSB0 -- USB-to-serial adapter (FTDI, CP210x, CH340)
|
||||||
|
/dev/ttyACM0 -- USB CDC ACM device (Arduino, STM32)
|
||||||
|
/dev/ttyS0 -- Built-in UART (motherboard, Raspberry Pi)
|
||||||
|
/dev/ttyAMA0 -- ARM UART (Raspberry Pi primary UART)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```
|
||||||
|
COM3 -- USB-to-serial or built-in COM port
|
||||||
|
COM12 -- Higher-numbered ports
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
```
|
||||||
|
/dev/tty.usbserial-1420 -- USB-to-serial adapter
|
||||||
|
/dev/tty.usbmodem14201 -- USB CDC ACM device
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
open_serial_port(port="/dev/ttyUSB0", baudrate=115200)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## socket://
|
||||||
|
|
||||||
|
Raw TCP socket connection. Connects to serial-to-Ethernet bridges, terminal servers, and networked serial devices that expose a plain TCP port.
|
||||||
|
|
||||||
|
**Format:** `socket://host:port`
|
||||||
|
|
||||||
|
Data sent to the socket appears on the remote serial port and vice versa. No protocol negotiation occurs -- the connection is a raw byte stream.
|
||||||
|
|
||||||
|
**When to use:** Serial-to-Ethernet bridges (Moxa, Digi, Lantronix), ESP32/ESP8266 with TCP server firmware, or any device that bridges serial to a TCP socket.
|
||||||
|
|
||||||
|
```python
|
||||||
|
open_serial_port(port="socket://192.168.1.100:4001", baudrate=115200)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
The baud rate, parity, and other serial parameters must be configured on the bridge device separately. The `baudrate` parameter here is stored locally but not transmitted to the remote device. Use `rfc2217://` if you need to configure the remote port's serial settings.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## rfc2217://
|
||||||
|
|
||||||
|
Telnet COM Port Control per RFC 2217. Extends the Telnet protocol to configure remote serial port settings (baud rate, data bits, parity, flow control) over the network.
|
||||||
|
|
||||||
|
**Format:** `rfc2217://host:port`
|
||||||
|
|
||||||
|
Unlike `socket://`, this scheme negotiates serial parameters with the remote device. When you set `baudrate=115200`, that setting is transmitted to and applied by the remote server.
|
||||||
|
|
||||||
|
**When to use:** Remote serial port servers that implement RFC 2217, such as ser2net, or commercial terminal servers with Telnet COM Port support.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Remote serial with full parameter control
|
||||||
|
open_serial_port(port="rfc2217://192.168.1.100:2217", baudrate=115200)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
If your serial server supports RFC 2217, always prefer it over `socket://`. It ensures the remote port is configured with the correct baud rate and framing, rather than relying on out-of-band configuration.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## loop://
|
||||||
|
|
||||||
|
Loopback virtual port. Everything written is echoed back as readable data. No hardware required.
|
||||||
|
|
||||||
|
**Format:** `loop://`
|
||||||
|
|
||||||
|
**When to use:** Testing, development, CI/CD pipelines, or verifying tool behavior without physical serial hardware.
|
||||||
|
|
||||||
|
```python
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
write_serial(port="loop://", data="hello")
|
||||||
|
read_serial(port="loop://") # Returns "hello"
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## spy://
|
||||||
|
|
||||||
|
Debug wrapper that logs all serial traffic to stderr while passing data through to the underlying port. Wraps a real device path.
|
||||||
|
|
||||||
|
**Format:** `spy://device_path`
|
||||||
|
|
||||||
|
All bytes read and written are printed to stderr in a human-readable format. The underlying serial connection works exactly as if you opened the device directly.
|
||||||
|
|
||||||
|
**When to use:** Debugging communication issues, reverse-engineering protocols, or logging traffic for analysis.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Wraps /dev/ttyUSB0 with traffic logging
|
||||||
|
open_serial_port(port="spy:///dev/ttyUSB0", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
The spy output goes to stderr of the mcserial server process. Check your server's stderr output to see the logged traffic.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cp2110://
|
||||||
|
|
||||||
|
Silicon Labs CP2110 HID-to-UART bridge. Connects to CP2110 devices via the USB HID interface rather than a traditional serial driver.
|
||||||
|
|
||||||
|
**Format:** `cp2110://`
|
||||||
|
|
||||||
|
**When to use:** Devices using the Silicon Labs CP2110 chip that present as HID devices rather than standard serial ports.
|
||||||
|
|
||||||
|
<Aside type="caution" title="Requires extra dependency">
|
||||||
|
This scheme requires the `hidapi` library. Install it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install mcserial[cp2110]
|
||||||
|
```
|
||||||
|
|
||||||
|
Without this extra, opening a `cp2110://` URL returns an error explaining the missing dependency.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
```python
|
||||||
|
open_serial_port(port="cp2110://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## hwgrep://
|
||||||
|
|
||||||
|
Hardware grep -- opens the first serial port matching a hardware description pattern. Searches across device path, description, hardware ID, manufacturer, product, and serial number fields.
|
||||||
|
|
||||||
|
**Format:** `hwgrep://pattern`
|
||||||
|
|
||||||
|
**When to use:** When you know the type of device but not the exact port assignment. Useful for scripts that need to find a specific adapter regardless of which USB port it is plugged into.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Open the first FTDI adapter found
|
||||||
|
open_serial_port(port="hwgrep://FTDI", baudrate=115200)
|
||||||
|
|
||||||
|
# Match by USB VID:PID
|
||||||
|
open_serial_port(port="hwgrep://VID:PID=0403:6001", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
For port discovery without opening, use `list_serial_ports(grep="FTDI")` to see all matching ports first, then open a specific one by device path.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Scheme | Use Case | Remote Config | Hardware Required |
|
||||||
|
|--------|----------|---------------|-------------------|
|
||||||
|
| Device path | Local serial hardware | N/A | Yes |
|
||||||
|
| `socket://` | Serial-to-Ethernet bridges | No (configure bridge separately) | Network bridge |
|
||||||
|
| `rfc2217://` | Remote serial with parameter control | Yes (via Telnet negotiation) | RFC 2217 server |
|
||||||
|
| `loop://` | Testing and development | N/A | None |
|
||||||
|
| `spy://` | Traffic debugging | N/A | Underlying device |
|
||||||
|
| `cp2110://` | Silicon Labs HID-to-UART | N/A | CP2110 chip |
|
||||||
|
| `hwgrep://` | Auto-detect by hardware description | N/A | Matched device |
|
||||||
269
docs/src/content/docs/tutorials/getting-started.mdx
Normal file
269
docs/src/content/docs/tutorials/getting-started.mdx
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
description: Install mcserial and make your first serial connection.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Tabs, TabItem, Aside, Card, CardGrid } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
mcserial is a FastMCP server that gives MCP clients full serial port access -- discovery, configuration, read/write, modem control, and file transfers. This tutorial walks you through installation and your first connection.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Python 3.10+** (check with `python --version`)
|
||||||
|
- **uv** (recommended) or pip for package management
|
||||||
|
- A serial device, or nothing at all -- `loop://` lets you test without hardware
|
||||||
|
|
||||||
|
## Install mcserial
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="uvx (recommended)">
|
||||||
|
```bash
|
||||||
|
# Run directly -- no install step needed
|
||||||
|
uvx mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
`uvx` fetches and runs the latest version in an isolated environment. Nothing is installed globally.
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="pip">
|
||||||
|
```bash
|
||||||
|
pip install mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need CP2110 HID-to-UART support:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install mcserial[cp2110]
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Add to your MCP client
|
||||||
|
|
||||||
|
mcserial runs over stdio, so you register it with your MCP client and the client launches it on demand.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Claude Code">
|
||||||
|
```bash
|
||||||
|
claude mcp add mcserial -- uvx mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. Claude Code will start mcserial automatically when serial tools are needed.
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Claude Desktop">
|
||||||
|
Add this to your `claude_desktop_config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"mcserial": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcserial"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On macOS, the config file is at `~/Library/Application Support/Claude/claude_desktop_config.json`. On Windows, it's at `%APPDATA%\Claude\claude_desktop_config.json`.
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Other MCP clients">
|
||||||
|
Any MCP client that supports stdio transport can run mcserial. The command is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvx mcserial
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass this as the server command in your client's MCP configuration. Consult your client's documentation for the exact format.
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
You can tune defaults with environment variables before the server starts. Set `MCSERIAL_DEFAULT_BAUDRATE` (default: 9600), `MCSERIAL_DEFAULT_TIMEOUT` (default: 1.0s), or `MCSERIAL_MAX_CONNECTIONS` (default: 10). See the [Environment reference](/reference/environment/) for details.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Your first connection
|
||||||
|
|
||||||
|
The natural workflow is: **discover** available ports, **open** one, **read/write** data, then **close** when done.
|
||||||
|
|
||||||
|
This walkthrough uses `loop://` so you can follow along without any hardware. Everything written to a loopback port is immediately available to read back.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Discover available ports**
|
||||||
|
|
||||||
|
Call `list_serial_ports` to see what's connected:
|
||||||
|
|
||||||
|
```
|
||||||
|
list_serial_ports(usb_only=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"device": "/dev/ttyUSB0",
|
||||||
|
"description": "FT232R USB UART",
|
||||||
|
"hwid": "USB VID:PID=0403:6001",
|
||||||
|
"manufacturer": "FTDI",
|
||||||
|
"product": "FT232R USB UART",
|
||||||
|
"serial_number": "A50285BI",
|
||||||
|
"is_open": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `usb_only=False` to include built-in serial ports. Use `grep` to filter by hardware: `list_serial_ports(grep="FTDI")`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
If you have no hardware, skip this step and go straight to opening `loop://` in the next step.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
2. **Open a connection**
|
||||||
|
|
||||||
|
Open the loopback port (or a real device path from step 1):
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "loop://",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"bytesize": 8,
|
||||||
|
"parity": "N",
|
||||||
|
"stopbits": 1,
|
||||||
|
"xonxoff": false,
|
||||||
|
"rtscts": false,
|
||||||
|
"dsrdtr": false,
|
||||||
|
"resource_uri": "serial://loop:///data",
|
||||||
|
"url_scheme": "loop",
|
||||||
|
"hint": "Opened via URL handler. Some features (exclusive, auto-baud) are not available."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The port is now open in RS-232 mode with 8N1 framing (8 data bits, no parity, 1 stop bit) -- the most common configuration.
|
||||||
|
|
||||||
|
3. **Write data**
|
||||||
|
|
||||||
|
Send a string to the port:
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial(port="loop://", data="Hello, serial world!\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_written": 21,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Read data back**
|
||||||
|
|
||||||
|
Read whatever is waiting in the receive buffer:
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "Hello, serial world!\n",
|
||||||
|
"bytes_read": 21,
|
||||||
|
"raw_hex": "48656c6c6f2c2073657269616c20776f726c64210a",
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since this is a loopback, you get back exactly what you wrote. With a real device, you'd see the device's response instead.
|
||||||
|
|
||||||
|
5. **Close the connection**
|
||||||
|
|
||||||
|
Always close ports when you're done to release the resource:
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "loop://",
|
||||||
|
"message": "Port closed"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Reading tools at a glance
|
||||||
|
|
||||||
|
mcserial provides several ways to read data, each suited to different situations:
|
||||||
|
|
||||||
|
| Tool | When to use |
|
||||||
|
|------|-------------|
|
||||||
|
| `read_serial` | Read whatever bytes are available right now |
|
||||||
|
| `read_serial_line` | Read one line (up to `\n`) |
|
||||||
|
| `read_serial_lines` | Drain multi-line responses (up to `max_lines`) |
|
||||||
|
| `read_until` | Read until a custom terminator (e.g., `>` prompt) |
|
||||||
|
|
||||||
|
## Auto-baud detection
|
||||||
|
|
||||||
|
When opening a real device, you can omit `baudrate` and mcserial will attempt to detect it automatically:
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="/dev/ttyUSB0")
|
||||||
|
```
|
||||||
|
|
||||||
|
The response includes detection details:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"autobaud": {
|
||||||
|
"auto_detected": true,
|
||||||
|
"detection_confidence": 87.3,
|
||||||
|
"detection_candidates": [
|
||||||
|
{"baudrate": 115200, "score": 87.3},
|
||||||
|
{"baudrate": 9600, "score": 12.1}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Auto-detection works best when the device is actively sending data. You can also pass `autobaud_probe="UUUUU"` to send a sync string that triggers a response.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Auto-baud detection is only available for local device paths. URL-based ports (`loop://`, `socket://`, etc.) skip detection and fall back to the default baud rate.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## What's next
|
||||||
|
|
||||||
|
<CardGrid>
|
||||||
|
<Card title="Loopback testing" icon="rocket">
|
||||||
|
Practice read/write patterns, encodings, and flow control without hardware using [loop:// virtual ports](/tutorials/loopback-testing/).
|
||||||
|
</Card>
|
||||||
|
<Card title="RS-232 basics" icon="setting">
|
||||||
|
Learn modem line control, device resets, and break signals in the [RS-232 guide](/guides/rs232-basics/).
|
||||||
|
</Card>
|
||||||
|
<Card title="RS-485 and Modbus" icon="random">
|
||||||
|
Set up multi-drop bus communication and device scanning in the [RS-485 guide](/guides/rs485-modbus/).
|
||||||
|
</Card>
|
||||||
|
<Card title="Tool reference" icon="open-book">
|
||||||
|
Full parameter details for all 30 tools in the [reference section](/reference/tools-common/).
|
||||||
|
</Card>
|
||||||
|
</CardGrid>
|
||||||
378
docs/src/content/docs/tutorials/loopback-testing.mdx
Normal file
378
docs/src/content/docs/tutorials/loopback-testing.mdx
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
---
|
||||||
|
title: Loopback Testing
|
||||||
|
description: Test serial communication without any hardware using loop:// virtual ports.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
pyserial provides a built-in `loop://` URL scheme that creates a virtual serial port. Everything written to it is immediately available to read back -- no cables, no adapters, no hardware at all. This makes it ideal for testing tool workflows, validating encodings, and running automated checks in CI/CD.
|
||||||
|
|
||||||
|
## Why use loopback
|
||||||
|
|
||||||
|
- **No hardware required** -- test serial workflows on any machine, including headless CI runners
|
||||||
|
- **Deterministic** -- what you write is exactly what you read, making assertions straightforward
|
||||||
|
- **Fast** -- no real baud rate delays; data transfers are instantaneous in memory
|
||||||
|
- **Safe** -- no risk of misconfiguring or locking a real device
|
||||||
|
|
||||||
|
## Basic round-trip
|
||||||
|
|
||||||
|
The simplest test: write a string and read it back.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the loopback port**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "loop://",
|
||||||
|
"mode": "rs232",
|
||||||
|
"baudrate": 9600,
|
||||||
|
"bytesize": 8,
|
||||||
|
"parity": "N",
|
||||||
|
"stopbits": 1,
|
||||||
|
"resource_uri": "serial://loop:///data",
|
||||||
|
"url_scheme": "loop"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `baudrate` parameter is accepted but has no effect on loopback -- data moves at memory speed regardless of the value. Setting it still validates that the parameter is handled correctly throughout the tool chain.
|
||||||
|
|
||||||
|
2. **Write a message**
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial(port="loop://", data="AT+VERSION\r\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_written": 12,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Read it back**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "AT+VERSION\r\n",
|
||||||
|
"bytes_read": 12,
|
||||||
|
"raw_hex": "41542b56455253494f4e0d0a",
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `raw_hex` field confirms the exact bytes on the wire -- useful for verifying that line endings and control characters are correct.
|
||||||
|
|
||||||
|
4. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Testing line-oriented reads
|
||||||
|
|
||||||
|
Many serial devices send responses as lines terminated with `\r\n` or `\n`. Use `read_serial_line` and `read_serial_lines` to handle these.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open and write multi-line data**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=115200)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial(port="loop://", data="OK\r\nERROR 42\r\nDONE\r\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Read one line at a time**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial_line(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"line": "OK",
|
||||||
|
"bytes_read": 4,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `line` field strips trailing `\r\n` automatically. The `bytes_read` count includes the line ending bytes.
|
||||||
|
|
||||||
|
3. **Drain remaining lines in one call**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial_lines(port="loop://", max_lines=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"lines": ["ERROR 42", "DONE"],
|
||||||
|
"count": 2,
|
||||||
|
"bytes_read": 16,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`read_serial_lines` stops early when no more data is available, so setting `max_lines` higher than expected is fine.
|
||||||
|
|
||||||
|
4. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Testing custom terminators
|
||||||
|
|
||||||
|
Not every protocol uses newlines. Some devices use `>` as a prompt, `\x00` as a null terminator, or multi-byte sequences. Use `read_until` for these.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open and write prompt-terminated data**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial(port="loop://", data="ELM327 v1.5>")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Read until the `>` prompt**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_until(port="loop://", terminator=">")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "ELM327 v1.5>",
|
||||||
|
"bytes_read": 12,
|
||||||
|
"raw_hex": "454c4d33323720312e353e",
|
||||||
|
"port": "loop://",
|
||||||
|
"terminator_found": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `terminator_found` field confirms whether the terminator was actually received or whether the read timed out first.
|
||||||
|
|
||||||
|
3. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Testing encodings
|
||||||
|
|
||||||
|
Serial devices don't always speak UTF-8. Industrial equipment, legacy systems, and some sensors use Latin-1 or other single-byte encodings. mcserial's `encoding` parameter handles the translation.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Write with Latin-1 encoding**
|
||||||
|
|
||||||
|
Latin-1 maps bytes 0x00--0xFF directly, which makes it useful for binary-safe text:
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial(port="loop://", data="Temp: 23\xb0C", encoding="latin-1")
|
||||||
|
```
|
||||||
|
|
||||||
|
The `\xb0` byte is the degree symbol in Latin-1.
|
||||||
|
|
||||||
|
3. **Read with matching encoding**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial(port="loop://", encoding="latin-1")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "Temp: 23\u00b0C",
|
||||||
|
"bytes_read": 10,
|
||||||
|
"raw_hex": "54656d703a203233b043",
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `b0` byte in `raw_hex` confirms the degree symbol was sent correctly.
|
||||||
|
|
||||||
|
4. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
If you see garbled characters, the most common cause is a read/write encoding mismatch. Always use the same `encoding` value for both `write_serial` and `read_serial` on a given port.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Testing raw byte writes
|
||||||
|
|
||||||
|
For binary protocols (Modbus RTU, custom framing, firmware uploads), use `write_serial_bytes` to send exact byte sequences:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Write raw bytes**
|
||||||
|
|
||||||
|
Send a Modbus-style query (address 0x01, function 0x03, register 0x0000, count 0x0001):
|
||||||
|
|
||||||
|
```
|
||||||
|
write_serial_bytes(port="loop://", data=[1, 3, 0, 0, 0, 1])
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"bytes_written": 6,
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Read back and verify hex**
|
||||||
|
|
||||||
|
```
|
||||||
|
read_serial(port="loop://", encoding="latin-1")
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": "\u0001\u0003\u0000\u0000\u0000\u0001",
|
||||||
|
"bytes_read": 6,
|
||||||
|
"raw_hex": "010300000001",
|
||||||
|
"port": "loop://"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `raw_hex` field is the most reliable way to verify binary data -- it shows the exact bytes without encoding ambiguity.
|
||||||
|
|
||||||
|
4. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Testing port configuration changes
|
||||||
|
|
||||||
|
You can reconfigure an open port without closing and reopening it. This is useful for testing parameter changes or simulating baud rate switching:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **Open at 9600 baud**
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="loop://", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Change to 115200 baud with hardware flow control**
|
||||||
|
|
||||||
|
```
|
||||||
|
configure_serial(port="loop://", baudrate=115200, rtscts=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"port": "loop://",
|
||||||
|
"baudrate": 115200,
|
||||||
|
"timeout": 1.0,
|
||||||
|
"write_timeout": null,
|
||||||
|
"inter_byte_timeout": null,
|
||||||
|
"xonxoff": false,
|
||||||
|
"rtscts": true,
|
||||||
|
"dsrdtr": false,
|
||||||
|
"rts": true,
|
||||||
|
"dtr": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The response shows the full port state after the change, making it easy to confirm the new settings.
|
||||||
|
|
||||||
|
3. **Verify the connection status**
|
||||||
|
|
||||||
|
```
|
||||||
|
get_connection_status()
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns all open connections with their current settings, including the updated baud rate and flow control flags.
|
||||||
|
|
||||||
|
4. **Close the port**
|
||||||
|
|
||||||
|
```
|
||||||
|
close_serial_port(port="loop://")
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Testing buffer management
|
||||||
|
|
||||||
|
When building protocol handlers, you sometimes need to clear stale data before sending a new command. Use `flush_serial` to reset the buffers:
|
||||||
|
|
||||||
|
```
|
||||||
|
flush_serial(port="loop://", input_buffer=true, output_buffer=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
This discards any unread data in the input buffer and any unsent data in the output buffer. Call it before sending a command when you want a clean slate.
|
||||||
|
|
||||||
|
<Aside type="tip" title="Debugging real connections with spy://">
|
||||||
|
When you move from loopback testing to real hardware and things don't work, wrap your device path with `spy://` to log all traffic to stderr:
|
||||||
|
|
||||||
|
```
|
||||||
|
open_serial_port(port="spy:///dev/ttyUSB0", baudrate=9600)
|
||||||
|
```
|
||||||
|
|
||||||
|
Every byte sent and received will be printed to the server's stderr output, which shows up in your MCP client's server logs. This is invaluable for diagnosing timing issues, encoding mismatches, or unexpected device responses.
|
||||||
|
|
||||||
|
`spy://` works with any local device path -- just prefix the full path after the scheme. On Windows: `spy://COM3`.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Using loopback in CI/CD
|
||||||
|
|
||||||
|
Since `loop://` requires no hardware or special permissions, it works in any CI environment. A typical test pattern:
|
||||||
|
|
||||||
|
1. Start mcserial as a subprocess
|
||||||
|
2. Connect via MCP client
|
||||||
|
3. Open `loop://`
|
||||||
|
4. Write known data, read it back, assert equality
|
||||||
|
5. Test edge cases: empty writes, large payloads, encoding boundaries
|
||||||
|
6. Close the port
|
||||||
|
|
||||||
|
This validates that your MCP tool chain works end-to-end without needing a physical device in the test runner.
|
||||||
30
docs/src/styles/custom.css
Normal file
30
docs/src/styles/custom.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* mcserial docs — teal accent theme */
|
||||||
|
:root {
|
||||||
|
--sl-color-accent-low: #042f2e;
|
||||||
|
--sl-color-accent: #0d9488;
|
||||||
|
--sl-color-accent-high: #99f6e4;
|
||||||
|
|
||||||
|
--sl-color-white: #f0fdfa;
|
||||||
|
--sl-color-gray-1: #ccfbf1;
|
||||||
|
--sl-color-gray-2: #5eead4;
|
||||||
|
--sl-color-gray-3: #2dd4bf;
|
||||||
|
--sl-color-gray-4: #14b8a6;
|
||||||
|
--sl-color-gray-5: #0f766e;
|
||||||
|
--sl-color-gray-6: #115e59;
|
||||||
|
--sl-color-gray-7: #134e4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='light'] {
|
||||||
|
--sl-color-accent-low: #ccfbf1;
|
||||||
|
--sl-color-accent: #0d9488;
|
||||||
|
--sl-color-accent-high: #042f2e;
|
||||||
|
|
||||||
|
--sl-color-white: #134e4a;
|
||||||
|
--sl-color-gray-1: #115e59;
|
||||||
|
--sl-color-gray-2: #0f766e;
|
||||||
|
--sl-color-gray-3: #14b8a6;
|
||||||
|
--sl-color-gray-4: #2dd4bf;
|
||||||
|
--sl-color-gray-5: #99f6e4;
|
||||||
|
--sl-color-gray-6: #ccfbf1;
|
||||||
|
--sl-color-gray-7: #f0fdfa;
|
||||||
|
}
|
||||||
5
docs/tsconfig.json
Normal file
5
docs/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user