From b2496b17b0497df764d53ca09a270856efe9c4df Mon Sep 17 00:00:00 2001 From: MarekWo Date: Thu, 15 Jan 2026 15:18:07 +0100 Subject: [PATCH] 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 --- .env.example | 9 +++--- README.md | 8 +++-- docker-compose.yml | 11 ++++--- meshcore-bridge/bridge.py | 62 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 2eed2db..186cb2a 100644 --- a/.env.example +++ b/.env.example @@ -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) diff --git a/README.md b/README.md index d4e5cf4..e7127b6 100644 --- a/README.md +++ b/README.md @@ -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/` + - `MC_SERIAL_PORT=auto` (recommended) or `/dev/serial/by-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 diff --git a/docker-compose.yml b/docker-compose.yml index 80a18e6..de6b4d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/meshcore-bridge/bridge.py b/meshcore-bridge/bridge.py index 1b4ca85..389cc36 100644 --- a/meshcore-bridge/bridge.py +++ b/meshcore-bridge/bridge.py @@ -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