---
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="note">
Examples use tool-call notation: `tool_name(param=value)`. These are MCP tool calls made by the assistant, not code you write directly. Boolean values use JSON convention (`true`/`false`).
</Aside>

<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:

```
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>

```
# 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 |

```
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

```
# 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>

```
# 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>
