Add smart port conflict detection and comprehensive documentation update

## New Features:
- Smart port conflict detection with process identification
- Enhanced serial_connect with automatic conflict prevention
- New serial_check_port tool for diagnosing port issues
- Professional error messages with specific solutions

## Enhancements:
- Updated README with complete 70+ tool inventory
- Added MCP resources documentation (8 resources)
- Enhanced WireViz templates (fixed YAML bugs)
- Comprehensive testing and validation

## Technical:
- New PortConflictDetector class with lsof/fuser integration
- Process-specific conflict resolution suggestions
- Graceful fallbacks when detection tools unavailable
- Memory-safe circular buffer architecture highlighted

## Documentation:
- Updated badge: 60+ → 70+ tools
- Complete tool reference with categories
- Smart features section added
- Professional positioning and capabilities showcase
This commit is contained in:
Ryan Malloy 2025-09-27 20:50:58 -06:00
parent 101e3bb0a8
commit 4ad3b87ca8
5 changed files with 539 additions and 9 deletions

165
README.md
View File

@ -6,7 +6,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![PyPI version](https://img.shields.io/pypi/v/mcp-arduino.svg)](https://pypi.org/project/mcp-arduino/) [![PyPI version](https://img.shields.io/pypi/v/mcp-arduino.svg)](https://pypi.org/project/mcp-arduino/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Tools: 60+](https://img.shields.io/badge/tools-60+-brightgreen.svg)](https://git.supported.systems/MCP/mcp-arduino) [![Tools: 70+](https://img.shields.io/badge/tools-70+-brightgreen.svg)](https://git.supported.systems/MCP/mcp-arduino)
**The Arduino development server that speaks your language.** **The Arduino development server that speaks your language.**
@ -235,6 +235,21 @@ Claude: → serial_read with cursor navigation
✓ Memory usage: still just 10MB (fixed size) ✓ Memory usage: still just 10MB (fixed size)
``` ```
### 🆕 **Smart Port Conflict Detection**
No more mysterious "device busy" errors - know exactly what's using your ports:
```
You: "Connect to my Arduino"
Claude: → serial_check_port /dev/ttyUSB0
❌ Port conflict detected!
🔧 Arduino IDE (PID 12345) is using this port
💡 Solution: Close Arduino IDE Serial Monitor
💡 Or use: kill 12345
→ serial_connect with automatic conflict prevention
✓ Connected successfully after conflict resolved
```
### 🔍 **Auto-Detect Everything** ### 🔍 **Auto-Detect Everything**
No more guessing board types or ports: No more guessing board types or ports:
``` ```
@ -273,14 +288,158 @@ Claude: → arduino_debug_start
## 📦 What You Get ## 📦 What You Get
**60+ Tools** organized into logical groups: **70+ Professional Tools** organized into logical groups:
- **Sketch Operations**: Create, read, write, compile, upload - **Sketch Operations**: Create, read, write, compile, upload
- **Library Management**: Search, install, dependency resolution - **Library Management**: Search, install, dependency resolution
- **Board Management**: Detection, configuration, core installation - **Board Management**: Detection, configuration, core installation
- **Serial Monitoring**: Memory-safe buffering, cursor pagination - **Serial Monitoring**: Memory-safe buffering, cursor pagination, conflict detection
- **Debugging**: GDB integration, breakpoints, memory inspection - **Debugging**: GDB integration, breakpoints, memory inspection
- **Project Templates**: WiFi, sensor, serial, blink patterns - **Project Templates**: WiFi, sensor, serial, blink patterns
- **Circuit Diagrams**: Generate wiring diagrams from descriptions - **Circuit Diagrams**: Generate wiring diagrams from descriptions
- **Client Debugging**: MCP capability detection and troubleshooting
---
## 🔧 Complete Tool & Resource Reference
### 📝 **Arduino Sketch Management (8 tools)**
- `arduino_create_sketch` - Create new sketch with boilerplate code
- `arduino_list_sketches` - List all Arduino sketches
- `arduino_read_sketch` - Read sketch file contents
- `arduino_write_sketch` - Write/update sketch files
- `arduino_compile_sketch` - Compile sketch without uploading
- `arduino_upload_sketch` - Compile and upload to board
- `arduino_sketch_new` - Create sketch from templates (blink, sensor, wifi, etc.)
- `arduino_sketch_archive` - Create archive of sketch for sharing
### 📚 **Library Management (12 tools)**
- `arduino_search_libraries` - Search Arduino library index
- `arduino_install_library` - Install library from index
- `arduino_uninstall_library` - Remove installed library
- `arduino_list_library_examples` - List examples from library
- `arduino_lib_deps` - Check library dependencies and compatibility
- `arduino_lib_download` - Download libraries without installing
- `arduino_lib_install_missing` - Install all missing dependencies automatically
- `arduino_lib_examples` - List examples from installed libraries
- `arduino_lib_list` - List installed libraries with version info
- `arduino_lib_upgrade` - Upgrade libraries to latest versions
- `arduino_update_index` - Update libraries and boards index
- `arduino_outdated` - List outdated libraries and cores
### 🔧 **Board Management (11 tools)**
- `arduino_list_boards` - List connected Arduino boards
- `arduino_search_boards` - Search board definitions
- `arduino_install_core` - Install board support packages
- `arduino_list_cores` - List installed cores
- `arduino_update_cores` - Update all cores to latest
- `arduino_install_esp32` - Install ESP32 board support with proper config
- `arduino_board_attach` - Attach board to sketch persistently
- `arduino_board_details` - Get detailed board information and properties
- `arduino_board_identify` - Auto-detect board type from connected port
- `arduino_board_listall` - List all available boards from installed cores
- `arduino_board_search_online` - Search for boards in online index
### 🐛 **Debug Tools (15 tools)**
- `arduino_debug_start` - Start GDB debug session
- `arduino_debug_stop` - Stop debug session and cleanup
- `arduino_debug_interactive` - Interactive debugging with AI assistance
- `arduino_debug_break` - Set breakpoints in code
- `arduino_debug_run` - Run/continue/step execution
- `arduino_debug_print` - Print variable values/expressions
- `arduino_debug_backtrace` - Show call stack
- `arduino_debug_watch` - Monitor variable changes
- `arduino_debug_memory` - Examine memory contents at addresses
- `arduino_debug_registers` - Show CPU register values
- `arduino_debug_list_breakpoints` - List all active breakpoints
- `arduino_debug_delete_breakpoint` - Delete specific or all breakpoints
- `arduino_debug_enable_breakpoint` - Enable/disable breakpoints
- `arduino_debug_condition_breakpoint` - Add conditional breakpoints
- `arduino_debug_save_breakpoints` - Save breakpoints to file
- `arduino_debug_restore_breakpoints` - Restore saved breakpoints
### 📡 **Serial Monitor Tools (15 tools) - 🆕 Enhanced with Smart Conflict Detection**
- `serial_check_port` - **🆕 Check port availability and detect conflicts**
- `serial_connect` - **🆕 Enhanced connection with automatic conflict detection**
- `serial_disconnect` - Disconnect from serial port
- `serial_send` - Send data/commands to serial port
- `serial_read` - Read data with cursor-based pagination
- `serial_list_ports` - List available serial ports (with Arduino detection)
- `serial_clear_buffer` - Clear buffered serial data
- `serial_reset_board` - Reset Arduino board (DTR/RTS/1200bps methods)
- `serial_monitor_state` - Get current monitor state and statistics
- `serial_cursor_info` - Get detailed cursor information
- `serial_list_cursors` - List all active cursors
- `serial_delete_cursor` - Delete specific cursor
- `serial_cleanup_cursors` - Remove invalid/stale cursors
- `serial_buffer_stats` - Get detailed circular buffer statistics
- `serial_resize_buffer` - Resize circular buffer (100-1M entries)
### 🎨 **WireViz Circuit Diagrams (2 tools)**
- `wireviz_generate_from_yaml` - Create circuit diagram from YAML definition
- `wireviz_generate_from_description` - **AI-powered circuit generation from natural language**
### ⚙️ **Advanced System Tools (13 tools)**
- `arduino_compile_advanced` - Advanced compilation with custom build properties
- `arduino_size_analysis` - Analyze binary size and memory usage breakdown
- `arduino_export_compiled_binary` - Export compiled binaries to custom location
- `arduino_cache_clean` - Clean Arduino build cache
- `arduino_build_show_properties` - Show all build properties for board
- `arduino_burn_bootloader` - Burn bootloader using external programmer
- `arduino_config_init` - Initialize Arduino CLI configuration
- `arduino_config_get` - Get specific configuration values
- `arduino_config_set` - Set configuration values
- `arduino_config_dump` - Dump entire Arduino CLI configuration
- `arduino_monitor_advanced` - Use Arduino CLI's built-in advanced serial monitor
- `arduino_show_directories` - Show current directory configuration and MCP roots
- `arduino_sketch_archive` - Create distributable archives of sketches
### 🔍 **Client Debug Tools (5 tools)**
- `client_debug_info` - Show comprehensive MCP client debug information
- `client_capability_check` - Test specific client capabilities (sampling, etc.)
- `client_declared_capabilities` - Show what capabilities client declares
- `client_test_sampling` - Test client sampling functionality with simple prompt
- `client_fix_capabilities` - Suggest fixes for common capability issues
---
## 📋 **MCP Resources (8 resources)**
Real-time information resources that Claude can access to understand your current Arduino environment:
### 🚀 **Development Resources**
- **`arduino://sketches`** - List all Arduino sketches with sizes and paths
- **`arduino://libraries`** - List all installed Arduino libraries with versions
- **`arduino://boards`** - List all connected Arduino boards with details
### 🔧 **Active Session Monitoring**
- **`arduino://debug/sessions`** - List all active debug sessions with status
- **`arduino://serial/state`** - Get current serial monitor state and connections
### 📚 **Documentation & Configuration**
- **`wireviz://instructions`** - WireViz usage instructions and circuit examples
- **`server://info`** - Complete server configuration and capabilities overview
- **`arduino://roots`** - Current MCP roots configuration and directory setup
---
## 🎯 **Smart Features**
### 🆕 **Intelligent Port Conflict Detection**
- **Process Identification**: Recognizes Arduino IDE, PlatformIO, terminal programs
- **Specific Solutions**: Tailored advice for each type of conflicting application
- **Command Suggestions**: Provides exact kill commands and troubleshooting steps
- **Graceful Fallbacks**: Works even when detection tools are unavailable
### 🧠 **Memory-Safe Architecture**
- **Circular Buffer**: Fixed 10MB memory usage regardless of runtime duration
- **Cursor Pagination**: Navigate through millions of serial entries efficiently
- **Auto-Cleanup**: Removes stale cursors and manages buffer health
### 🔗 **MCP Roots Integration**
- **Automatic Detection**: Uses client-provided project directories
- **Smart Selection**: Prefers Arduino-specific directories
- **Environment Override**: `MCP_SKETCH_DIR` for custom locations
- **Multi-Client Support**: Works across different MCP clients
## 🎓 Perfect For ## 🎓 Perfect For

View File

@ -6,6 +6,7 @@ from .arduino_sketch import ArduinoSketch
from .circular_buffer import CircularSerialBuffer from .circular_buffer import CircularSerialBuffer
from .client_capabilities import ClientCapabilitiesInfo from .client_capabilities import ClientCapabilitiesInfo
from .client_debug import ClientDebugInfo from .client_debug import ClientDebugInfo
from .port_checker import PortConflictDetector, port_checker
from .serial_manager import SerialConnectionManager from .serial_manager import SerialConnectionManager
from .wireviz import WireViz from .wireviz import WireViz
@ -19,4 +20,6 @@ __all__ = [
"ClientCapabilitiesInfo", "ClientCapabilitiesInfo",
"SerialConnectionManager", "SerialConnectionManager",
"CircularSerialBuffer", "CircularSerialBuffer",
"PortConflictDetector",
"port_checker",
] ]

View File

@ -12,6 +12,7 @@ from pydantic import Field
from .circular_buffer import CircularSerialBuffer, SerialDataType from .circular_buffer import CircularSerialBuffer, SerialDataType
from .serial_manager import SerialConnectionManager from .serial_manager import SerialConnectionManager
from .port_checker import port_checker
# Use CircularSerialBuffer directly # Use CircularSerialBuffer directly
SerialDataBuffer = CircularSerialBuffer SerialDataBuffer = CircularSerialBuffer
@ -97,6 +98,49 @@ class ArduinoSerial(MCPMixin):
{json.dumps(state['connections'], indent=2)} {json.dumps(state['connections'], indent=2)}
""" """
@mcp_tool(name="serial_check_port", description="Check if a serial port is available or detect conflicts")
async def check_port(
self,
port: str = Field(..., description="Serial port path to check (e.g., /dev/ttyUSB0)"),
ctx: Context = None
) -> dict:
"""Check serial port availability and detect conflicts"""
try:
usage = await port_checker.check_port_usage(port)
if usage is None:
return {
"success": True,
"available": True,
"port": port,
"message": f"✅ Port {port} is available for connection"
}
else:
conflict_message = port_checker.generate_conflict_message(port, usage)
return {
"success": True,
"available": False,
"port": port,
"conflicts": [
{
"pid": proc.pid,
"process_name": proc.process_name,
"command": proc.command,
"user": proc.user,
"description": str(proc)
}
for proc in usage
],
"message": conflict_message,
"suggestions": "Use the provided commands to resolve conflicts before connecting"
}
except Exception as e:
return {
"success": False,
"error": f"Error checking port {port}: {str(e)}",
"port": port
}
@mcp_tool(name="serial_connect", description="Connect to a serial port for monitoring") @mcp_tool(name="serial_connect", description="Connect to a serial port for monitoring")
async def connect( async def connect(
self, self,
@ -110,12 +154,43 @@ class ArduinoSerial(MCPMixin):
dsrdtr: bool = Field(False, description="Enable hardware (DSR/DTR) flow control"), dsrdtr: bool = Field(False, description="Enable hardware (DSR/DTR) flow control"),
auto_monitor: bool = Field(True, description="Start monitoring automatically"), auto_monitor: bool = Field(True, description="Start monitoring automatically"),
exclusive: bool = Field(False, description="Disconnect other ports first"), exclusive: bool = Field(False, description="Disconnect other ports first"),
force: bool = Field(False, description="Skip port conflict check and attempt connection anyway"),
ctx: Context = None ctx: Context = None
) -> dict: ) -> dict:
"""Connect to a serial port""" """Connect to a serial port with smart conflict detection"""
if not self._initialized: if not self._initialized:
await self.initialize() await self.initialize()
# Smart port conflict detection (unless forced)
if not force:
try:
usage = await port_checker.check_port_usage(port)
if usage:
conflict_message = port_checker.generate_conflict_message(port, usage)
self.data_buffer.add_entry(port, "Connection blocked: Port in use", SerialDataType.ERROR)
return {
"success": False,
"error": "Port conflict detected",
"port": port,
"conflicts": [
{
"pid": proc.pid,
"process_name": proc.process_name,
"command": proc.command,
"user": proc.user,
"description": str(proc)
}
for proc in usage
],
"message": conflict_message,
"hint": "Use 'serial_check_port' for detailed conflict info, or set 'force=true' to override"
}
except Exception as e:
# If conflict detection fails, log warning but continue
import logging
logger = logging.getLogger(__name__)
logger.warning(f"Port conflict check failed for {port}: {e}")
try: try:
conn = await self.connection_manager.connect( conn = await self.connection_manager.connect(
port=port, port=port,
@ -146,11 +221,24 @@ class ArduinoSerial(MCPMixin):
"rtscts": rtscts, "rtscts": rtscts,
"dsrdtr": dsrdtr "dsrdtr": dsrdtr
}, },
"state": conn.state.value "state": conn.state.value,
"message": f"✅ Successfully connected to {port}"
} }
except Exception as e: except Exception as e:
self.data_buffer.add_entry(port, str(e), SerialDataType.ERROR) # Enhanced error message with suggestions
return {"success": False, "error": str(e)} error_msg = str(e)
if "permission denied" in error_msg.lower():
error_msg += "\n💡 Try: sudo usermod -a -G dialout $USER (then logout/login)"
elif "device or resource busy" in error_msg.lower() or "multiple access" in error_msg.lower():
error_msg += f"\n💡 Another process is using {port}. Use 'serial_check_port' to identify it."
self.data_buffer.add_entry(port, error_msg, SerialDataType.ERROR)
return {
"success": False,
"error": error_msg,
"port": port,
"hint": f"Use 'serial_check_port {port}' to diagnose connection issues"
}
@mcp_tool(name="serial_disconnect", description="Disconnect from a serial port") @mcp_tool(name="serial_disconnect", description="Disconnect from a serial port")
async def disconnect( async def disconnect(

View File

@ -0,0 +1,282 @@
"""
Smart Port Conflict Detection for Arduino MCP Server
Proactively detects port usage conflicts and provides helpful guidance
"""
import os
import subprocess
import logging
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@dataclass
class PortUsage:
"""Information about a process using a port"""
pid: int
command: str
user: str
fd_type: str
process_name: str
def __str__(self) -> str:
return f"{self.process_name} (PID {self.pid}) by {self.user}"
class PortConflictDetector:
"""Detects and reports serial port usage conflicts"""
def __init__(self):
self.common_arduino_processes = {
'arduino': 'Arduino IDE',
'arduino-cli': 'Arduino CLI',
'platformio': 'PlatformIO',
'pio': 'PlatformIO CLI',
'esptool': 'ESP Flash Tool',
'avrdude': 'AVR Programming Tool',
'python': 'Python Script (possibly MCP server)',
'minicom': 'Minicom Terminal',
'screen': 'GNU Screen',
'picocom': 'Picocom Terminal',
'cu': 'Unix Terminal',
'putty': 'PuTTY',
'tio': 'TIO Terminal'
}
async def check_port_usage(self, port: str) -> Optional[List[PortUsage]]:
"""
Check if a serial port is being used by another process
Returns list of processes using the port, or None if available
"""
try:
# Try lsof first (most detailed info)
usage = await self._check_with_lsof(port)
if usage:
return usage
# Fallback to fuser if lsof fails
usage = await self._check_with_fuser(port)
if usage:
return usage
# Try direct file access test
if await self._test_direct_access(port):
# Port is busy but we can't identify the process
return [PortUsage(
pid=0,
command="unknown",
user="unknown",
fd_type="unknown",
process_name="Unknown Process"
)]
return None # Port is available
except Exception as e:
logger.warning(f"Error checking port usage for {port}: {e}")
return None
async def _check_with_lsof(self, port: str) -> Optional[List[PortUsage]]:
"""Check port usage using lsof command"""
try:
result = subprocess.run(
['lsof', port],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0 and result.stdout:
return self._parse_lsof_output(result.stdout)
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError):
pass
return None
async def _check_with_fuser(self, port: str) -> Optional[List[PortUsage]]:
"""Check port usage using fuser command"""
try:
result = subprocess.run(
['fuser', port],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0 and result.stdout.strip():
pids = [int(pid.strip()) for pid in result.stdout.split() if pid.strip().isdigit()]
return [await self._get_process_info(pid) for pid in pids]
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError, ValueError):
pass
return None
async def _test_direct_access(self, port: str) -> bool:
"""Test if port is accessible by trying to open it briefly"""
try:
import serial
# Very brief test - just try to open and immediately close
with serial.Serial(port, timeout=0.1) as ser:
pass
return False # Successfully opened, so available
except (serial.SerialException, PermissionError, FileNotFoundError):
# If we can't open it, something else might be using it
# But could also be permissions or non-existent port
if Path(port).exists():
return True # Port exists but can't open - likely in use
return False # Port doesn't exist
def _parse_lsof_output(self, output: str) -> List[PortUsage]:
"""Parse lsof output to extract process information"""
processes = []
lines = output.strip().split('\n')[1:] # Skip header
for line in lines:
parts = line.split(None, 8) # Split into max 9 parts
if len(parts) >= 8:
try:
processes.append(PortUsage(
pid=int(parts[1]),
command=parts[0],
user=parts[2],
fd_type=parts[4],
process_name=parts[0]
))
except ValueError:
continue
return processes
async def _get_process_info(self, pid: int) -> PortUsage:
"""Get detailed process information for a PID"""
try:
# Get process name and command from /proc/pid/comm and /proc/pid/cmdline
comm_path = f"/proc/{pid}/comm"
cmdline_path = f"/proc/{pid}/cmdline"
process_name = "unknown"
command = "unknown"
if Path(comm_path).exists():
with open(comm_path, 'r') as f:
process_name = f.read().strip()
if Path(cmdline_path).exists():
with open(cmdline_path, 'r') as f:
cmdline = f.read().replace('\0', ' ').strip()
command = cmdline if cmdline else process_name
# Get user from process status
user = "unknown"
try:
result = subprocess.run(['ps', '-p', str(pid), '-o', 'user='],
capture_output=True, text=True, timeout=2)
if result.returncode == 0:
user = result.stdout.strip()
except:
pass
return PortUsage(
pid=pid,
command=command,
user=user,
fd_type="chr",
process_name=process_name
)
except Exception:
return PortUsage(
pid=pid,
command="unknown",
user="unknown",
fd_type="unknown",
process_name="unknown"
)
def generate_conflict_message(self, port: str, usage: List[PortUsage]) -> str:
"""Generate a helpful error message for port conflicts"""
if not usage:
return f"Port {port} is available"
message_parts = [f"❌ **Port {port} is currently in use:**\n"]
for proc in usage:
# Try to identify the type of application
app_type = self._identify_application_type(proc.process_name.lower())
if proc.pid > 0:
message_parts.append(f" • **{app_type}** - {proc} (FD: {proc.fd_type})")
else:
message_parts.append(f" • **{app_type}** - Process details unavailable")
message_parts.extend([
"\n**🔧 Suggested Solutions:**",
self._get_solutions_for_processes(usage),
"\n**💡 Quick Commands:**"
])
# Add specific commands based on detected processes
commands = self._get_suggested_commands(port, usage)
message_parts.extend(commands)
return "\n".join(message_parts)
def _identify_application_type(self, process_name: str) -> str:
"""Identify the type of application using the port"""
for pattern, app_name in self.common_arduino_processes.items():
if pattern in process_name:
return app_name
return "Unknown Application"
def _get_solutions_for_processes(self, usage: List[PortUsage]) -> str:
"""Generate process-specific solutions"""
solutions = []
for proc in usage:
process_lower = proc.process_name.lower()
if 'arduino' in process_lower:
solutions.append(" 1. Close Arduino IDE serial monitor (Tools → Serial Monitor)")
elif 'platformio' in process_lower:
solutions.append(" 1. Stop PlatformIO monitor: `pio device monitor --exit`")
elif 'python' in process_lower:
solutions.append(" 1. Stop the Python script/MCP server using the port")
elif any(term in process_lower for term in ['minicom', 'screen', 'picocom', 'cu']):
solutions.append(f" 1. Exit the terminal program: {proc.process_name}")
elif 'esptool' in process_lower or 'avrdude' in process_lower:
solutions.append(" 1. Wait for programming/flashing to complete")
else:
solutions.append(f" 1. Stop or close {proc.process_name}")
if not solutions:
solutions.append(" 1. Identify and close the application using the port")
solutions.append(" 2. Unplug and reconnect the Arduino device")
solutions.append(" 3. Try a different USB port")
return "\n".join(solutions)
def _get_suggested_commands(self, port: str, usage: List[PortUsage]) -> List[str]:
"""Generate helpful commands based on detected processes"""
commands = []
valid_pids = [proc.pid for proc in usage if proc.pid > 0]
if valid_pids:
commands.append(f" • **Kill process:** `kill {' '.join(map(str, valid_pids))}`")
commands.append(f" • **Force kill:** `kill -9 {' '.join(map(str, valid_pids))}`")
commands.extend([
f" • **Check port:** `lsof {port}`",
f" • **List all processes:** `fuser {port}`",
" • **List Arduino ports:** Use `serial_list_ports` with `arduino_only=true`"
])
return commands
# Global instance for easy access
port_checker = PortConflictDetector()

View File

@ -550,7 +550,6 @@ options:
fontname: arial fontname: arial
bgcolor: white bgcolor: white
color_mode: full color_mode: full
notes: Pull-up resistor (10) connects D2 to 5V
""" """
def _generate_display_template(self, description: str) -> str: def _generate_display_template(self, description: str) -> str:
@ -601,7 +600,6 @@ options:
fontname: arial fontname: arial
bgcolor: white bgcolor: white
color_mode: full color_mode: full
notes: I2C communication at 0x3C or 0x27 address
""" """
def _clean_yaml_content(self, content: str) -> str: def _clean_yaml_content(self, content: str) -> str: