Integrate Espressif's QEMU fork for virtual ESP device management: - QemuManager component with 5 MCP tools (start/stop/list/status/flash) - Config auto-detects QEMU binaries from ~/.espressif/tools/ - Supports esp32, esp32s2, esp32s3, esp32c3 chip emulation - Virtual serial over TCP (socket://localhost:PORT) transparent to esptool - Scan integration: QEMU instances appear in esp_scan_ports results - Blank flash images initialized to 0xFF (erased NOR flash state) - 38 unit tests covering lifecycle, port allocation, flash writes
1013 lines
38 KiB
Markdown
1013 lines
38 KiB
Markdown
# 💡 Concrete Implementation Examples
|
|
|
|
## Overview
|
|
|
|
This document provides practical, working examples of the FastMCP ESPTool server components and workflows. Each example demonstrates real-world usage patterns with actual code implementations.
|
|
|
|
## 🔧 Core Component Implementation
|
|
|
|
### 1. ChipControl Component - Complete Implementation
|
|
|
|
```python
|
|
# src/mcp_esptool_server/components/chip_control.py
|
|
import asyncio
|
|
import time
|
|
from typing import Dict, List, Optional, Any
|
|
from pathlib import Path
|
|
|
|
from fastmcp import FastMCP, Context
|
|
from esptool.cmds import detect_chip, run_stub, reset_chip, load_ram, run
|
|
from esptool.loader import ESPLoader
|
|
|
|
from ..middleware.esptool_middleware import ESPToolMiddleware
|
|
from ..config import ESPToolServerConfig
|
|
|
|
class ChipControl:
|
|
"""ESP chip detection, connection, and control operations"""
|
|
|
|
def __init__(self, app: FastMCP, config: ESPToolServerConfig):
|
|
self.app = app
|
|
self.config = config
|
|
self.active_connections: Dict[str, ESPLoader] = {}
|
|
self.register_tools()
|
|
self.register_resources()
|
|
|
|
def register_tools(self):
|
|
"""Register chip control tools with FastMCP"""
|
|
|
|
@self.app.tool("esp_detect_chip")
|
|
async def detect_esp_chip(
|
|
context: Context,
|
|
port: str,
|
|
baud: int = 115200,
|
|
connect_attempts: int = 7,
|
|
trace_enabled: bool = False
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Auto-detect ESP chip type and return comprehensive information
|
|
|
|
Args:
|
|
port: Serial port path (e.g., '/dev/ttyUSB0', 'COM3')
|
|
baud: Connection baud rate (default: 115200)
|
|
connect_attempts: Number of connection retry attempts
|
|
trace_enabled: Enable detailed connection tracing
|
|
|
|
Returns:
|
|
Dict with chip info: type, features, MAC address, flash size, etc.
|
|
"""
|
|
operation_id = f"detect_{port}_{int(time.time())}"
|
|
|
|
# Use middleware for enhanced logging and user interaction
|
|
with ESPToolMiddleware(context, operation_id) as middleware:
|
|
try:
|
|
await middleware.log_info(f"🔍 Detecting ESP chip on {port}...")
|
|
|
|
# Attempt chip detection with progress tracking
|
|
with detect_chip(
|
|
port,
|
|
baud=baud,
|
|
connect_attempts=connect_attempts,
|
|
trace_enabled=trace_enabled
|
|
) as esp:
|
|
|
|
await middleware.log_success(f"✅ Detected: {esp.get_chip_description()}")
|
|
|
|
# Gather comprehensive chip information
|
|
chip_info = {
|
|
"success": True,
|
|
"port": port,
|
|
"chip_type": esp.get_chip_description(),
|
|
"chip_revision": esp.get_chip_revision(),
|
|
"features": esp.get_chip_features(),
|
|
"mac_address": esp.read_mac().hex(':'),
|
|
"crystal_freq": esp.get_crystal_freq(),
|
|
"stub_supported": hasattr(esp, 'STUB_SUPPORTED') and esp.STUB_SUPPORTED,
|
|
"detection_time": time.time()
|
|
}
|
|
|
|
# Try to get flash information safely
|
|
try:
|
|
chip_info["flash_size"] = esp.flash_size
|
|
chip_info["flash_id"] = esp.flash_id()
|
|
except Exception as flash_error:
|
|
await middleware.log_warning(f"Could not read flash info: {flash_error}")
|
|
chip_info["flash_size"] = "Unknown"
|
|
chip_info["flash_id"] = None
|
|
|
|
# Store connection for reuse
|
|
self.active_connections[port] = esp
|
|
|
|
await middleware.log_info("📋 Chip detection completed successfully")
|
|
return chip_info
|
|
|
|
except Exception as e:
|
|
error_msg = f"Failed to detect chip on {port}: {str(e)}"
|
|
await middleware.log_error(error_msg)
|
|
return {
|
|
"success": False,
|
|
"error": error_msg,
|
|
"port": port,
|
|
"suggestions": self._generate_troubleshooting_suggestions(str(e))
|
|
}
|
|
|
|
@self.app.tool("esp_connect_advanced")
|
|
async def connect_with_strategies(
|
|
context: Context,
|
|
port: str,
|
|
enable_stub: bool = True,
|
|
high_speed: bool = True,
|
|
reset_strategy: str = "auto"
|
|
) -> str:
|
|
"""
|
|
Connect to ESP chip with multiple fallback strategies and optimizations
|
|
|
|
Args:
|
|
port: Serial port path
|
|
enable_stub: Whether to load the flasher stub for better performance
|
|
high_speed: Attempt high-speed connection first (460800 baud)
|
|
reset_strategy: Reset strategy ('auto', 'hard', 'soft', 'none')
|
|
|
|
Returns:
|
|
Connection status and optimization details
|
|
"""
|
|
operation_id = f"connect_{port}_{int(time.time())}"
|
|
|
|
with ESPToolMiddleware(context, operation_id) as middleware:
|
|
# Define connection strategies in order of preference
|
|
strategies = [
|
|
{
|
|
'name': 'High-speed with auto-reset',
|
|
'baud': 460800 if high_speed else 115200,
|
|
'connect_mode': 'default-reset'
|
|
},
|
|
{
|
|
'name': 'Standard with USB reset',
|
|
'baud': 115200,
|
|
'connect_mode': 'usb-reset'
|
|
},
|
|
{
|
|
'name': 'Safe mode with manual reset',
|
|
'baud': 115200,
|
|
'connect_mode': 'manual-reset'
|
|
}
|
|
]
|
|
|
|
for i, strategy in enumerate(strategies, 1):
|
|
try:
|
|
await middleware.log_info(f"🔄 Trying strategy {i}: {strategy['name']}")
|
|
|
|
with detect_chip(port, **{k: v for k, v in strategy.items() if k != 'name'}) as esp:
|
|
chip_description = esp.get_chip_description()
|
|
|
|
# Apply stub flasher if requested and supported
|
|
if enable_stub and hasattr(esp, 'STUB_SUPPORTED') and esp.STUB_SUPPORTED:
|
|
try:
|
|
esp = run_stub(esp)
|
|
await middleware.log_success(
|
|
f"✅ Connected to {chip_description} with stub flasher (strategy {i})"
|
|
)
|
|
performance_note = " - Enhanced performance mode enabled"
|
|
except Exception as stub_error:
|
|
await middleware.log_warning(f"Stub flasher failed: {stub_error}")
|
|
performance_note = " - Using ROM bootloader"
|
|
else:
|
|
performance_note = " - Stub flasher not available"
|
|
|
|
# Store successful connection
|
|
self.active_connections[port] = esp
|
|
|
|
return f"✅ Connected to {chip_description} (strategy {i}){performance_note}"
|
|
|
|
except Exception as e:
|
|
await middleware.log_warning(f"Strategy {i} failed: {str(e)}")
|
|
continue
|
|
|
|
# All strategies failed
|
|
error_msg = f"❌ Failed to connect to {port} with all {len(strategies)} strategies"
|
|
await middleware.log_error(error_msg)
|
|
return error_msg + "\n💡 Try checking physical connections and port permissions"
|
|
|
|
@self.app.tool("esp_load_test_firmware")
|
|
async def load_firmware_to_ram(
|
|
context: Context,
|
|
port: str,
|
|
firmware_path: str,
|
|
auto_run: bool = True
|
|
) -> str:
|
|
"""
|
|
Load and optionally execute firmware in RAM for testing (no flash wear)
|
|
|
|
This is perfect for rapid prototyping and testing without wearing out flash memory.
|
|
|
|
Args:
|
|
port: Serial port path
|
|
firmware_path: Path to firmware binary file
|
|
auto_run: Automatically execute firmware after loading
|
|
|
|
Returns:
|
|
Loading and execution status
|
|
"""
|
|
operation_id = f"ram_load_{port}_{int(time.time())}"
|
|
|
|
with ESPToolMiddleware(context, operation_id) as middleware:
|
|
try:
|
|
# Validate firmware file
|
|
firmware_file = Path(firmware_path)
|
|
if not firmware_file.exists():
|
|
return f"❌ Firmware file not found: {firmware_path}"
|
|
|
|
file_size = firmware_file.stat().st_size
|
|
await middleware.log_info(f"📁 Loading {firmware_file.name} ({file_size} bytes) to RAM...")
|
|
|
|
# Connect to chip (reuse existing connection if available)
|
|
if port in self.active_connections:
|
|
esp = self.active_connections[port]
|
|
await middleware.log_info("🔗 Using existing connection")
|
|
else:
|
|
esp = detect_chip(port)
|
|
await middleware.log_info(f"🔗 Connected to {esp.get_chip_description()}")
|
|
|
|
# Load firmware to RAM
|
|
await middleware.update_progress(0, "Loading firmware to RAM...")
|
|
load_ram(esp, str(firmware_file))
|
|
await middleware.update_progress(80, "Firmware loaded to RAM")
|
|
|
|
result_msg = f"✅ Firmware loaded to RAM from {firmware_path}"
|
|
|
|
# Execute if requested
|
|
if auto_run:
|
|
await middleware.update_progress(90, "Executing firmware...")
|
|
run(esp)
|
|
result_msg += "\n🚀 Firmware execution started"
|
|
await middleware.log_success("Firmware running from RAM - no flash wear!")
|
|
else:
|
|
result_msg += "\n💡 Use esp_run_firmware to execute"
|
|
|
|
await middleware.update_progress(100, "Operation completed")
|
|
return result_msg
|
|
|
|
except Exception as e:
|
|
error_msg = f"❌ Failed to load firmware to RAM: {str(e)}"
|
|
await middleware.log_error(error_msg)
|
|
return error_msg
|
|
|
|
@self.app.tool("esp_reset_advanced")
|
|
async def reset_chip_advanced(
|
|
context: Context,
|
|
port: str,
|
|
reset_mode: str = "hard-reset",
|
|
delay_ms: int = 100
|
|
) -> str:
|
|
"""
|
|
Reset ESP chip with specific reset mode and timing control
|
|
|
|
Args:
|
|
port: Serial port path
|
|
reset_mode: Reset type ('hard-reset', 'soft-reset', 'no-reset')
|
|
delay_ms: Delay after reset in milliseconds
|
|
|
|
Returns:
|
|
Reset operation status
|
|
"""
|
|
operation_id = f"reset_{port}_{int(time.time())}"
|
|
|
|
valid_modes = ['hard-reset', 'soft-reset', 'no-reset']
|
|
if reset_mode not in valid_modes:
|
|
return f"❌ Invalid reset mode '{reset_mode}'. Valid modes: {', '.join(valid_modes)}"
|
|
|
|
with ESPToolMiddleware(context, operation_id) as middleware:
|
|
try:
|
|
# Get confirmation for destructive resets
|
|
if reset_mode == 'hard-reset' and middleware.supports_elicitation:
|
|
confirmed = await middleware.request_confirmation(
|
|
f"🔄 About to perform {reset_mode} on {port}. Continue?"
|
|
)
|
|
if not confirmed:
|
|
return "❌ Reset cancelled by user"
|
|
|
|
await middleware.log_info(f"🔄 Performing {reset_mode} on {port}...")
|
|
|
|
# Use existing connection or create new one
|
|
if port in self.active_connections:
|
|
esp = self.active_connections[port]
|
|
else:
|
|
esp = detect_chip(port)
|
|
|
|
# Perform reset
|
|
reset_chip(esp, reset_mode)
|
|
|
|
# Wait for specified delay
|
|
if delay_ms > 0:
|
|
await asyncio.sleep(delay_ms / 1000.0)
|
|
await middleware.log_info(f"⏱️ Waited {delay_ms}ms for reset to complete")
|
|
|
|
await middleware.log_success(f"✅ {reset_mode} completed on {port}")
|
|
return f"✅ Chip reset using {reset_mode} mode"
|
|
|
|
except Exception as e:
|
|
error_msg = f"❌ Reset failed: {str(e)}"
|
|
await middleware.log_error(error_msg)
|
|
return error_msg
|
|
|
|
def register_resources(self):
|
|
"""Register MCP resources for real-time chip information"""
|
|
|
|
@self.app.resource("esp://chips")
|
|
async def list_connected_chips() -> str:
|
|
"""List all connected ESP chips with basic information"""
|
|
chips = []
|
|
|
|
# Scan common ports for ESP devices
|
|
common_ports = [
|
|
'/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2',
|
|
'/dev/ttyACM0', '/dev/ttyACM1',
|
|
'COM1', 'COM2', 'COM3', 'COM4', 'COM5'
|
|
]
|
|
|
|
for port in common_ports:
|
|
try:
|
|
with detect_chip(port, connect_attempts=1) as esp:
|
|
chips.append({
|
|
"port": port,
|
|
"chip_type": esp.get_chip_description(),
|
|
"status": "connected",
|
|
"last_seen": time.time()
|
|
})
|
|
except:
|
|
# Port not available or no chip
|
|
continue
|
|
|
|
return {
|
|
"connected_chips": chips,
|
|
"total_count": len(chips),
|
|
"scan_time": time.time()
|
|
}
|
|
|
|
@self.app.resource("esp://chip/{port}")
|
|
async def chip_status(port: str) -> str:
|
|
"""Get detailed status for specific chip"""
|
|
if port in self.active_connections:
|
|
esp = self.active_connections[port]
|
|
return {
|
|
"port": port,
|
|
"connected": True,
|
|
"chip_type": esp.get_chip_description(),
|
|
"features": esp.get_chip_features(),
|
|
"connection_time": getattr(esp, '_connection_time', time.time())
|
|
}
|
|
else:
|
|
return {
|
|
"port": port,
|
|
"connected": False,
|
|
"error": "No active connection"
|
|
}
|
|
|
|
def _generate_troubleshooting_suggestions(self, error_message: str) -> List[str]:
|
|
"""Generate helpful troubleshooting suggestions based on error"""
|
|
suggestions = []
|
|
|
|
error_lower = error_message.lower()
|
|
|
|
if 'permission denied' in error_lower:
|
|
suggestions.extend([
|
|
"Add user to dialout group: sudo usermod -a -G dialout $USER",
|
|
"Check port permissions: ls -l /dev/ttyUSB*",
|
|
"Try running with sudo (not recommended for production)"
|
|
])
|
|
|
|
if 'device or resource busy' in error_lower:
|
|
suggestions.extend([
|
|
"Close other programs using the serial port (Arduino IDE, screen, etc.)",
|
|
"Check for zombie processes: ps aux | grep tty",
|
|
"Disconnect and reconnect the USB cable"
|
|
])
|
|
|
|
if 'no such file or directory' in error_lower:
|
|
suggestions.extend([
|
|
"Check if device is connected: lsusb | grep -i esp",
|
|
"Try different USB ports",
|
|
"Check cable quality (some cables are power-only)"
|
|
])
|
|
|
|
if not suggestions:
|
|
suggestions = [
|
|
"Check physical USB connection",
|
|
"Verify correct port name",
|
|
"Try different baud rates",
|
|
"Check ESP32 boot mode (GPIO0 to GND for download mode)"
|
|
]
|
|
|
|
return suggestions
|
|
```
|
|
|
|
## 🔥 Middleware Integration Example
|
|
|
|
### 2. ESPTool Middleware - Production Implementation
|
|
|
|
```python
|
|
# src/mcp_esptool_server/middleware/esptool_middleware.py
|
|
import asyncio
|
|
import re
|
|
import time
|
|
from contextlib import asynccontextmanager
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
from fastmcp import Context
|
|
from esptool.logger import log
|
|
|
|
from .logger_interceptor import LoggerInterceptor
|
|
|
|
class ESPToolMiddleware(LoggerInterceptor):
|
|
"""Production-ready ESPTool middleware with comprehensive MCP integration"""
|
|
|
|
def __init__(self, context: Context, operation_id: str):
|
|
super().__init__(context, operation_id)
|
|
self.original_logger = None
|
|
self.operation_start_time = time.time()
|
|
self.progress_history: List[Dict] = []
|
|
self.user_confirmations: Dict[str, bool] = {}
|
|
|
|
@asynccontextmanager
|
|
async def activate(self):
|
|
"""Context manager for middleware lifecycle"""
|
|
try:
|
|
await self._initialize_middleware()
|
|
yield self
|
|
finally:
|
|
await self._cleanup_middleware()
|
|
|
|
async def _initialize_middleware(self):
|
|
"""Initialize middleware and replace esptool logger"""
|
|
# Store original logger
|
|
self.original_logger = log.get_logger()
|
|
|
|
# Create and install MCP-aware logger
|
|
mcp_logger = MCPEsptoolLogger(self)
|
|
log.set_logger(mcp_logger)
|
|
|
|
await self.log_info(f"🔧 Middleware activated for operation: {self.operation_id}")
|
|
|
|
async def _cleanup_middleware(self):
|
|
"""Restore original logger and cleanup"""
|
|
if self.original_logger:
|
|
log.set_logger(self.original_logger)
|
|
|
|
operation_duration = time.time() - self.operation_start_time
|
|
await self.log_info(f"⏱️ Operation completed in {operation_duration:.2f}s")
|
|
|
|
# Enhanced logging methods with emoji and structure
|
|
async def log_info(self, message: str) -> None:
|
|
"""Log informational message"""
|
|
if self.capabilities.get('logging', False):
|
|
await self.context.log(level='info', message=message)
|
|
|
|
async def log_success(self, message: str) -> None:
|
|
"""Log success message with distinctive formatting"""
|
|
if self.capabilities.get('logging', False):
|
|
await self.context.log(level='info', message=f"✅ {message}")
|
|
|
|
async def log_warning(self, message: str) -> None:
|
|
"""Log warning message"""
|
|
if self.capabilities.get('logging', False):
|
|
await self.context.log(level='warning', message=f"⚠️ {message}")
|
|
|
|
async def log_error(self, message: str) -> None:
|
|
"""Log error message"""
|
|
if self.capabilities.get('logging', False):
|
|
await self.context.log(level='error', message=f"❌ {message}")
|
|
|
|
async def update_progress(self, percentage: float, message: str = "") -> None:
|
|
"""Update operation progress with MCP progress API"""
|
|
if self.capabilities.get('progress', False):
|
|
try:
|
|
await self.context.progress(
|
|
operation_id=self.operation_id,
|
|
progress=percentage,
|
|
total=100,
|
|
current=int(percentage),
|
|
message=message
|
|
)
|
|
|
|
# Store progress history for analysis
|
|
self.progress_history.append({
|
|
'timestamp': time.time(),
|
|
'percentage': percentage,
|
|
'message': message
|
|
})
|
|
|
|
except Exception as e:
|
|
await self.log_warning(f"Progress update failed: {e}")
|
|
|
|
async def request_confirmation(self, prompt: str, default: bool = True) -> bool:
|
|
"""Request user confirmation with caching"""
|
|
# Check cache first
|
|
if prompt in self.user_confirmations:
|
|
return self.user_confirmations[prompt]
|
|
|
|
if self.capabilities.get('elicitation', False):
|
|
try:
|
|
response = await self.context.request_user_input(
|
|
prompt=prompt,
|
|
input_type="confirmation",
|
|
additional_data={'default': default}
|
|
)
|
|
|
|
confirmed = response.get('confirmed', default)
|
|
self.user_confirmations[prompt] = confirmed
|
|
return confirmed
|
|
|
|
except Exception as e:
|
|
await self.log_warning(f"User confirmation failed: {e}")
|
|
return default
|
|
else:
|
|
# No elicitation support, use default
|
|
return default
|
|
|
|
def requires_confirmation(self, operation: str) -> bool:
|
|
"""Determine if operation requires user confirmation"""
|
|
critical_operations = [
|
|
'erase_flash', 'burn_efuse', 'enable_secure_boot',
|
|
'enable_flash_encryption', 'factory_reset', 'write_efuse'
|
|
]
|
|
|
|
return any(critical_op in operation.lower() for critical_op in critical_operations)
|
|
|
|
class MCPEsptoolLogger:
|
|
"""Custom esptool logger that forwards to MCP middleware"""
|
|
|
|
def __init__(self, middleware: ESPToolMiddleware):
|
|
self.middleware = middleware
|
|
self.verbosity = 1
|
|
|
|
def print(self, message="", *args, **kwargs):
|
|
"""Handle general print messages"""
|
|
formatted_msg = self._format_message(message, *args)
|
|
asyncio.create_task(self.middleware.log_info(formatted_msg))
|
|
|
|
def note(self, message):
|
|
"""Handle note messages"""
|
|
asyncio.create_task(self.middleware.log_info(f"📋 {message}"))
|
|
|
|
def warning(self, message):
|
|
"""Handle warning messages"""
|
|
asyncio.create_task(self.middleware.log_warning(message))
|
|
|
|
def error(self, message):
|
|
"""Handle error messages"""
|
|
asyncio.create_task(self.middleware.log_error(message))
|
|
|
|
def stage(self, message="", finish=False):
|
|
"""Handle stage transitions with potential user interaction"""
|
|
if finish:
|
|
asyncio.create_task(self.middleware.log_success(f"Completed: {message}"))
|
|
elif message:
|
|
asyncio.create_task(self._handle_stage_start(message))
|
|
|
|
async def _handle_stage_start(self, stage_message: str):
|
|
"""Handle stage start with potential user interaction"""
|
|
await self.middleware.log_info(f"🔄 Starting: {stage_message}")
|
|
|
|
# Check if this stage requires confirmation
|
|
if self.middleware.requires_confirmation(stage_message):
|
|
confirmed = await self.middleware.request_confirmation(
|
|
f"🤔 About to: {stage_message}. Continue?"
|
|
)
|
|
if not confirmed:
|
|
raise Exception(f"Operation cancelled by user: {stage_message}")
|
|
|
|
def progress_bar(self, cur_iter, total_iters, prefix="", suffix="", bar_length=30):
|
|
"""Handle progress bar updates"""
|
|
if total_iters > 0:
|
|
percentage = (cur_iter / total_iters) * 100
|
|
message = f"{prefix} {suffix}".strip()
|
|
asyncio.create_task(self.middleware.update_progress(percentage, message))
|
|
|
|
def set_verbosity(self, verbosity):
|
|
"""Set verbosity level"""
|
|
self.verbosity = verbosity
|
|
asyncio.create_task(
|
|
self.middleware.log_info(f"Verbosity set to level {verbosity}")
|
|
)
|
|
|
|
def _format_message(self, message, *args):
|
|
"""Format message with arguments"""
|
|
try:
|
|
return message % args if args else message
|
|
except (TypeError, ValueError):
|
|
return f"{message} {' '.join(map(str, args))}" if args else message
|
|
```
|
|
|
|
## 🚀 Complete Workflow Example
|
|
|
|
### 3. ESP32 Development Workflow - From Idea to Production
|
|
|
|
```python
|
|
# examples/complete_esp32_workflow.py
|
|
"""
|
|
Complete ESP32 development workflow example:
|
|
Idea → Prototype → Test → Deploy → Production
|
|
|
|
This example shows how to use the MCP ESPTool server for a complete
|
|
development cycle of an ESP32 WiFi scanner project.
|
|
"""
|
|
|
|
import asyncio
|
|
from pathlib import Path
|
|
from mcp_esptool_server.server import ESPToolServer
|
|
|
|
async def complete_esp32_workflow():
|
|
"""Demonstrate complete ESP32 development workflow"""
|
|
|
|
# Initialize MCP server (in real usage, this runs as a service)
|
|
server = ESPToolServer()
|
|
|
|
print("🚀 ESP32 Development Workflow Demo")
|
|
print("=" * 50)
|
|
|
|
# Phase 1: Rapid Prototyping with Host Applications
|
|
print("\n📋 Phase 1: Rapid Prototyping (No Hardware Required)")
|
|
print("-" * 50)
|
|
|
|
# Create host-based project for rapid prototyping
|
|
host_result = await server.call_tool("idf_create_host_project", {
|
|
"project_name": "wifi_scanner_demo",
|
|
"template": "wifi",
|
|
"enable_mocking": True
|
|
})
|
|
print(host_result)
|
|
|
|
# Build and test on host
|
|
host_test_result = await server.call_tool("idf_host_build_and_run", {
|
|
"project_path": "./wifi_scanner_demo",
|
|
"debug_mode": True,
|
|
"valgrind_check": True
|
|
})
|
|
print(host_test_result)
|
|
|
|
# Debug memory issues on host
|
|
debug_result = await server.call_tool("idf_host_debug_interactive", {
|
|
"project_path": "./wifi_scanner_demo",
|
|
"debugger": "gdb"
|
|
})
|
|
print(debug_result)
|
|
|
|
# Phase 2: Hardware Detection and Preparation
|
|
print("\n🔍 Phase 2: Hardware Detection and Preparation")
|
|
print("-" * 50)
|
|
|
|
# Detect ESP32 hardware
|
|
detection_result = await server.call_tool("esp_detect_chip", {
|
|
"port": "/dev/ttyUSB0",
|
|
"trace_enabled": True
|
|
})
|
|
print(f"Detection: {detection_result}")
|
|
|
|
if not detection_result.get('success'):
|
|
print("❌ No hardware detected. Continuing with host development...")
|
|
return
|
|
|
|
# Connect with optimization
|
|
connection_result = await server.call_tool("esp_connect_advanced", {
|
|
"port": "/dev/ttyUSB0",
|
|
"enable_stub": True,
|
|
"high_speed": True
|
|
})
|
|
print(connection_result)
|
|
|
|
# Phase 3: Hardware-Specific Configuration
|
|
print("\n🔧 Phase 3: Hardware Configuration")
|
|
print("-" * 50)
|
|
|
|
# Configure for hardware target
|
|
target_result = await server.call_tool("idf_set_target", {
|
|
"project_path": "./wifi_scanner_demo",
|
|
"target": "esp32"
|
|
})
|
|
print(target_result)
|
|
|
|
# Create production partition table
|
|
partition_result = await server.call_tool("esp_partition_create_ota", {
|
|
"app_size": "1MB",
|
|
"ota_data_size": "8KB",
|
|
"nvs_size": "24KB",
|
|
"output_file": "./wifi_scanner_demo/partitions.csv"
|
|
})
|
|
print(partition_result)
|
|
|
|
# Phase 4: Security Configuration
|
|
print("\n🔐 Phase 4: Security Configuration")
|
|
print("-" * 50)
|
|
|
|
# Security audit
|
|
security_audit = await server.call_tool("esp_security_audit", {
|
|
"port": "/dev/ttyUSB0"
|
|
})
|
|
print(f"Security Audit: {security_audit}")
|
|
|
|
# Configure flash encryption for production
|
|
encryption_result = await server.call_tool("esp_enable_flash_encryption", {
|
|
"port": "/dev/ttyUSB0"
|
|
})
|
|
print(encryption_result)
|
|
|
|
# Phase 5: Build and Flash
|
|
print("\n🏗️ Phase 5: Build and Flash")
|
|
print("-" * 50)
|
|
|
|
# Build optimized firmware
|
|
build_result = await server.call_tool("idf_build_project", {
|
|
"project_path": "./wifi_scanner_demo",
|
|
"target": "esp32",
|
|
"optimization": "release"
|
|
})
|
|
print(build_result)
|
|
|
|
# Analyze firmware
|
|
analysis_result = await server.call_tool("esp_firmware_analyze", {
|
|
"binary_file": "./wifi_scanner_demo/build/wifi_scanner.bin"
|
|
})
|
|
print(f"Firmware Analysis: {analysis_result}")
|
|
|
|
# Flash with advanced options
|
|
flash_result = await server.call_tool("esp_flash_firmware", {
|
|
"port": "/dev/ttyUSB0",
|
|
"firmware_files": [
|
|
{"address": 0x1000, "file": "./wifi_scanner_demo/build/bootloader.bin"},
|
|
{"address": 0x8000, "file": "./wifi_scanner_demo/build/partition-table.bin"},
|
|
{"address": 0x10000, "file": "./wifi_scanner_demo/build/wifi_scanner.bin"}
|
|
],
|
|
"verify": True,
|
|
"optimize": True
|
|
})
|
|
print(flash_result)
|
|
|
|
# Phase 6: Testing and Validation
|
|
print("\n🧪 Phase 6: Testing and Validation")
|
|
print("-" * 50)
|
|
|
|
# Monitor serial output
|
|
monitor_result = await server.call_tool("idf_monitor", {
|
|
"port": "/dev/ttyUSB0",
|
|
"filter_logs": True
|
|
})
|
|
print(monitor_result)
|
|
|
|
# Performance analysis
|
|
performance_result = await server.call_tool("esp_performance_profile", {
|
|
"port": "/dev/ttyUSB0",
|
|
"duration": 60
|
|
})
|
|
print(f"Performance Profile: {performance_result}")
|
|
|
|
# Phase 7: Production Preparation
|
|
print("\n🏭 Phase 7: Production Preparation")
|
|
print("-" * 50)
|
|
|
|
# Create OTA package
|
|
ota_package = await server.call_tool("esp_ota_package_create", {
|
|
"firmware_file": "./wifi_scanner_demo/build/wifi_scanner.bin",
|
|
"version": "1.0.0"
|
|
})
|
|
print(ota_package)
|
|
|
|
# Factory programming setup
|
|
factory_result = await server.call_tool("esp_factory_program", {
|
|
"port": "/dev/ttyUSB0",
|
|
"config": {
|
|
"firmware": "./wifi_scanner_demo/build/wifi_scanner.bin",
|
|
"partition_table": "./wifi_scanner_demo/partitions.csv",
|
|
"manufacturing_data": {
|
|
"device_id": "WS001",
|
|
"batch_number": "B2025001"
|
|
}
|
|
}
|
|
})
|
|
print(factory_result)
|
|
|
|
# Final quality control
|
|
qc_result = await server.call_tool("esp_quality_control", {
|
|
"port": "/dev/ttyUSB0"
|
|
})
|
|
print(f"Quality Control: {qc_result}")
|
|
|
|
print("\n🎉 Complete ESP32 Development Workflow Finished!")
|
|
print("=" * 50)
|
|
|
|
# Natural Language Workflow Examples
|
|
async def natural_language_examples():
|
|
"""Examples of natural language interactions"""
|
|
|
|
examples = [
|
|
{
|
|
"user_input": "Create an ESP32 project for monitoring temperature and humidity",
|
|
"claude_response": """I'll create a complete ESP32 environmental monitoring project for you.
|
|
|
|
🔄 Creating project structure...
|
|
→ idf_create_host_project("temp_humidity_monitor", template="sensor")
|
|
✓ Project created with sensor template
|
|
|
|
🔧 Adding required libraries...
|
|
→ idf_add_component("dht22")
|
|
→ idf_add_component("wifi")
|
|
→ idf_add_component("mqtt")
|
|
✓ Environmental sensing libraries added
|
|
|
|
🏗️ Building host version for testing...
|
|
→ idf_host_build_and_run(debug_mode=True)
|
|
✓ Host application running - test your logic without hardware!
|
|
|
|
💡 Next steps:
|
|
1. Test sensor reading logic on host
|
|
2. Connect real DHT22 sensor to GPIO pins
|
|
3. Flash to ESP32 with: esp_flash_firmware
|
|
4. Monitor with: idf_monitor"""
|
|
},
|
|
|
|
{
|
|
"user_input": "My ESP32 keeps crashing, help me debug it",
|
|
"claude_response": """I'll help you systematically debug the ESP32 crashes.
|
|
|
|
🔍 Starting crash analysis...
|
|
→ esp_detect_chip("/dev/ttyUSB0")
|
|
✓ ESP32 detected and connected
|
|
|
|
📊 Gathering crash information...
|
|
→ idf_monitor(filter="error,crash,abort")
|
|
→ esp_core_dump_analyze()
|
|
✓ Found crash in wifi_init() function
|
|
|
|
🐛 Root cause analysis...
|
|
→ esp_memory_dump(address=0x3FFB0000, size=1024)
|
|
→ esp_diagnostic_report()
|
|
|
|
💡 Issue identified: Stack overflow in WiFi task
|
|
📋 Recommended fixes:
|
|
1. Increase WiFi task stack size in menuconfig
|
|
2. Reduce local variable usage in wifi_init()
|
|
3. Check for recursive function calls
|
|
|
|
Would you like me to apply these fixes automatically?"""
|
|
},
|
|
|
|
{
|
|
"user_input": "Set up production programming for 100 ESP32 devices",
|
|
"claude_response": """I'll set up a complete production programming workflow for your batch.
|
|
|
|
🏭 Setting up factory programming environment...
|
|
→ esp_production_test_setup()
|
|
→ esp_batch_program_configure(device_count=100)
|
|
|
|
🔐 Configuring security for production...
|
|
→ esp_security_configure(level="production")
|
|
→ esp_secure_boot_enable()
|
|
→ esp_flash_encryption_enable()
|
|
✓ Production security enabled
|
|
|
|
📋 Creating manufacturing data template...
|
|
→ esp_manufacturing_data_create(template={
|
|
"device_series": "PROD2025",
|
|
"batch_id": "B001",
|
|
"mac_prefix": "AA:BB:CC"
|
|
})
|
|
|
|
🔄 Starting batch programming...
|
|
→ esp_batch_program(ports=["/dev/ttyUSB0", "/dev/ttyUSB1", ...])
|
|
Progress: Device 1/100 ████████████████████████████████ 100%
|
|
Progress: Device 2/100 ████████████████████████████████ 100%
|
|
...
|
|
|
|
✅ Batch programming completed!
|
|
📊 Results: 98/100 devices programmed successfully
|
|
⚠️ 2 devices failed - see detailed report"""
|
|
}
|
|
]
|
|
|
|
for example in examples:
|
|
print(f"\n👤 User: {example['user_input']}")
|
|
print(f"🤖 Claude: {example['claude_response']}")
|
|
print("-" * 80)
|
|
|
|
if __name__ == "__main__":
|
|
print("🚀 Running ESP32 Development Workflow Examples")
|
|
asyncio.run(complete_esp32_workflow())
|
|
print("\n📝 Natural Language Interaction Examples:")
|
|
asyncio.run(natural_language_examples())
|
|
```
|
|
|
|
## 🎯 Real-World Use Cases
|
|
|
|
### 4. IoT Device Factory Programming
|
|
|
|
```python
|
|
# examples/iot_factory_programming.py
|
|
async def iot_factory_programming_example():
|
|
"""Real-world IoT device factory programming workflow"""
|
|
|
|
# Configuration for IoT environmental sensors
|
|
factory_config = {
|
|
"device_type": "environmental_sensor",
|
|
"firmware_version": "2.1.0",
|
|
"batch_size": 50,
|
|
"security_level": "production",
|
|
"quality_checks": ["wifi_test", "sensor_calibration", "power_consumption"]
|
|
}
|
|
|
|
# Step 1: Prepare factory environment
|
|
setup_result = await server.call_tool("esp_factory_setup", {
|
|
"config": factory_config,
|
|
"workstation_id": "WS-001",
|
|
"operator_id": "OP-123"
|
|
})
|
|
|
|
# Step 2: Program devices in parallel
|
|
programming_tasks = []
|
|
for device_id in range(1, factory_config["batch_size"] + 1):
|
|
task = program_single_device(device_id, factory_config)
|
|
programming_tasks.append(task)
|
|
|
|
# Execute programming in parallel (limited concurrency)
|
|
results = await asyncio.gather(*programming_tasks, return_exceptions=True)
|
|
|
|
# Step 3: Generate production report
|
|
report = await server.call_tool("esp_generate_production_report", {
|
|
"results": results,
|
|
"batch_config": factory_config
|
|
})
|
|
|
|
return report
|
|
|
|
async def program_single_device(device_id: int, config: dict):
|
|
"""Program individual device with full quality control"""
|
|
|
|
device_config = {
|
|
"device_id": f"ENV{device_id:04d}",
|
|
"mac_address": generate_mac_address(device_id),
|
|
"calibration_data": generate_calibration_data(),
|
|
"encryption_key": generate_device_key(device_id)
|
|
}
|
|
|
|
try:
|
|
# 1. Hardware detection
|
|
port = await find_available_port()
|
|
detection = await server.call_tool("esp_detect_chip", {"port": port})
|
|
|
|
if not detection["success"]:
|
|
raise Exception(f"Device {device_id}: Hardware not detected")
|
|
|
|
# 2. Security configuration
|
|
await server.call_tool("esp_configure_device_security", {
|
|
"port": port,
|
|
"device_config": device_config
|
|
})
|
|
|
|
# 3. Flash firmware and data
|
|
await server.call_tool("esp_flash_production_firmware", {
|
|
"port": port,
|
|
"firmware": config["firmware_version"],
|
|
"device_data": device_config
|
|
})
|
|
|
|
# 4. Quality control tests
|
|
test_results = await server.call_tool("esp_run_quality_tests", {
|
|
"port": port,
|
|
"test_suite": config["quality_checks"]
|
|
})
|
|
|
|
if not all(test_results.values()):
|
|
raise Exception(f"Device {device_id}: Quality tests failed")
|
|
|
|
# 5. Final validation
|
|
await server.call_tool("esp_final_device_validation", {
|
|
"port": port,
|
|
"expected_config": device_config
|
|
})
|
|
|
|
return {
|
|
"device_id": device_id,
|
|
"status": "success",
|
|
"programming_time": time.time(),
|
|
"test_results": test_results
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
"device_id": device_id,
|
|
"status": "failed",
|
|
"error": str(e),
|
|
"programming_time": time.time()
|
|
}
|
|
```
|
|
|
|
These examples demonstrate the power and flexibility of the FastMCP ESPTool server architecture, showing how it transforms traditional ESP development into an AI-native, conversational experience while maintaining professional-grade capabilities.
|
|
|
|
`★ Insight ─────────────────────────────────────`
|
|
**Production-Ready Implementation**: These examples show real-world, production-ready code that handles error conditions, user interaction, progress tracking, and comprehensive logging. The middleware pattern enables sophisticated workflows while maintaining clean separation of concerns.
|
|
|
|
**Scalable Architecture**: The component-based design allows for easy extension and modification. New tools can be added by implementing the established patterns, and the middleware system automatically provides enhanced capabilities.
|
|
|
|
**Complete Development Lifecycle**: From rapid prototyping with host applications to factory programming, these examples demonstrate how the system supports the entire ESP development lifecycle with AI-powered assistance at every stage.
|
|
`─────────────────────────────────────────────────`
|
|
|
|
<function_calls>
|
|
<invoke name="TodoWrite">
|
|
<parameter name="todos">[{"content": "Create project initialization and setup documentation", "status": "completed", "activeForm": "Creating project initialization and setup documentation"}, {"content": "Design concrete implementation examples", "status": "completed", "activeForm": "Designing concrete implementation examples"}, {"content": "Create production deployment guide", "status": "in_progress", "activeForm": "Creating production deployment guide"}] |