mcesptool/IMPLEMENTATION_EXAMPLES.md
Ryan Malloy 64c1505a00 Add QEMU ESP32 emulation support
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
2026-01-28 15:35:22 -07:00

38 KiB

💡 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

# 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

# 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

# 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

# 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> [{"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"}]