feat: Auto-detect serial port from /dev/serial/by-id/

When MC_SERIAL_PORT=auto, bridge scans /dev/serial/by-id/ and:
- Uses the device if exactly one found
- Fails with helpful message if multiple devices (list provided)
- Fails if no devices found

docker-compose.yml now uses device_cgroup_rules instead of explicit
device mapping, allowing auto-detection inside the container.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-01-15 15:18:07 +01:00
parent b8a77285ab
commit b2496b17b0
4 changed files with 79 additions and 11 deletions

View File

@@ -5,10 +5,11 @@
# MeshCore Device Configuration
# ============================================
# Serial port path (use /dev/serial/by-id for stable device names)
# Find your device: ls /dev/serial/by-id/
# Example: usb-Espressif_Systems_heltec_wifi_lora_32_v4__16_MB_FLASH__2_MB_PSRAM__90706984A000-if00
MC_SERIAL_PORT=/dev/serial/by-id/[YOUR_DEVICE_ID]
# Serial port path
# Use "auto" for automatic detection (recommended if only one USB device)
# Or specify manually: /dev/serial/by-id/usb-xxx or /dev/ttyUSB0
# Find available devices: ls /dev/serial/by-id/
MC_SERIAL_PORT=auto
# Your MeshCore device name (used for .msgs file)
# Use "auto" for automatic detection from meshcli (recommended)

View File

@@ -68,11 +68,15 @@ For detailed feature documentation, see the [User Guide](docs/user-guide.md).
```
**Required changes in .env:**
- `MC_SERIAL_PORT=/dev/serial/by-id/<your-device-id>`
- `MC_SERIAL_PORT=auto` (recommended) or `/dev/serial/by-id/<your-device-id>`
- `MC_DEVICE_NAME=auto` (recommended) or your device name
- `TZ=Europe/Warsaw` (optional, set your timezone)
**Note:** With `MC_DEVICE_NAME=auto`, the system automatically detects your device name from meshcli. This is the recommended setting as it ensures the `.msgs` file always matches your actual device name.
**Note:** With `auto` settings, the system automatically detects:
- **Serial port** from `/dev/serial/by-id/` (works if only one USB device connected)
- **Device name** from meshcli prompt (ensures `.msgs` file matches actual device)
If you have multiple USB serial devices, run `ls /dev/serial/by-id/` and specify `MC_SERIAL_PORT` explicitly.
**Leave these as default:**
```bash

View File

@@ -6,14 +6,17 @@ services:
dockerfile: Dockerfile
container_name: meshcore-bridge
restart: unless-stopped
devices:
- "${MC_SERIAL_PORT}:${MC_SERIAL_PORT}"
# Grant access to all ttyUSB devices (major 188) for auto-detection
# This allows MC_SERIAL_PORT=auto to work without specifying device upfront
device_cgroup_rules:
- 'c 188:* rmw'
volumes:
- "${MC_CONFIG_DIR}:/root/.config/meshcore:rw"
- "/dev:/dev"
environment:
- MC_SERIAL_PORT=${MC_SERIAL_PORT}
- MC_SERIAL_PORT=${MC_SERIAL_PORT:-auto}
- MC_CONFIG_DIR=/root/.config/meshcore
- MC_DEVICE_NAME=${MC_DEVICE_NAME}
- MC_DEVICE_NAME=${MC_DEVICE_NAME:-auto}
- TZ=${TZ:-UTC}
networks:
- meshcore-net

View File

@@ -36,12 +36,71 @@ app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='gevent')
# Configuration
MC_SERIAL_PORT = os.getenv('MC_SERIAL_PORT', '/dev/ttyUSB0')
MC_CONFIG_DIR = os.getenv('MC_CONFIG_DIR', '/config')
MC_DEVICE_NAME = os.getenv('MC_DEVICE_NAME', 'meshtastic')
DEFAULT_TIMEOUT = 10
RECV_TIMEOUT = 60
# Serial port detection
SERIAL_BY_ID_PATH = Path('/dev/serial/by-id')
SERIAL_PORT_SOURCE = "config" # Will be updated by detect_serial_port()
def detect_serial_port() -> str:
"""
Auto-detect serial port from /dev/serial/by-id/.
Returns:
Path to detected serial device
Raises:
RuntimeError: If no device found or multiple devices without explicit selection
"""
global SERIAL_PORT_SOURCE
env_port = os.getenv('MC_SERIAL_PORT', 'auto')
# If explicit port specified (not "auto"), use it directly
if env_port and env_port.lower() != 'auto':
logger.info(f"Using configured serial port: {env_port}")
SERIAL_PORT_SOURCE = "config"
return env_port
# Auto-detect from /dev/serial/by-id/
logger.info("Auto-detecting serial port...")
if not SERIAL_BY_ID_PATH.exists():
raise RuntimeError(
"No serial devices found: /dev/serial/by-id/ does not exist. "
"Make sure a MeshCore device is connected via USB."
)
devices = list(SERIAL_BY_ID_PATH.iterdir())
if len(devices) == 0:
raise RuntimeError(
"No serial devices found in /dev/serial/by-id/. "
"Make sure a MeshCore device is connected via USB."
)
if len(devices) == 1:
device_path = str(devices[0])
logger.info(f"Auto-detected serial port: {device_path}")
SERIAL_PORT_SOURCE = "detected"
return device_path
# Multiple devices found - list them and fail
device_list = '\n - '.join(str(d.name) for d in devices)
raise RuntimeError(
f"Multiple serial devices found. Please specify MC_SERIAL_PORT in .env:\n"
f" - {device_list}\n\n"
f"Example: MC_SERIAL_PORT=/dev/serial/by-id/{devices[0].name}"
)
# Detect serial port at startup
MC_SERIAL_PORT = detect_serial_port()
class MeshCLISession:
"""
Manages a persistent meshcli subprocess session.
@@ -608,6 +667,7 @@ def health():
return jsonify({
'status': session_status,
'serial_port': MC_SERIAL_PORT,
'serial_port_source': SERIAL_PORT_SOURCE,
'advert_log': str(meshcli_session.advert_log_path) if meshcli_session else None,
'device_name': device_name,
'device_name_source': name_source