diff --git a/generate_fat_images.ps1 b/generate_fat_images.ps1 new file mode 100644 index 0000000..1c90ea8 --- /dev/null +++ b/generate_fat_images.ps1 @@ -0,0 +1,49 @@ +# Generate FAT32 images for Wippersnapper firmware +param( + [Parameter(Mandatory=$true)] + [string]$OutputDir, + + [Parameter(Mandatory=$true)] + [int]$SizeMB +) + +# Get current timestamp +$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" + +# Create output directory if it doesn't exist +New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null + +# Create a temporary file for the FAT image +$imagePath = Join-Path -Path $OutputDir -ChildPath ("wippersnapper_fat_image_$timestamp.img") + +# Create an empty file of the specified size +$sizeBytes = $SizeMB * 1MB +fsutil file createnew $imagePath $sizeBytes + +# Create temporary diskpart script +$scriptPath = [System.IO.Path]::GetTempFileName() + +# Create the script file content +@" +create vdisk file="$imagePath" maximum=$SizeMB type=fixed +select vdisk file="$imagePath" +attach vdisk +create partition primary +format fs=fat32 quick +assign letter=X +exit +"@ | Out-File -FilePath $scriptPath -Encoding ascii + +try { + # Run diskpart with the script + diskpart /s $scriptPath + + # Wait for drive letter to be assigned + Start-Sleep -Seconds 1 + + # Return the image path + return $imagePath +} finally { + # Clean up the script file + Remove-Item $scriptPath -Force +} diff --git a/merge-script.py b/merge-script.py new file mode 100644 index 0000000..24637d8 --- /dev/null +++ b/merge-script.py @@ -0,0 +1,1325 @@ +import re +import requests +import traceback + + +def main(): + """Main function to process all firmware files""" + global CI_ARDUINO_PARAMS + + print("=== Wippersnapper UF2 Firmware and Filesystem Merger ===") + os.chdir(BASE_DIR) + print(f"Working directory: {os.getcwd()}") + + # Ensure output directory exists + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # Check for arduino-cli + try: + result = subprocess.run(["arduino-cli", "version"], capture_output=True, text=True, check=False) + if result.returncode == 0: + print(f"Found arduino-cli: {result.stdout.strip()}") + use_arduino_cli = True + + # Update the core index with BSP URLs + print("Updating arduino-cli core index with BSP URLs...") + update_cmd = ["arduino-cli", "core", "update-index", "--additional-urls", BSP_URLS] + subprocess.run(update_cmd, check=False) + else: + print("Warning: arduino-cli not found in PATH, will use local paths only") + use_arduino_cli = False + except Exception: + print("Warning: arduino-cli not available, will use local paths only") + use_arduino_cli = False + + # Extract build targets from workflow file + print("\n=== Extracting build targets from GitHub workflow file ===") + build_targets = extract_build_targets_from_workflow() + + # Try to fetch board-specific parameters from ci-arduino repo + print("\n=== Fetching board configurations from ci-arduino ===") + CI_ARDUINO_PARAMS = fetch_ci_arduino_parameters(use_git=True) + if CI_ARDUINO_PARAMS: + print(f"Found {len(CI_ARDUINO_PARAMS)} board configurations from ci-arduino") + else: + print("Warning: Could not fetch configurations from ci-arduino") + + # Find all UF2 firmware files + print("\n=== Scanning for firmware files ===") + fw_files = list(FIRMWARE_DIR.glob("*.uf2")) + if not fw_files: + print(f"No UF2 firmware files found in {FIRMWARE_DIR}") + return + + print(f"Found {len(fw_files)} firmware files") + + # Process each firmware file + print("\n=== Processing firmware files ===") + for fw_file in fw_files: + try: + print(f"\nProcessing: {fw_file.name}") + process_firmware_file(fw_file) + except Exception as e: + print(f"Error processing {fw_file}: {e}") + import traceback + traceback.print_exc() + + print("\n=== Processing complete! ===") + print(f"Merged firmware files are in: {OUTPUT_DIR}") + +import os +import sys +import subprocess +import tempfile +import shutil +import hashlib +import json +import glob +from pathlib import Path +from datetime import datetime + +# Constants +BASE_DIR = Path(r"C:\dev\arduino\Adafruit_Wippersnapper_Offline_Configurator") +OUTPUT_DIR = BASE_DIR / "merged_firmware" +FIRMWARE_DIR = BASE_DIR / "latest_firmware" +DEFAULT_FAT_SIZE = 1024 * 1024 # Default 1MB FAT partition, will be overridden by board-specific values +OFFLINE_FILES = ["offline.js", "offline.html"] + +# Dictionary of common partition sizes from Arduino ESP32 board definitions +# These sizes need to match exactly what's defined in the board's partition table +PARTITION_SIZES = { + "default": DEFAULT_FAT_SIZE, + "default_16MB": 0xC00000, # 12MB for fat when using the default_16MB partition scheme + "large_spiffs": 0x290000, # ~2.6MB fat partition in large_spiffs scheme + "huge_app": 0x120000, # ~1.1MB fat partition in huge_app scheme + "min_spiffs": 0x40000, # 256KB fat partition in min_spiffs scheme + "custom_fat": 0x400000, # 4MB fat partition for custom partition schemes + "tinyuf2_noota": 0xF0000 # 960KB fat partition in tinyuf2-partitions-4MB-noota.csv +} + +# GitHub URLs for ESP32 Arduino core +ESP32_BOARDS_TXT_URL = "https://raw.githubusercontent.com/espressif/arduino-esp32/master/boards.txt" +ESP32_PARTITIONS_BASE_URL = "https://raw.githubusercontent.com/espressif/arduino-esp32/master/tools/partitions/" +ESP32_GITHUB_API_PARTITIONS_URL = "https://api.github.com/repos/espressif/arduino-esp32/contents/tools/partitions" + +# BSP URLs from ci-arduino +BSP_URLS = ( + "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json," + "http://arduino.esp8266.com/stable/package_esp8266com_index.json," + "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json," + "https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json," + "https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json," + "https://drazzy.good-enough.cloud/package_drazzy.com_index.json," + "https://github.com/openwch/board_manager_files/raw/main/package_ch32v_index.json" +) + +# Global variable to store ci-arduino parameters +CI_ARDUINO_PARAMS = {} + +# Platform-specific parameters +PLATFORM_PARAMS = { + "RP2040": { + "family": "RP2040", + "fat_base_addr": 0x10100000, # Example, would need to be adjusted per target + "uf2_family_id": "0xe48bff56" # RP2040 family ID for RP2040/RP2350/etc + }, + "ESP32": { + "family": "ESP32", + "fat_base_addr": 0x310000, # Example, would need to be adjusted per target + "partition_size": DEFAULT_FAT_SIZE + } +} + +# Map of board identifiers to detect specific target properties +# This could be expanded based on the ci-arduino/all_platforms.py information +BOARD_SPECIFIC_PARAMS = { + # ESP32-S2 boards might have different partition layouts + "esp32s2": { + "platform": "ESP32", + "fat_base_addr": 0x310000 # Adjust as needed + }, + # ESP32-S3 boards + "esp32s3": { + "platform": "ESP32", + "fat_base_addr": 0x310000 # Adjust as needed + }, + # RP2350 might have specific settings + "rp2350": { + "platform": "RP2040", + "fat_base_addr": 0x10100000 # Adjust as needed + } + # Add more board-specific configurations as needed +} + +# Ensure output directory exists +os.makedirs(OUTPUT_DIR, exist_ok=True) + +def create_fat_image(fat_img_path, files_to_include, fat_size): + """Create a FAT32 image containing the specified files""" + try: + # Generate the PowerShell script + script_path = generate_image_generation_script( + os.path.dirname(fat_img_path), + int(fat_size/1024/1024) + ) + + print(f"Generated PowerShell script: {script_path}") + + # Run the PowerShell script to generate the image + cmd = f'powershell -File "{script_path}"' + + # Execute the command + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + + # Get the image path from the output + if result.returncode != 0: + print(f"Error generating FAT32 image: {result.stderr}") + return None + + image_path = result.stdout.strip() + if not image_path: + print("Error: No image path returned from PowerShell script") + return None + + # Mount the image using diskpart + mount_script = ["select vdisk file=\"{}\"".format(image_path), + "attach vdisk", + "assign letter=X", + "exit"] + + with tempfile.NamedTemporaryFile(delete=False, suffix='.txt') as mount_file: + mount_file.write('\n'.join(mount_script).encode()) + mount_file.flush() + + # Run diskpart to mount the image + subprocess.run(["diskpart", "/s", mount_file.name], check=True) + + # Copy files to the mounted drive + for file_path in files_to_include: + src_path = BASE_DIR / file_path + dest_path = Path(f"X:/") / os.path.basename(file_path) + shutil.copy2(src_path, dest_path) + print(f"Copied {src_path} to {dest_path}") + + # Unmount the image + unmount_script = ["select vdisk file=\"{}\"".format(image_path), + "detach vdisk", + "exit"] + + with tempfile.NamedTemporaryFile(delete=False, suffix='.txt') as unmount_file: + unmount_file.write('\n'.join(unmount_script).encode()) + unmount_file.flush() + + # Run diskpart to unmount the image + subprocess.run(["diskpart", "/s", unmount_file.name], check=True) + + # Copy the final image to the desired location + shutil.copy2(image_path, fat_img_path) + + return fat_img_path + + except Exception as e: + print(f"Error creating FAT image: {e}") + return None + +def generate_image_generation_script(output_dir, image_size_mb): + """ + Generate a PowerShell script that will create multiple FAT32 images + + Args: + output_dir: Directory where images will be created + image_size_mb: Size of each image in megabytes + + Returns: + Path to the generated PowerShell script + """ + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + script_path = os.path.join(output_dir, f"generate_images_{timestamp}.ps1") + + with open(script_path, 'w') as f: + f.write(""" +# Function to create a single FAT32 image +function Create-FatImage { + param( + [string]$ImagePath, + [int]$SizeMB + ) + + # Create empty file + $sizeBytes = $SizeMB * 1MB + fsutil file createnew $ImagePath $sizeBytes + + # Create temporary diskpart script + $scriptPath = [System.IO.Path]::GetTempFileName() + + # Create the script file content + @" + create vdisk file="$ImagePath" maximum=$SizeMB type=fixed + select vdisk file="$ImagePath" + attach vdisk + create partition primary + format fs=fat32 quick + assign letter=X + exit + "@ | Out-File -FilePath $scriptPath -Encoding ascii + + try { + # Run diskpart with the script + diskpart /s $scriptPath + + # Wait for drive letter to be assigned + Start-Sleep -Seconds 1 + + # Return the image path + return $ImagePath + } finally { + # Clean up the script file + Remove-Item $scriptPath -Force + } +} + +# Main script execution +try { + # Create output directory if it doesn't exist + if (-not (Test-Path $outputDir)) { + New-Item -ItemType Directory -Force -Path $outputDir | Out-Null + } + + # Create FAT32 image + $imagePath = Join-Path -Path $outputDir -ChildPath ("wippersnapper_fat_image_$timestamp.img") + Create-FatImage -ImagePath $imagePath -SizeMB $SizeMB + + # Return the image path + Write-Output $imagePath +} catch { + Write-Error "Error creating FAT32 image: $_" + exit 1 +} +""".format(output_dir=output_dir, SizeMB=image_size_mb)) + + return script_path + +def convert_to_uf2(img_path, platform_type, output_path, base_addr, uf2conv_script): + """Convert raw image to UF2 format""" + # Build the command based on platform + if platform_type == "RP2040": + cmd = [ + "python", str(uf2conv_script), + "--base", hex(base_addr), + "--family", PLATFORM_PARAMS[platform_type]["uf2_family_id"], + "--output", output_path, + img_path + ] + else: # ESP32 + cmd = [ + "python", str(uf2conv_script), + "--base", hex(base_addr), + "--family", PLATFORM_PARAMS[platform_type]["uf2_family_id"], + "--output", output_path, + img_path + ] + + print(f"Running command: {' '.join(cmd)}") + try: + subprocess.run(cmd, check=True) + return output_path + except subprocess.CalledProcessError as e: + print(f"Error running uf2conv.py: {e}") + print(f"Command output: {e.output}") + return None + +def merge_uf2_files(fw_uf2, fs_uf2, output_path): + """Merge firmware and filesystem UF2 files""" + # For simplicity we'll just concatenate the files + # In a production environment, you might want a proper UF2 merger + with open(output_path, 'wb') as outfile: + for infile in [fw_uf2, fs_uf2]: + with open(infile, 'rb') as f: + outfile.write(f.read()) + + return output_path + +def process_firmware_file(fw_path): + """ + Process a single firmware file + """ + fw_name = os.path.basename(fw_path) + print(f"Processing firmware: {fw_name}") + + # Extract board ID from firmware name + board_id = extract_board_id_from_filename(fw_name) + if not board_id: + print(f"Warning: Could not extract board ID from {fw_name}") + return None + + # Determine platform type based on board ID + if board_id.startswith(('esp32', 'esp32s2', 'esp32s3')): + platform_type = "ESP32" + elif board_id.startswith(('rp2040', 'rp2350', 'rp2')): + platform_type = "RP2040" + else: + print(f"Warning: Could not determine platform type for {board_id}, assuming RP2040") + platform_type = "RP2040" + + print(f"Detected platform: {platform_type}") + print(f"Board ID: {board_id}") + + # Create output directory if it doesn't exist + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # Determine output filename + output_filename = fw_name.replace(".uf2", f"_{board_id}_with_fs.uf2") + output_path = os.path.join(OUTPUT_DIR, output_filename) + + # Process the firmware file + with tempfile.TemporaryDirectory() as temp_dir: + # Create FAT image + fat_img_path = os.path.join(temp_dir, f"fat32_{board_id}.img") + + # Get board-specific parameters + fat_size, fat_base_addr = get_board_fat_parameters(fw_name, platform_type) + + create_fat_image(fat_img_path, OFFLINE_FILES, fat_size) + + # Convert FAT image to UF2 + uf2conv_script = locate_uf2conv_script() + if not uf2conv_script: + print("Error: Could not locate uf2conv.py script") + return None + + fs_uf2_path = os.path.join(temp_dir, f"filesystem_{board_id}.uf2") + convert_to_uf2(fat_img_path, platform_type, fs_uf2_path, fat_base_addr, uf2conv_script) + + # Merge firmware UF2 with filesystem UF2 + merge_uf2_files(fw_path, fs_uf2_path, output_path) + + print(f"Created merged firmware: {output_path}") + + return output_path + +def get_board_fat_parameters(fw_name, platform_type): + """ + Get the FAT size and base address for the board based on the firmware file name and platform type + """ + # Default values + fat_size = DEFAULT_FAT_SIZE + fat_base_addr = PLATFORM_PARAMS[platform_type]["fat_base_addr"] + + # For ESP32 boards, try to get more accurate parameters + if platform_type == "ESP32": + # Extract board ID from firmware name + board_id = extract_board_id_from_filename(fw_name) + if board_id: + # Try to get FQBN for the board + fqbn = f"esp32:esp32:{board_id}" + + # Fetch board info from GitHub + board_info = fetch_board_info_from_github(board_id) + if board_info: + # Get flash size + flash_size = board_info.get("flash_size") + + # Get partition scheme + partition_scheme = board_info.get("default_partition") + + if partition_scheme and "tinyuf2" in partition_scheme.lower(): + # This is a TinyUF2 partition, get the parameters from the partition file + partition_file = locate_partition_file(partition_scheme) + if partition_file: + fat_params = parse_fat_parameters_from_partition(partition_file) + if fat_params: + fat_size = fat_params["size"] + fat_base_addr = fat_params["offset"] + elif flash_size: + # Try to find a TinyUF2 partition that matches the flash size + tinyuf2_partition = find_matching_tinyuf2_partition(flash_size) + if tinyuf2_partition: + fat_params = parse_fat_parameters_from_partition(tinyuf2_partition) + if fat_params: + fat_size = fat_params["size"] + fat_base_addr = fat_params["offset"] + + print(f"Using FAT size: {fat_size} bytes, base address: 0x{fat_base_addr:X}") + return fat_size, fat_base_addr + +def extract_board_id_from_filename(fw_name): + """ + Extract a board ID from the firmware filename that matches the format used in boards.txt + + Args: + fw_name: The firmware filename + + Returns: + The board ID as it appears in boards.txt, or None if not found + """ + # First try to get the exact board name from the filename + # Split on dots and take the second part (after wippersnapper and before version) + parts = fw_name.split('.') + if len(parts) >= 2: + board_name = parts[1] + # Convert to lowercase and replace underscores with hyphens to match boards.txt format + board_id = board_name.lower().replace('_', '-') + + # Special case for feather boards since they use feather-esp32s2 in boards.txt + if board_id.startswith('feather-esp32s2'): + return 'feather-esp32s2' + elif board_id.startswith('feather-esp32s3'): + return 'feather-esp32s3' + + # For other boards, return the converted name + return board_id + + return None + +def parse_fat_parameters_from_partition(partition_file): + """ + Parse the FAT partition parameters from a partition CSV file + """ + try: + with open(partition_file, 'r') as f: + lines = f.readlines() + + # Print the header and first few lines for debugging + print("\nPartition CSV contents:") + for line in lines[:5]: # Print first 5 lines + print(line.strip()) + + # Parse the CSV to find the FAT partition + fat_offset = None + fat_size = None + + for line in lines: + line = line.strip() + if not line or line.startswith('#'): + continue + + parts = [p.strip() for p in line.split(',')] + if len(parts) >= 5: + name, type_val, subtype, offset, size = parts[:5] + print(f"\nProcessing partition row: {parts}") # Debug print + print(f"Name: {name}, Type: {type_val}, Subtype: {subtype}, Offset: {offset}, Size: {size}") + + # Look for the FAT partition (could be named ffat, fat, spiffs, etc.) + if (type_val.lower() == "data" and + (subtype.lower() == "fat" or "fat" in name.lower() or + "ffat" in name.lower() or "spiffs" in name.lower())): + + # Convert offset and size to integers + try: + if offset.startswith("0x"): + fat_offset = int(offset, 16) + print(f"Offset converted from hex: 0x{offset} -> {fat_offset}") + else: + try: + fat_offset = int(offset) + print(f"Offset converted from decimal: {offset} -> {fat_offset}") + except ValueError: + print(f"Warning: Could not convert offset '{offset}' to integer, skipping") + continue + + if size.endswith("K"): + fat_size = int(size[:-1]) * 1024 + print(f"Size converted from K: {size} -> {fat_size} bytes") + elif size.endswith("M"): + fat_size = int(size[:-1]) * 1024 * 1024 + print(f"Size converted from M: {size} -> {fat_size} bytes") + elif size.endswith(","): + # Remove trailing comma + size = size[:-1] + if "K" in size: + fat_size = int(size.replace("K", "")) * 1024 + print(f"Size converted from K with comma: {size} -> {fat_size} bytes") + elif "M" in size: + fat_size = int(size.replace("M", "")) * 1024 * 1024 + print(f"Size converted from M with comma: {size} -> {fat_size} bytes") + else: + try: + fat_size = int(size) + print(f"Size converted from decimal: {size} -> {fat_size} bytes") + except ValueError: + print(f"Warning: Could not convert size '{size}' to integer, skipping") + continue + else: + try: + fat_size = int(size) + print(f"Size converted from decimal: {size} -> {fat_size} bytes") + except ValueError: + print(f"Warning: Could not convert size '{size}' to integer, skipping") + continue + + print(f"Found FAT partition: offset={hex(fat_offset)}, size={fat_size} bytes") + return {"offset": fat_offset, "size": fat_size} + except ValueError as e: + print(f"Error converting offset/size: {e}") + continue + + print("No FAT partition found in CSV file") + return None + except Exception as e: + print(f"Error parsing partition file: {e}") + return None + +def fetch_workflow_file_from_github(): + """ + Fetch the workflow file from GitHub API from https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/blob/offline-mode/.github/workflows/release-offline.yml + """ + try: + import requests + response = requests.get("https://raw.githubusercontent.com/adafruit/Adafruit_Wippersnapper_Arduino/offline-mode/.github/workflows/release-offline.yml") + if response.status_code == 200: + return response.text + else: + print(f"Failed to fetch workflow file: {response.status_code}") + return None + except Exception as e: + print(f"Error fetching workflow file: {e}") + return None + +def extract_build_targets_from_workflow(workflow_file_path=None): + """ + Extract build targets from the GitHub Actions workflow file + """ + try: + # fetch workflow file from git api + if workflow_file_path is None: + workflow_content = fetch_workflow_file_from_github() + if workflow_content is None: + print("Warning: Could not fetch workflow file") + + # If no workflow file is provided, try to find it + if workflow_file_path is None: + + potential_paths = [ + Path(r"C:\dev\arduino\Adafruit_Wippersnapper_Arduino\.github\workflows\release-offline.yml"), + Path(r".github\workflows\release-offline.yml"), + Path(r"..\.github\workflows\release-offline.yml") + ] + + for path in potential_paths: + if path.exists(): + workflow_file_path = path + break + + + + + if workflow_file_path is None: + print("Warning: Could not find workflow file") + return [] + + # Read the workflow file + with open(workflow_file_path, 'r') as f: + workflow_content = f.read() + + # Extract build matrix for ESP32-SX + import re + esp32_matrix = re.search(r'build-esp32sx:.*?matrix:.*?arduino-platform:.*?\[(.*?)\]', + workflow_content, re.DOTALL) + + # Extract build matrix for RP2040 + rp2040_matrix = re.search(r'build-rp2040:.*?matrix:.*?arduino-platform:.*?\[(.*?)\]', + workflow_content, re.DOTALL) + + build_targets = [] + + if esp32_matrix: + # Parse the ESP32 matrix entries + esp32_platforms = esp32_matrix.group(1).strip() + # Extract quoted strings + esp32_targets = re.findall(r'"([^"]+)"', esp32_platforms) + build_targets.extend(esp32_targets) + + if rp2040_matrix: + # Parse the RP2040 matrix entries + rp2040_platforms = rp2040_matrix.group(1).strip() + # Extract quoted strings + rp2040_targets = re.findall(r'"([^"]+)"', rp2040_platforms) + build_targets.extend(rp2040_targets) + + print(f"Found {len(build_targets)} build targets in workflow file:") + for target in build_targets: + print(f" - {target}") + + return build_targets + + except Exception as e: + print(f"Error extracting build targets from workflow: {e}") + import traceback + traceback.print_exc() + return [] + +def locate_partition_file(partition_name, fqbn=None): + """ + Locate the partition CSV file based on name in various Arduino BSP directories + """ + # First try using arduino-cli if FQBN is provided + if fqbn: + cli_partition_file = locate_arduino_cli_partition_file(fqbn, partition_name) + if cli_partition_file: + return cli_partition_file + + # If no partition name is provided or not found, and it's an ESP32 board, + # try to find a TinyUF2 partition by fetching boards.txt from GitHub + if (not partition_name or partition_name.lower() == "default") and fqbn and "esp32" in fqbn.lower(): + print(f"No specific partition scheme found, looking for TinyUF2 partition for ESP32...") + + # Extract board ID from FQBN + fqbn_parts = fqbn.split(':') + if len(fqbn_parts) >= 3: + vendor, arch, board_id = fqbn_parts[:3] + + # Fetch board info from GitHub + board_info = fetch_board_info_from_github(board_id) + if board_info: + flash_size = board_info.get("flash_size") + if flash_size: + print(f"Detected flash size from boards.txt: {flash_size}MB") + + # Check if a specific partition scheme is specified in the FQBN + partition_scheme = None + if len(fqbn_parts) > 3 and '=' in fqbn_parts[3]: + params = fqbn_parts[3].split(',') + for param in params: + if '=' in param: + key, value = param.split('=', 1) + if key == 'PartitionScheme': + partition_scheme = value + break + + # If no specific scheme in FQBN, use the default from boards.txt + if not partition_scheme: + partition_scheme = board_info.get("default_partition") + + if partition_scheme: + print(f"Using partition scheme: {partition_scheme}") + + # Check if this is a TinyUF2 partition or filter for TinyUF2 partitions + if "tinyuf2" in partition_scheme.lower(): + # This is already a TinyUF2 partition, fetch it + partition_file = fetch_partition_file_from_github(partition_scheme) + if partition_file: + return partition_file + else: + # Try to find a TinyUF2 partition that matches the flash size + tinyuf2_partition = find_matching_tinyuf2_partition(flash_size) + if tinyuf2_partition: + return tinyuf2_partition + + # Then try local paths + potential_paths = [ + # ESP32 Arduino core + Path(r"C:\dev\arduino\arduino-esp32\tools\partitions"), + # User's Arduino15 folder - we can't access this directly due to permissions, + # but we can look in known locations in accessible directories + Path(r"C:\dev\arduino\Adafruit_Wippersnapper_Arduino\tools\partitions"), + Path(r"C:\dev\arduino\tinyuf2\ports\esp32s2\boards\adafruit_feather_esp32s2\partitions"), + # CI-Arduino repo + Path(r"C:\dev\arduino\ci-arduino\partitions") + ] + + for base_path in potential_paths: + if not base_path.exists(): + continue + + # Try exact name match + exact_path = base_path / f"{partition_name}.csv" + if exact_path.exists(): + print(f"Found partition file in local path: {exact_path}") + return exact_path + + # Try case-insensitive search + for file_path in base_path.glob("*.csv"): + if file_path.stem.lower() == partition_name.lower(): + print(f"Found partition file in local path (case-insensitive): {file_path}") + return file_path + + print(f"Warning: Could not find partition file for {partition_name}") + return None + +def find_tinyuf2_partition_for_board(boards_path, board_id): + """ + Find a TinyUF2 partition for the given board ID by parsing boards.txt + """ + try: + with open(boards_path, 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + # Check if this is the board we're looking for + if line.startswith(board_id + '.name='): + # Look for the build.partitions parameter + for line in f: + line = line.strip() + if line.startswith(board_id + '.build.partitions='): + partition_name = line.split('=')[1] + if partition_name.startswith('tinyuf2'): + # Avoid OTA versions by default + if 'ota' not in partition_name.lower(): + return locate_partition_file(partition_name) + except Exception as e: + print(f"Error finding TinyUF2 partition for board {board_id}: {e}") + return None + +def locate_arduino_cli_partition_file(fqbn, partition_name): + """ + Use arduino-cli to locate partition files from installed platforms + """ + try: + # Parse FQBN to get package, architecture + fqbn_parts = fqbn.split(":") + if len(fqbn_parts) < 3: + print(f"Warning: Invalid FQBN format: {fqbn}") + return None + + package, architecture = fqbn_parts[0], fqbn_parts[1] + + # Get arduino-cli config to find package paths + result = subprocess.run( + ["arduino-cli", "config", "dump", "--format", "json"], + capture_output=True, text=True, check=False + ) + + if result.returncode != 0 or result.stdout == '{\n "config": {}\n}\n': + print(f"Warning: arduino-cli config failed: {result.stderr}") + if package == "espressif": + package = "esp32" + # Try to install the core if it's not found + print(f"Attempting to install core {package}:{architecture} with BSP URLs: {BSP_URLS}") + install_cmd = [ + "arduino-cli", "core", "install", + f"{package}:{architecture}", + "--additional-urls", BSP_URLS + ] + install_result = subprocess.run(install_cmd, capture_output=True, text=True, check=False) + + if install_result.returncode != 0: + print(f"Warning: Failed to install core: {install_result.stderr}") + return None + + # Try getting config again + result = subprocess.run( + ["arduino-cli", "config", "dump", + "--additional-urls", BSP_URLS, "--format", "json"], + capture_output=True, text=True, check=False + ) + + if result.returncode != 0: + print(f"Warning: arduino-cli config still failed after core install: {result.stderr}") + return None + + config = json.loads(result.stdout) + + # Extract data directory where packages are installed + data_dir = config.get("directories", {}).get("data", None) + if not data_dir: + print("Warning: Could not find arduino-cli data directory") + return None + + # Construct path to partition files + # Typical path is: /packages//hardware///tools/partitions/ + package_dir = Path(data_dir) / "packages" / package + + # Find the latest version directory + hw_dir = package_dir / "hardware" / architecture + if not hw_dir.exists(): + print(f"Warning: Hardware directory not found: {hw_dir}") + return None + + # Get latest version directory + versions = sorted([d for d in hw_dir.iterdir() if d.is_dir()], + key=lambda x: [int(p) if p.isdigit() else p for p in x.name.split('.')], + reverse=True) + + if not versions: + print(f"Warning: No versions found in {hw_dir}") + return None + + latest_version = versions[0] + + # Check for partitions directory + partitions_dir = latest_version / "tools" / "partitions" + if not partitions_dir.exists(): + print(f"Warning: Partitions directory not found: {partitions_dir}") + return None + + # Look for the partition file + partition_file = partitions_dir / f"{partition_name}.csv" + if partition_file.exists(): + print(f"Found partition file via arduino-cli: {partition_file}") + return partition_file + + # Try case-insensitive search + for file_path in partitions_dir.glob("*.csv"): + if file_path.stem.lower() == partition_name.lower(): + print(f"Found partition file via arduino-cli (case-insensitive): {file_path}") + return file_path + + print(f"Warning: Partition file {partition_name}.csv not found in {partitions_dir}") + return None + + except Exception as e: + print(f"Error locating arduino-cli partition file: {e}") + return None + +def clone_or_update_ci_arduino(): + """ + Clone or update the ci-arduino repository from the specified branch + """ + try: + ci_arduino_path = Path("ci-arduino") + + # Check if the directory exists + if ci_arduino_path.exists() and ci_arduino_path.is_dir(): + print("Updating existing ci-arduino repository...") + subprocess.run( + ["git", "fetch", "origin"], + cwd=str(ci_arduino_path), + check=True + ) + subprocess.run( + ["git", "checkout", "ci-wippersnapper"], + cwd=str(ci_arduino_path), + check=True + ) + subprocess.run( + ["git", "pull", "origin", "ci-wippersnapper"], + cwd=str(ci_arduino_path), + check=True + ) + else: + print("Cloning ci-arduino repository...") + subprocess.run( + ["git", "clone", "https://github.com/adafruit/ci-arduino.git", "-b", "ci-wippersnapper"], + check=True + ) + + print("ci-arduino repository is ready") + return True + except Exception as e: + print(f"Error setting up ci-arduino repository: {e}") + return False + +def fetch_ci_arduino_parameters(use_git=True): + """ + Fetch board-specific parameters from the ci-arduino repository including FQBN and partition scheme + """ + try: + # If use_git is True, try to clone/update the repository first + if use_git: + clone_success = clone_or_update_ci_arduino() + if clone_success: + ci_arduino_path = "ci-arduino" + else: + print("Falling back to local path...") + ci_arduino_path = os.environ.get("CI_ARDUINO_PATH", "../ci-arduino") + else: + ci_arduino_path = os.environ.get("CI_ARDUINO_PATH", "../ci-arduino") + + platforms_file = Path(ci_arduino_path) / "all_platforms.py" + + if not platforms_file.exists(): + print(f"Warning: Could not find ci-arduino platforms file at {platforms_file}") + return {} + + # First, get the build targets from the workflow file + build_targets = extract_build_targets_from_workflow() + build_targets_set = set(build_targets) + + # Parse the platforms file to extract FQBN and partition information + board_params = {} + with open(platforms_file, 'r') as f: + content = f.read() + + # Extract the ALL_PLATFORMS dictionary + import re + platforms_match = re.search(r'ALL_PLATFORMS\s*=\s*{(.*?)}', content, re.DOTALL | re.MULTILINE) + if not platforms_match: + print("Warning: Could not find ALL_PLATFORMS dictionary in the file") + return {} + + platforms_content = platforms_match.group(1) + + # Parse board entries + board_entries = re.findall(r'["\']([\w\d_]+)["\'][\s]*:[\s]*\[(.*?)\]', platforms_content, re.DOTALL) + + for board_name, board_config in board_entries: + # Skip all platform groupings + if board_name in ["main_platforms", "arcada_platforms", "wippersnapper_platforms", "rp2040_platforms"]: + continue + + # Only process boards explicitly mentioned in build targets + if board_name in build_targets_set: + # Direct match with a build target + process_platform_entry(board_name, board_config, board_params) + + return board_params + except Exception as e: + print(f"Error fetching ci-arduino parameters: {e}") + import traceback + traceback.print_exc() + return {} + +def process_platform_entry(board_name, board_config, board_params): + """Process a single platform entry from the ALL_PLATFORMS dictionary""" + # Extract FQBN + fqbn_match = re.search(r'["\']([^"\']+?:[^"\']+?:[^"\']+?(?::[^"\']+?)?)["\']', board_config) + if fqbn_match: + fqbn = fqbn_match.group(1) + + # Check for custom core URL in ALL_PLATFORMS + core_url_match = re.search(r'None,\s*["\']([^"\']+?)["\']', board_config) + custom_core_url = None + if core_url_match: + custom_core_url = core_url_match.group(1) + + # Parse FQBN for partition scheme + partition_scheme = parse_fqbn_for_partition(fqbn) + + # If we found a partition scheme, look for the CSV file + fat_size = DEFAULT_FAT_SIZE + fat_offset = None + + if partition_scheme: + if isinstance(partition_scheme, dict): + # Handle RP2040 special case + fat_size = partition_scheme.get("fat_size", DEFAULT_FAT_SIZE) + else: + # For ESP32, find and parse the partition file + partition_file = locate_partition_file(partition_scheme, fqbn) + if partition_file: + fat_info = parse_partition_csv(partition_file) + if fat_info["size"]: + fat_size = fat_info["size"] + if fat_info["offset"]: + fat_offset = fat_info["offset"] + # If no partition scheme was found but it's an ESP32 board, try to use TinyUF2 partition + elif "esp32" in fqbn.lower(): + print(f"No partition scheme found for {board_name}, trying to use TinyUF2 partition...") + partition_file = locate_partition_file(None, fqbn) + if partition_file: + fat_info = parse_partition_csv(partition_file) + if fat_info["size"]: + fat_size = fat_info["size"] + if fat_info["offset"]: + fat_offset = fat_info["offset"] + # Use tinyuf2_noota as the partition scheme name for reference + partition_scheme = "tinyuf2_noota" + else: + # If no TinyUF2 partition found, use default ESP32 values from PARTITION_SIZES + fat_size = PARTITION_SIZES.get("tinyuf2_noota", DEFAULT_FAT_SIZE) + print(f"No TinyUF2 partition file found, using default size: {fat_size/1024/1024:.2f}MB") + + board_params[board_name.lower()] = { + "fqbn": fqbn, + "partition_scheme": partition_scheme, + "fat_size": fat_size, + "fat_base_addr": fat_offset if fat_offset is not None else + (0x310000 if "esp32" in fqbn.lower() else 0x10100000), + "custom_core_url": custom_core_url + } + print(f"Found board config for {board_name}: FQBN={fqbn}, Partition={partition_scheme}, " + f"FAT size={fat_size/1024/1024:.2f}MB, FAT offset={hex(board_params[board_name.lower()]['fat_base_addr'])}") + +def parse_fqbn_for_partition(fqbn): + """ + Parse FQBN string to extract partition scheme + FQBN format is typically: vendor:architecture:board:param1=value1,param2=value2,... + """ + if not fqbn: + return None + + parts = fqbn.split(':') + if len(parts) < 3: + return None + + # Check if there are parameters + if len(parts) > 3 and '=' in parts[3]: + params = parts[3].split(',') + for param in params: + if '=' in param: + key, value = param.split('=', 1) + if key.lower() == 'partitionscheme': + return value + + # Check specifically for some known patterns + board_name = parts[2].lower() + if "esp32" in board_name: + # For ESP32 boards without explicit partition scheme, check if it's a known one + if "s2" in board_name or "s3" in board_name: + return "default" # Most S2/S3 boards use default partition + elif "c3" in board_name or "c6" in board_name: + return "min_spiffs" # C3/C6 often use min_spiffs + + # For RP2040 boards, there's no standard partition scheme in the FQBN + # but we can look for flash size indicators + if "rp2040" in board_name or "rp2350" in board_name or "rp2" in board_name: + # Check if there's a flash parameter + if len(parts) > 3: + params = parts[3].split(',') + for param in params: + if 'flash=' in param: + flash_param = param.split('=')[1] + # Format is typically total_offset + flash_values = flash_param.split('_') + if len(flash_values) > 0: + try: + total_flash = int(flash_values[0]) + # Calculate usable filesystem size (conservative estimate) + # Typically 25% of flash is used for filesystem in RP2040 + return {"flash_size": total_flash, "fat_size": total_flash // 4} + except ValueError: + pass + + return None + +def parse_partition_csv_claude(partition_file_path): + """ + Parse an ESP32 partition CSV file to extract the FAT partition size and offset + """ + try: + with open(partition_file_path, 'r') as f: + lines = f.readlines() + + fat_info = {"size": None, "offset": None, "name": None} + + for line in lines: + line = line.strip() + if line.startswith('#') or not line: + continue + + parts = [p.strip() for p in line.split(',')] + if len(parts) < 5: + continue + + name, type_, subtype, offset, size = parts[:5] + # Look for FAT/SPIFFS/FFat partitions + if (type_.lower() == 'data' and + subtype.lower() in ['fat', 'spiffs', 'ffat']): + + fat_info["name"] = name + + # Parse the offset + offset_str = offset.strip() + if offset_str.startswith('0x'): + fat_info["offset"] = int(offset_str, 16) + else: + try: + fat_info["offset"] = int(offset_str) + except ValueError: + # Some partition tables use empty offset which means "next available" + fat_info["offset"] = None + + # Parse the size + size_str = size.strip() + if not size_str: # Empty size means "rest of flash" + fat_info["size"] = None + elif size_str.lower().endswith('k'): + fat_info["size"] = int(size_str[:-1]) * 1024 + elif size_str.lower().endswith('m'): + fat_info["size"] = int(size_str[:-1]) * 1024 * 1024 + elif size_str.startswith('0x'): + fat_info["size"] = int(size_str, 16) + else: + fat_info["size"] = int(size_str) + + if fat_info["size"]: + print(f"Found FAT partition in CSV: {name}, size: {fat_info['size'] / 1024 / 1024:.2f}MB, offset: {hex(fat_info['offset']) if fat_info['offset'] else 'auto'}") + else: + print(f"Found FAT partition in CSV: {name}, size: auto, offset: {hex(fat_info['offset']) if fat_info['offset'] else 'auto'}") + + return fat_info + + return fat_info + except Exception as e: + print(f"Error parsing partition CSV: {e}") + return {"size": None, "offset": None, "name": None} + +def parse_partition_csv(partition_file_path): + c = parse_partition_csv_claude(partition_file_path) + if c is not None: + return c + return None + +def fetch_board_info_from_github(board_id): + """ + Fetch board information from boards.txt on GitHub + """ + print(f"Fetching board info for {board_id} from GitHub...") + try: + # Fetch boards.txt from GitHub + response = requests.get(ESP32_BOARDS_TXT_URL, timeout=10) + response.raise_for_status() + + # Parse boards.txt to find the board + board_info = {} + board_prefix = f"{board_id}." + for line in response.text.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + + if '=' in line: + key, value = line.split('=', 1) + if key.startswith(board_prefix): + # Extract the property name after the board prefix + prop = key[len(board_prefix):] + board_info[prop] = value + + if board_info: + # Extract flash size from build.flash_size if available + flash_size = None + if "build.flash_size" in board_info: + flash_size_str = board_info["build.flash_size"] + # Extract numeric part of flash size + size_match = re.match(r'(\d+)[mM]', flash_size_str) + if size_match: + flash_size = int(size_match.group(1)) + + # Extract default partition scheme + default_partition = None + if "build.partitions" in board_info: + default_partition = board_info["build.partitions"] + + return { + "flash_size": flash_size, + "default_partition": default_partition, + "raw_info": board_info + } + else: + print(f"Board {board_id} not found in boards.txt") + return None + except Exception as e: + print(f"Error fetching board info from GitHub: {e}") + return None + +def fetch_partition_file_from_github(partition_name): + """ + Fetch a partition file from the ESP32 Arduino core GitHub repository + """ + print(f"Fetching partition file {partition_name} from GitHub...") + try: + # Ensure the partition name has a .csv extension + if not partition_name.endswith('.csv'): + partition_name += '.csv' + + # Fetch the partition file + url = f"{ESP32_PARTITIONS_BASE_URL}{partition_name}" + response = requests.get(url, timeout=10) + response.raise_for_status() + + # Save to a temporary file + with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as temp_file: + temp_file.write(response.content) + return temp_file.name + except Exception as e: + print(f"Error fetching partition file from GitHub: {e}") + return None + +def find_matching_tinyuf2_partition(flash_size): + """ + Find a TinyUF2 partition that matches the flash size + """ + print(f"Looking for a TinyUF2 partition for {flash_size}MB flash...") + try: + # Use GitHub API to fetch the list of files in the partitions directory + response = requests.get(ESP32_GITHUB_API_PARTITIONS_URL, timeout=10) + response.raise_for_status() + + # Parse the JSON response to get the file list + files_data = response.json() + partition_files = [] + + # Extract file names from the response + for file_data in files_data: + if file_data.get("type") == "file" and file_data.get("name", "").endswith(".csv"): + partition_files.append(file_data.get("name")) + + print(f"Found {len(partition_files)} partition files") + + # Filter for TinyUF2 partitions + tinyuf2_partitions = [p for p in partition_files if "tinyuf2" in p.lower()] + print(f"Found {len(tinyuf2_partitions)} TinyUF2 partition files: {tinyuf2_partitions}") + + # Prioritize normal TinyUSB partitions over no-ota versions + normal_partitions = [p for p in tinyuf2_partitions if "noota" not in p.lower()] + if normal_partitions: + tinyuf2_partitions = normal_partitions + print(f"Filtered to {len(tinyuf2_partitions)} normal TinyUSB partitions: {tinyuf2_partitions}") + else: + print("No normal TinyUSB partitions found, falling back to no-ota versions") + + # Find a partition that matches the flash size + if flash_size: + size_str = f"{flash_size}mb" + matching_partitions = [p for p in tinyuf2_partitions if size_str in p.lower()] + if matching_partitions: + print(f"Found matching TinyUF2 partition for {flash_size}MB flash: {matching_partitions[0]}") + # Use the first matching partition + return fetch_partition_file_from_github(matching_partitions[0]) + + # If no exact match, try to find the closest match + if flash_size > 8: + # Look for 8MB partitions if we can't find 16MB + fallback_size = 8 + elif flash_size > 4: + # Look for 4MB partitions if we can't find 8MB + fallback_size = 4 + else: + # Default to 4MB + fallback_size = 4 + + fallback_size_str = f"{fallback_size}mb" + fallback_partitions = [p for p in tinyuf2_partitions if fallback_size_str in p.lower()] + if fallback_partitions: + print(f"No exact match for {flash_size}MB, using fallback {fallback_size}MB partition: {fallback_partitions[0]}") + return fetch_partition_file_from_github(fallback_partitions[0]) + + # If no match by size, use the first available TinyUF2 partition + if tinyuf2_partitions: + print(f"No size-specific match found, using first available TinyUSB partition: {tinyuf2_partitions[0]}") + return fetch_partition_file_from_github(tinyuf2_partitions[0]) + + print("No TinyUSB partitions found") + return None + except Exception as e: + print(f"Error finding matching TinyUSB partition: {e}") + return None + +def locate_uf2conv_script(): + """ + Locate the uf2conv.py script in common locations + + Returns: + Path to the uf2conv.py script if found, None otherwise + """ + # Check common Arduino ESP32 core locations + common_locations = [ + # Arduino IDE default location + Path(os.path.expanduser("~")) / "AppData" / "Local" / "Arduino15" / "packages" / "esp32" / "tools" / "uf2conv.py", + # Manual installation locations + Path("C:\") / "dev" / "arduino" / "arduino-esp32" / "tools" / "uf2conv.py", + Path("C:\") / "Users" / os.getlogin() / "Arduino" / "hardware" / "esp32" / "esp32" / "tools" / "uf2conv.py", + # Arduino CLI default location + Path(os.path.expanduser("~")) / ".arduino15" / "packages" / "esp32" / "tools" / "uf2conv.py" + ] + + # Check each location + for location in common_locations: + if location.exists(): + print(f"Found uf2conv.py at: {location}") + return location + + # Check if it's in PATH + for path in os.environ["PATH"].split(os.pathsep): + script_path = Path(path) / "uf2conv.py" + if script_path.exists(): + print(f"Found uf2conv.py in PATH at: {script_path}") + return script_path + + print("Warning: Could not locate uf2conv.py. Please ensure the Arduino ESP32 core is installed.") + return None + +if __name__ == "__main__": + main() diff --git a/offline.html b/offline.html new file mode 100644 index 0000000..505b5b3 --- /dev/null +++ b/offline.html @@ -0,0 +1,634 @@ + + + + + + Wippersnapper Configuration Builder + + + +
+

Wippersnapper Configuration Builder

+ +
+ + +
+

Loading Wippersnapper Data

+
+

Loading board and component definitions...

+
+ +
+ + +
+ +
+
+

1. Select Board

+ + + +
+ + + + + + + + + + + + +
+ +
+
+

Import Configuration

+

Import a previously saved configuration file:

+ + + +

Or paste your configuration JSON here:

+ + + +
+

Export Configuration

+

Export the current configuration to a file:

+ +
+ +
+

Add Custom Board

+

Add a custom board to the boards collection (for boards without definition.json files):

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + + + + + + + + + + + + + diff --git a/offline.js b/offline.js new file mode 100644 index 0000000..fe0bd60 --- /dev/null +++ b/offline.js @@ -0,0 +1,129 @@ +const BOARDS_JSON_URL="https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/main/wippersnapper_boards.json",COMPONENTS_JSON_URL="https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/main/wippersnapper_components.json",appState={boardsData:null,componentsData:null,isLoading:!1,loadError:null,selectedBoard:null,companionBoard:null,sdCardCS:null,rtcType:"soft",statusLEDBrightness:.5,i2cBuses:[],i2cMultiplexers:[],selectedComponents:[],usedPins:new Set,nextComponentId:1};async function loadWippersnapperData(){try{appState.isLoading=!0,console.log("Loading Wippersnapper data from local file copy");const e=document.createElement("script");e.id="jsonBoardObject",e.src="wippersnapper_boards.js",document.body.appendChild(e),console.log("Loaded boards data from local file copy");const a=document.createElement("script");a.id="jsonComponentsObject",a.src="wippersnapper_components.js",document.body.appendChild(a),console.log("Loaded components data from local file copy"),await new Promise(e=>setTimeout(e,1e3));const s=window.jsonBoardObject,t=window.jsonComponentsObject;return appState.boardsData=s.boards,appState.componentsData=t.components,document.body.removeChild(a),document.body.removeChild(e),appState.componentsData.i2c&&(appState.componentsData.i2c.push({id:"pca9546",name:"PCA9546 I2C Multiplexer",address:"0x70",addresses:["0x70","0x71","0x72","0x73","0x74","0x75","0x76","0x77"],dataTypes:[],channels:4}),appState.componentsData.i2c.push({id:"pca9548",name:"PCA9548 I2C Multiplexer",address:"0x70",addresses:["0x70","0x71","0x72","0x73","0x74","0x75","0x76","0x77"],dataTypes:[],channels:8}),appState.componentsData.i2c.push({id:"tca9546",name:"TCA9546 I2C Multiplexer",address:"0x70",addresses:["0x70","0x71","0x72","0x73","0x74","0x75","0x76","0x77"],dataTypes:[],channels:4}),appState.componentsData.i2c.push({id:"tca9548",name:"TCA9548 I2C Multiplexer",address:"0x70",addresses:["0x70","0x71","0x72","0x73","0x74","0x75","0x76","0x77"],dataTypes:[],channels:8})),initializeUI(),console.log("Successfully loaded Wippersnapper data",{boards:Object.keys(appState.boardsData).length,components:Object.keys(appState.componentsData).filter(e=>!e.endsWith("_metadata")).reduce((e,a)=>e+appState.componentsData[a].length,0)}),appState.isLoading=!1,!0}catch(e){return console.error("Error loading Wippersnapper data:",e),appState.loadError=e.message,appState.isLoading=!1,showLoadError(e.message),!1}}function initializeUI(){populateBoardSelect(),attachEventListeners()}function populateBoardSelect(){const e=document.getElementById("board-select");e.innerHTML="";const a=Object.entries(appState.boardsData).filter(([e,a])=>["uf2","web-native-usb"].includes(a.installMethod)),s=a.sort((e,a)=>{const s=e[1].vendor||"",t=a[1].vendor||"";return s===t?e[1].displayName.localeCompare(a[1].displayName):s.localeCompare(t)}),t={};s.forEach(([e,a])=>{const s=a.vendor||"Other";t[s]||(t[s]=[]),t[s].push([e,a])}),Object.entries(t).forEach(([a,s])=>{const t=document.createElement("optgroup");t.label=a,s.forEach(([e,a])=>{const s=document.createElement("option");s.value=e,s.textContent=a.displayName,t.appendChild(s)}),e.appendChild(t)})}function convertBoardDataToConfig(e){const a=appState.boardsData[e];if(!a)return null;const s=a.pins.map(e=>e.number).filter(e=>!isNaN(e)),t={referenceVoltage:a.referenceVoltage,totalGPIOPins:a.totalGPIOPins,totalAnalogPins:a.totalAnalogPins||0,defaultI2C:{scl:a.defaultI2C.SCL,sda:a.defaultI2C.SDA},pins:s,displayName:a.displayName,image:a.image};return t}function convertComponentsDataToConfig(){const e={i2c:[],ds18x20:[],pin:[],pixel:[],pwm:[],servo:[],uart:[]};return appState.componentsData.i2c&&appState.componentsData.i2c.forEach(a=>{e.i2c.push({id:a.id,name:a.name,address:a.address||"0x00",addresses:a.addresses||[a.address||"0x00"],dataTypes:a.dataTypes||[],channels:a.channels||0})}),appState.componentsData.ds18x20&&appState.componentsData.ds18x20.forEach(a=>{e.ds18x20.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),appState.componentsData.pin&&appState.componentsData.pin.forEach(a=>{e.pin.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),appState.componentsData.pixel&&appState.componentsData.pixel.forEach(a=>{e.pixel.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),appState.componentsData.pwm&&appState.componentsData.pwm.forEach(a=>{e.pwm.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),appState.componentsData.servo&&appState.componentsData.servo.forEach(a=>{e.servo.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),appState.componentsData.uart&&appState.componentsData.uart.forEach(a=>{e.uart.push({id:a.id,name:a.name,dataTypes:a.dataTypes||[]})}),e}function attachEventListeners(){document.getElementById("board-select").addEventListener("change",function(){const e=this.value;if(!e)return document.getElementById("board-details").classList.add("hidden"),void hideSubsequentSections();const a=convertBoardDataToConfig(e);appState.selectedBoard={id:e,...a},document.getElementById("ref-voltage").textContent=a.referenceVoltage,document.getElementById("total-gpio").textContent=a.totalGPIOPins,document.getElementById("total-analog").textContent=a.totalAnalogPins,document.getElementById("default-scl").textContent=a.defaultI2C.scl,document.getElementById("default-sda").textContent=a.defaultI2C.sda,document.getElementById("board-details").classList.remove("hidden");const s=document.getElementById("board-image");s&&(a.image?(s.src=a.image.startsWith("http")?a.image:"https://raw.githubusercontent.com/adafruit/Wippersnapper_Boards/refs/heads/main/"+a.image,s.classList.remove("hidden")):s.classList.add("hidden")),appState.i2cBuses=[{id:"default",scl:a.defaultI2C.scl,sda:a.defaultI2C.sda}],document.getElementById("default-i2c-scl").textContent=a.defaultI2C.scl,document.getElementById("default-i2c-sda").textContent=a.defaultI2C.sda,appState.usedPins.add(a.defaultI2C.scl),appState.usedPins.add(a.defaultI2C.sda),document.getElementById("companion-board-section").classList.remove("hidden"),resetSubsequentSelections(),initializeManualConfig(),populatePinsLists();const t=convertComponentsDataToConfig();populateComponentLists(t)})}function showLoadError(e){let a=document.getElementById("load-error");a||(a=document.createElement("div"),a.id="load-error",a.style.backgroundColor="#ffdddd",a.style.color="#cc0000",a.style.padding="15px",a.style.margin="15px 0",a.style.borderRadius="5px",document.body.insertBefore(a,document.body.firstChild)),a.innerHTML=` +

Error Loading Data

+

${e}

+

Please check that the JSON files are available and properly formatted.

+ + `}function retryLoading(){const e=document.getElementById("load-error");e&&e.remove(),loadWippersnapperData()}document.addEventListener("DOMContentLoaded",function(){loadWippersnapperData()});window.jsonBoardObject={boards:{"feather-esp32":{boardName:"feather-esp32",mcuName:"ESP32",referenceVoltage:3.3,displayName:"Adafruit Feather HUZZAH ESP32",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/3405",documentationURL:"https://learn.adafruit.com/adafruit-huzzah32-esp32-feather",installMethod:"web",pins:[{number:13,name:"D13",displayName:"D13 (LED BUILT-IN)",hasPWM:!0,hasServo:!1},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"D27",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"D15",hasPWM:!0,hasServo:!0},{number:32,name:"D32",displayName:"D32",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"D26 (A0)",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"D25 (A1)",hasPWM:!0,hasServo:!0},{number:34,name:"D34",displayName:"D34 (A2)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (A3)",hasPWM:!1,hasServo:!1},{number:36,name:"D36",displayName:"D36 (A4)",hasPWM:!1,hasServo:!1},{number:4,name:"D4",displayName:"D4 (A5)",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16 (UART RX)",hasPWM:!1,hasServo:!1},{number:17,name:"D17",displayName:"D17 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A26",displayName:"A0",direction:"INPUT"},{name:"A25",displayName:"A1",direction:"INPUT"},{name:"A34",displayName:"A2",direction:"INPUT"},{name:"A39",displayName:"A3",direction:"INPUT"},{name:"A36",displayName:"A4",direction:"INPUT"},{name:"A4",displayName:"A5",direction:"INPUT"},{name:"A14",displayName:"A6",direction:"INPUT"},{name:"A32",displayName:"A7",direction:"INPUT"},{name:"A15",displayName:"A8",direction:"INPUT"},{name:"A33",displayName:"A9",direction:"INPUT"},{name:"A27",displayName:"A10",direction:"INPUT"},{name:"A12",displayName:"A11",direction:"INPUT"},{name:"A13",displayName:"A12",direction:"INPUT"},{name:"A35",displayName:"A13 (VBAT)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:22,SDA:23},image:"boards/feather-esp32/image.svg",totalAnalogPins:14,i2cPorts:[{i2cPortId:0,SDA:23,SCL:22}],totalGPIOPins:15},"feather-esp32-v2":{boardName:"feather-esp32-v2",mcuName:"ESP32",referenceVoltage:3.3,displayName:"Adafruit Feather ESP32 V2",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5400",documentationURL:"https://learn.adafruit.com/adafruit-esp32-feather-v2",installMethod:"web",pins:[{number:0,name:"D0",displayName:"D0 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:7,name:"D7",displayName:"D7 (UART RX)",hasPWM:!1,hasServo:!1},{number:8,name:"D8",displayName:"D8 (UART TX)",hasPWM:!1,hasServo:!1},{number:13,name:"D13",displayName:"D13 (LED BUILT-IN)",hasPWM:!0,hasServo:!1},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"D27",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"D15",hasPWM:!0,hasServo:!0},{number:32,name:"D32",displayName:"D32",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"D26 (A0)",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"D25 (A1)",hasPWM:!0,hasServo:!0},{number:34,name:"D34",displayName:"D34 (A2)",hasPWM:!0,hasServo:!0},{number:39,name:"D39",displayName:"D39 (A3)",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"D36 (A4)",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4 (A5)",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (SCK)",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"D19 (MOSI)",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"D21 (MISO)",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"D37",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"Button Switch (SW38)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A26",displayName:"A0",direction:"INPUT"},{name:"A25",displayName:"A1",direction:"INPUT"},{name:"A34",displayName:"A2",direction:"INPUT"},{name:"A39",displayName:"A3",direction:"INPUT"},{name:"A36",displayName:"A4",direction:"INPUT"},{name:"A4",displayName:"A5",direction:"INPUT"},{name:"A35",displayName:"VBat Monitor",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:20,SDA:22},image:"boards/feather-esp32-v2/image.svg",totalAnalogPins:7,i2cPorts:[{i2cPortId:0,SDA:22,SCL:20}],totalGPIOPins:21},"feather-esp32c6":{boardName:"feather-esp32c6",mcuName:"esp32c6",referenceVoltage:1.1,displayName:"Adafruit Feather ESP32-C6",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5933",documentationURL:"https://learn.adafruit.com/adafruit-esp32-c6-feather",installMethod:"web",pins:[{number:1,name:"D1",displayName:"IO1 (A0)",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"IO4 (A1)",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"IO6 (A2/D6)",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"IO5 (A3/D5)",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"IO3 (A4)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"IO2 (A5)",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"IO21 (SCK)",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"IO22 (MO)",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"IO23 (MI)",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"IO17 (UART RX)",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"IO16 (UART TX)",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"IO9 (NeoPixel)",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"IO15 (LED)",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"IO14",hasPWM:!0,hasServo:!0},{number:0,name:"D0",displayName:"IO0",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"IO8",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"IO7",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"IO18 (SCL)",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"IO19 (SDA)",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"IO20 (I2C Power)",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A1",displayName:"A0",direction:"INPUT"},{name:"A4",displayName:"A1",direction:"INPUT"},{name:"A6",displayName:"A2",direction:"INPUT"},{name:"A5",displayName:"A3",direction:"INPUT"},{name:"A3",displayName:"A4",direction:"INPUT"},{name:"A2",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:18,SDA:19},image:"boards/feather-esp32c6/image.png",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:19,SCL:18}],totalGPIOPins:20},"feather-esp32s2":{boardName:"feather-esp32s2",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit ESP32-S2 Feather",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5000",documentationURL:"https://learn.adafruit.com/adafruit-esp32-s2-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7 (I2C Power)",hasPWM:!1,hasServo:!1},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:4,SDA:3},image:"boards/feather-esp32s2/image.png",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:3,SCL:4}],totalGPIOPins:16},"feather-esp32s2-reverse-tft":{boardName:"feather-esp32s2-reverse-tft",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"ESP32-S2 Reverse TFT Feather",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5345",documentationURL:"https://learn.adafruit.com/esp32-s2-reverse-tft-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1 Push Button",hasPWM:!1,hasServo:!1},{number:2,name:"D2",displayName:"D2 Push Button",hasPWM:!1,hasServo:!1},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:21,name:"D21",displayName:"D21 (NeoPixel Power Pin)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:4,SDA:3},image:"boards/feather-esp32s2-reverse-tft/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:3,SCL:4}],totalGPIOPins:14},"feather-esp32s2-tft":{boardName:"feather-esp32s2-tft",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit ESP32-S2 TFT Feather",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5300",documentationURL:"https://learn.adafruit.com/adafruit-esp32-s2-tft-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1 (UART TX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2 (UART RX)",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:41,SDA:42},image:"boards/feather-esp32s2-tft/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:42,SCL:41}],totalGPIOPins:15},"feather-esp32s3":{boardName:"feather-esp32s3",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit Feather ESP32-S3 No PSRAM",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5000",documentationURL:"https://learn.adafruit.com/adafruit-esp32-s3-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:4,SDA:3},image:"boards/feather-esp32s3/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:3,SCL:4}],totalGPIOPins:18},"feather-esp32s3-4mbflash-2mbpsram":{boardName:"feather-esp32s3-4mbflash-2mbpsram",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit ESP32-S3 Feather with 4MB Flash 2MB PSRAM",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5477",documentationURL:"https://learn.adafruit.com/adafruit-esp32-s3-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:4,SDA:3},image:"boards/feather-esp32s3-4mbflash-2mbpsram/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:3,SCL:4}],totalGPIOPins:18},"feather-esp32s3-reverse-tft":{boardName:"feather-esp32s3-reverse-tft",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"ESP32-S3 Reverse TFT Feather",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5691",documentationURL:"https://learn.adafruit.com/esp32-s3-reverse-tft-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1 Push Button",hasPWM:!1,hasServo:!1},{number:2,name:"D2",displayName:"D2 Push Button",hasPWM:!1,hasServo:!1},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:38,name:"D38",displayName:"D38 (UART RX)",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:4,SDA:3},image:"boards/feather-esp32s3-reverse-tft/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:3,SCL:4}],totalGPIOPins:13},"feather-esp32s3-tft":{boardName:"feather-esp32s3-tft",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit ESP32-S3 TFT Feather",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5483",documentationURL:"https://learn.adafruit.com/adafruit-esp32-s3-tft-feather",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot Btn)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1 (UART TX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2 (UART RX)",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (LED)",hasPWM:!0,hasServo:!1},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A16",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A14",displayName:"A4",direction:"INPUT"},{name:"A8",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:41,SDA:42},image:"boards/feather-esp32s3-tft/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:42,SCL:41}],totalGPIOPins:16},"feather-esp8266":{boardName:"feather-esp8266",mcuName:"esp8266",referenceVoltage:1.1,displayName:"Feather Huzzah ESP8266",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/2821",documentationURL:"https://learn.adafruit.com/adafruit-feather-huzzah-esp8266",installMethod:"web",pins:[{number:0,name:"D0",displayName:"D0 (Red LED)",hasPWM:!0,hasServo:!1},{number:1,name:"D1",displayName:"D1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2 (Blue LED)",hasPWM:!0,hasServo:!1},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12 (UART RX)",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14 (UART TX)",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"D15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16 - Deep Sleep Wake Pin",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"A0",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:5,SDA:4},image:"boards/feather-esp8266/image.png",totalAnalogPins:1,i2cPorts:[{i2cPortId:0,SDA:4,SCL:5}],totalGPIOPins:10},funhouse:{boardName:"funhouse",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit Funhouse ESP32-S2",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4985",documentationURL:"https://learn.adafruit.com/adafruit-funhouse",installMethod:"web-native-usb",pins:[{number:5,name:"D5",displayName:"Button Up",hasPWM:!1,hasServo:!1},{number:3,name:"D3",displayName:"Button Down",hasPWM:!1,hasServo:!1},{number:4,name:"D4",displayName:"Button Select",hasPWM:!1,hasServo:!1},{number:17,name:"D17",displayName:"D17 (A0)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2 (A1)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"D1 (A2)",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!1,hasServo:!1},{number:7,name:"D7",displayName:"D7",hasPWM:!1,hasServo:!1},{number:8,name:"D8",displayName:"D8",hasPWM:!1,hasServo:!1},{number:9,name:"D9",displayName:"D9",hasPWM:!1,hasServo:!1},{number:10,name:"D10",displayName:"D10",hasPWM:!1,hasServo:!1},{number:11,name:"D11",displayName:"D11",hasPWM:!1,hasServo:!1},{number:12,name:"D12",displayName:"D12",hasPWM:!1,hasServo:!1},{number:13,name:"D13",displayName:"D13",hasPWM:!1,hasServo:!1},{number:14,name:"D14",displayName:"D14 (DotStar Data)",hasPWM:!1,hasServo:!1},{number:15,name:"D15",displayName:"D15 (DotStar Clock)",hasPWM:!1,hasServo:!1},{number:16,name:"D16",displayName:"PIR Sensor",hasPWM:!1,hasServo:!1},{number:42,name:"D42",displayName:"Speaker/Piezo",hasPWM:!0,hasServo:!1},{number:44,name:"D44",displayName:"D44 (UART RX)",hasPWM:!1,hasServo:!1},{number:43,name:"D43",displayName:"D43 (UART TX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A17",displayName:"A0",direction:"INPUT"},{name:"A2",displayName:"A1",direction:"INPUT"},{name:"A1",displayName:"A2",direction:"INPUT"},{name:"A18",displayName:"Light Sensor",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:33,SDA:34},image:"boards/funhouse/image.svg",totalAnalogPins:4,i2cPorts:[{i2cPortId:0,SDA:34,SCL:33}],totalGPIOPins:20},"itsybitsy-esp32":{boardName:"itsybitsy-esp32",mcuName:"ESP32",referenceVoltage:3.3,displayName:"Adafruit ItsyBitsy ESP32",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5890",documentationURL:"https://learn.adafruit.com/adafruit-itsybitsy-esp32",installMethod:"web",pins:[{number:0,name:"D0",displayName:"D0 (NeoPixel)",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"D35 (User BUTTON)",hasPWM:!1,hasServo:!1},{number:13,name:"D13",displayName:"D13 (LED BUILT-IN)",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33",hasPWM:!0,hasServo:!0},{number:32,name:"D32",displayName:"D32",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (!Vhi)",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"SCL",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"SDA",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"RX (UART Receive)",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"TX (UART Transmit)",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"A0",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"A1",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"A2",hasPWM:!0,hasServo:!0},{number:38,name:"D38",displayName:"A3",hasPWM:!1,hasServo:!1},{number:37,name:"D37",displayName:"A4",hasPWM:!1,hasServo:!1},{number:36,name:"D36",displayName:"A5",hasPWM:!1,hasServo:!1},{number:19,name:"D19",displayName:"SCK",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"MOSI / PICO",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"MISO / POCI",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A35",displayName:"D35 (User BUTTON)",direction:"INPUT"},{name:"A33",displayName:"D33",direction:"INPUT"},{name:"A32",displayName:"D32",direction:"INPUT"},{name:"A38",displayName:"A3",direction:"INPUT"},{name:"A37",displayName:"A4",direction:"INPUT"},{name:"A36",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:27,SDA:15},image:"boards/itsybitsy-esp32/image.svg",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:15,SCL:27}],totalGPIOPins:22},magtag:{boardName:"magtag",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit MagTag \"2.9\"",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4800",documentationURL:"https://learn.adafruit.com/adafruit-magtag",installMethod:"uf2",pins:[{number:1,name:"D1",displayName:"D1 (NeoPixel)",hasPWM:!1,hasServo:!1},{number:15,name:"D15",displayName:"Button A",hasPWM:!1,hasServo:!1},{number:14,name:"D14",displayName:"Button B",hasPWM:!1,hasServo:!1},{number:12,name:"D12",displayName:"Button C",hasPWM:!1,hasServo:!1},{number:11,name:"D11",displayName:"Button D",hasPWM:!1,hasServo:!1},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"D18 (A1)",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"Built-in LED",hasPWM:!0,hasServo:!1},{number:43,name:"D43",displayName:"D43 (UART TX)",hasPWM:!1,hasServo:!1},{number:44,name:"D44",displayName:"D44 (UART RX)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A17",displayName:"Piezo Speaker (A0)",direction:"INPUT"},{name:"A10",displayName:"D10",direction:"INPUT"},{name:"A18",displayName:"A1 (D18)",direction:"INPUT"},{name:"A3",displayName:"Light Sensor (A3)",direction:"INPUT"},{name:"A4",displayName:"Voltage Monitor (A4)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:34,SDA:33},image:"boards/magtag/image.svg",totalAnalogPins:5,i2cPorts:[{i2cPortId:0,SDA:33,SCL:34}],totalGPIOPins:10},"metro-m4-airliftlite-tinyusb":{boardName:"metro-m4-airliftlite-tinyusb",mcuName:"samd51j20",referenceVoltage:3.3,displayName:"Metro M4 Airlift Lite",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4000",documentationURL:"https://learn.adafruit.com/adafruit-metro-m4-express-airlift-wifi",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (UART RX)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"D1 (UART TX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 LED",hasPWM:!0,hasServo:!0},{number:40,name:"D40",displayName:"D40 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A0",displayName:"A0",direction:"INPUT"},{name:"A1",displayName:"A1",direction:"INPUT"},{name:"A2",displayName:"A2",direction:"INPUT"},{name:"A3",displayName:"A3",direction:"INPUT"},{name:"A4",displayName:"A4",direction:"INPUT"},{name:"A5",displayName:"A5",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:23,SDA:22},image:"boards/metro-m4-airliftlite-tinyusb/image.png",totalAnalogPins:6,i2cPorts:[{i2cPortId:0,SDA:22,SCL:23}],totalGPIOPins:15},metroesp32s2:{boardName:"metroesp32s2",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit Metro ESP32-S2",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4775",documentationURL:"https://learn.adafruit.com/adafruit-metro-esp32-s2",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"D0 (Boot0 Push Button)",hasPWM:!1,hasServo:!1},{number:1,name:"D1",displayName:"D1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (UART TX)",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6 (UART RX)",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"D15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"D21",hasPWM:!0,hasServo:!0},{number:42,name:"D42",displayName:"Built-in LED",hasPWM:!0,hasServo:!1},{number:45,name:"D45",displayName:"D45 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A0",displayName:"A0",direction:"INPUT"},{name:"A1",displayName:"A1",direction:"INPUT"},{name:"A2",displayName:"A2",direction:"INPUT"},{name:"A3",displayName:"A3",direction:"INPUT"},{name:"A4",displayName:"A4",direction:"INPUT"},{name:"A5",displayName:"A5 (IO5)",direction:"INPUT"},{name:"A6",displayName:"A6 (IO6)",direction:"INPUT"},{name:"A7",displayName:"A7 (IO7)",direction:"INPUT"},{name:"A8",displayName:"A8 (IO8)",direction:"INPUT"},{name:"A9",displayName:"A9 (IO9)",direction:"INPUT"},{name:"A10",displayName:"A10 (IO10)",direction:"INPUT"},{name:"A11",displayName:"A11 (IO11)",direction:"INPUT"},{name:"A12",displayName:"A12 (IO12)",direction:"INPUT"},{name:"A13",displayName:"A13 (IO13)",direction:"INPUT"},{name:"A14",displayName:"A14 (IO14)",direction:"INPUT"},{name:"A15",displayName:"A15 (IO15)",direction:"INPUT"},{name:"A16",displayName:"A16 (IO16)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:34,SDA:33},image:"boards/metroesp32s2/image.svg",totalAnalogPins:17,i2cPorts:[{i2cPortId:0,SDA:33,SCL:34}],totalGPIOPins:20},metroesp32s3:{boardName:"metroesp32s3",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit Metro ESP32-S3",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5500",documentationURL:"https://learn.adafruit.com/adafruit-metro-esp32-s3",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"Built-in Button (Boot0)",hasPWM:!1,hasServo:!1},{number:40,name:"D40",displayName:"D1 (UART TX)",hasPWM:!0,hasServo:!0},{number:41,name:"D41",displayName:"D1 (UART RX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"D9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"D10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"D11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"D12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (Built-in LED)",hasPWM:!0,hasServo:!0},{number:47,name:"D47",displayName:"D47 (SDA)",hasPWM:!0,hasServo:!0},{number:48,name:"D48",displayName:"D48 (SCL)",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"A0 (GPIO14)",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"A1 (GPIO15)",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"A2 (GPIO16)",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"A3 (GPIO17)",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"A4 (GPIO18)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"A5 (GPIO1)",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"D21 (MISO)",hasPWM:!0,hasServo:!0},{number:42,name:"D42",displayName:"D42 (MOSI)",hasPWM:!0,hasServo:!0},{number:39,name:"D39",displayName:"D39 (SCK)",hasPWM:!0,hasServo:!0},{number:46,name:"D46",displayName:"D46 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A14",displayName:"A0 (GPIO14)",direction:"INPUT"},{name:"A15",displayName:"A1 (GPIO15)",direction:"INPUT"},{name:"A16",displayName:"A2 (GPIO16)",direction:"INPUT"},{name:"A17",displayName:"A3 (GPIO17)",direction:"INPUT"},{name:"A18",displayName:"A4 (GPIO18)",direction:"INPUT"},{name:"A1",displayName:"A5 (GPIO1)",direction:"INPUT"},{name:"A2",displayName:"D2 (GPIO2)",direction:"INPUT"},{name:"A3",displayName:"D3 (GPIO3)",direction:"INPUT"},{name:"A4",displayName:"D4 (GPIO4)",direction:"INPUT"},{name:"A5",displayName:"D5 (GPIO5)",direction:"INPUT"},{name:"A6",displayName:"D6 (GPIO6)",direction:"INPUT"},{name:"A7",displayName:"D7 (GPIO7)",direction:"INPUT"},{name:"A8",displayName:"D8 (GPIO8)",direction:"INPUT"},{name:"A9",displayName:"D9 (GPIO9)",direction:"INPUT"},{name:"A10",displayName:"D10 (GPIO10)",direction:"INPUT"},{name:"A11",displayName:"D11 (GPIO11)",direction:"INPUT"},{name:"A12",displayName:"D12 (GPIO12)",direction:"INPUT"},{name:"A13",displayName:"D13 (GPIO13)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:48,SDA:47},image:"boards/metroesp32s3/image.png",totalAnalogPins:18,i2cPorts:[{i2cPortId:0,SDA:47,SCL:48}],totalGPIOPins:27},"microchip-mcp2221":{boardName:"microchip-mcp2221",mcuName:"mcp2221a",referenceVoltage:3.3,displayName:"MCP2221",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4471",documentationURL:"https://learn.adafruit.com/circuitpython-libraries-on-any-computer-with-mcp2221",installMethod:"python",pins:[],analogPins:[{name:"G1",displayName:"ADC1",direction:"INPUT"},{name:"G2",displayName:"ADC2",direction:"INPUT"},{name:"G3",displayName:"ADC3",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:23,SDA:22},image:"boards/microchip-mcp2221/image.png",totalAnalogPins:3,i2cPorts:[{i2cPortId:0,SDA:22,SCL:23}],totalGPIOPins:0},"pyportal-tinyusb":{boardName:"pyportal-tinyusb",mcuName:"samd51j20",referenceVoltage:3.3,displayName:"PyPortal",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4116",documentationURL:"https://learn.adafruit.com/adafruit-pyportal",installMethod:"uf2",pins:[{number:3,name:"D3",displayName:"D3",hasPWM:!1,hasServo:!1},{number:4,name:"D4",displayName:"D4",hasPWM:!1,hasServo:!1},{number:13,name:"D13",displayName:"D13 LED",hasPWM:!1,hasServo:!1},{number:25,name:"D25",displayName:"TFT Backlight",hasPWM:!0,hasServo:!1},{number:50,name:"D50",displayName:"Speaker Enable",hasPWM:!1,hasServo:!1},{number:2,name:"D2",displayName:"D2 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A14",displayName:"A0 (Speaker)",direction:"INPUT"},{name:"A15",displayName:"D3 (A1)",direction:"INPUT"},{name:"A16",displayName:"Light Sensor (A2)",direction:"INPUT"},{name:"A17",displayName:"D4 (A3)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:28,SDA:27},image:"boards/pyportal-tinyusb/image.png",totalAnalogPins:4,i2cPorts:[{i2cPortId:0,SDA:27,SCL:28}],totalGPIOPins:6},"pyportal-titano-tinyusb":{boardName:"pyportal-titano-tinyusb",mcuName:"samd51j20",referenceVoltage:3.3,displayName:"PyPortal Titano",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/4444",documentationURL:"https://learn.adafruit.com/adafruit-pyportal-titano",installMethod:"uf2",pins:[{number:3,name:"D3",displayName:"D3",hasPWM:!1,hasServo:!1},{number:4,name:"D4",displayName:"D4",hasPWM:!1,hasServo:!1},{number:13,name:"D13",displayName:"D13 LED",hasPWM:!1,hasServo:!1},{number:25,name:"D25",displayName:"TFT Backlight",hasPWM:!0,hasServo:!1},{number:50,name:"D50",displayName:"Speaker Enable",hasPWM:!1,hasServo:!1},{number:2,name:"D2",displayName:"D2 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A14",displayName:"A0 (Speaker)",direction:"INPUT"},{name:"A15",displayName:"D3 (A1)",direction:"INPUT"},{name:"A16",displayName:"Light Sensor (A2)",direction:"INPUT"},{name:"A17",displayName:"D4 (A3)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:28,SDA:27},image:"boards/pyportal-titano-tinyusb/image.png",totalAnalogPins:4,i2cPorts:[{i2cPortId:0,SDA:27,SCL:28}],totalGPIOPins:6},"qtpy-esp32":{boardName:"qtpy-esp32",mcuName:"esp32",referenceVoltage:3.3,displayName:"Adafruit QT Py ESP32 Pico",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5395",documentationURL:"https://learn.adafruit.com/adafruit-qt-py-esp32-pico",installMethod:"web",pins:[{number:26,name:"D26",displayName:"A0",hasPWM:!0,hasServo:!1},{number:25,name:"D25",displayName:"A1",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"A2",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"A3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"SDA",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"SCL",hasPWM:!0,hasServo:!0},{number:32,name:"D32",displayName:"D32 (UART TX)",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"MOSI",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"MISO",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"SCK",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7 (UART RX)",hasPWM:!0,hasServo:!0},{number:0,name:"D0",displayName:"Boot Pushbutton",hasPWM:!1,hasServo:!1},{number:5,name:"D5",displayName:"D5 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A26",displayName:"A0 (OUTPUT ONLY!)",direction:"INPUT"},{name:"A25",displayName:"A1",direction:"INPUT"},{name:"A27",displayName:"A2",direction:"INPUT"},{name:"A15",displayName:"A3",direction:"INPUT"},{name:"A4",displayName:"SDA",direction:"INPUT"},{name:"A33",displayName:"SCL",direction:"INPUT"},{name:"A32",displayName:"A32 (UART TX)",direction:"INPUT"},{name:"A7",displayName:"A7 (UART RX)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:19,SDA:22},image:"boards/qtpy-esp32/image.svg",totalAnalogPins:8,i2cPorts:[{i2cPortId:0,SDA:22,SCL:19}],totalGPIOPins:13},"qtpy-esp32c3":{boardName:"qtpy-esp32c3",mcuName:"esp32c3",referenceVoltage:2.6,displayName:"Adafruit QT Py ESP32-C3",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5405",documentationURL:"https://learn.adafruit.com/adafruit-qt-py-esp32-c3-wifi-dev-board",installMethod:"web",pins:[{number:0,name:"D0",displayName:"D0 (A3)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"D1 (A2)",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"D3 (A1)",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"D0 (A0)",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (SDA)",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"D6 (SCL)",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"D7 (MO)",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"D8 (MI)",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"Push-button (D9)",hasPWM:!1,hasServo:!1},{number:10,name:"D10",displayName:"D10 (SCK)",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"D20 (UART TX)",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"D21 (UART RX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"D2 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A0",displayName:"A0",direction:"INPUT"},{name:"A1",displayName:"A1",direction:"INPUT"},{name:"A2",displayName:"A2",direction:"INPUT"},{name:"A3",displayName:"A3",direction:"INPUT"},{name:"A4",displayName:"A4",direction:"INPUT"},{name:"A20",displayName:"A20 (UART TX)",direction:"INPUT"},{name:"A21",displayName:"A21 (UART RX)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:6,SDA:5},image:"boards/qtpy-esp32c3/image.svg",totalAnalogPins:7,i2cPorts:[{i2cPortId:0,SDA:5,SCL:6}],totalGPIOPins:13},"qtpy-esp32s2":{boardName:"qtpy-esp32s2",mcuName:"esp32s2",referenceVoltage:2.6,displayName:"Adafruit QT Py ESP32-S2",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5325",documentationURL:"https://learn.adafruit.com/adafruit-qt-py-esp32-s2",installMethod:"uf2",pins:[{number:18,name:"D18",displayName:"A0",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"A1",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"A2",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"A3",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"SDA",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"SCL",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (UART TX)",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"MOSI",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"MISO",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"SCK",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16 (UART RX)",hasPWM:!0,hasServo:!0},{number:0,name:"D0",displayName:"Boot Pushbutton",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A9",displayName:"A2",direction:"INPUT"},{name:"A8",displayName:"A3",direction:"INPUT"},{name:"A7",displayName:"SDA",direction:"INPUT"},{name:"A6",displayName:"SCL",direction:"INPUT"},{name:"A5",displayName:"A5 (UART TX)",direction:"INPUT"},{name:"A16",displayName:"A16 (UART RX)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:40,SDA:41},image:"boards/qtpy-esp32s2/image.svg",totalAnalogPins:8,i2cPorts:[{i2cPortId:0,SDA:41,SCL:40}],totalGPIOPins:13},"qtpy-esp32s3":{boardName:"qtpy-esp32s3",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit QT Py ESP32-S3 (NO PSRAM)",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5426",documentationURL:"https://learn.adafruit.com/adafruit-qt-py-esp32-s3",installMethod:"uf2",pins:[{number:18,name:"D18",displayName:"A0",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"A1",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"A2",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"A3",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"SDA",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"SCL",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (UART TX)",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"MOSI",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"MISO",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"SCK",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16 (UART RX)",hasPWM:!0,hasServo:!0},{number:0,name:"D0",displayName:"Boot Pushbutton",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A9",displayName:"A2",direction:"INPUT"},{name:"A8",displayName:"A3",direction:"INPUT"},{name:"A7",displayName:"SDA",direction:"INPUT"},{name:"A6",displayName:"SCL",direction:"INPUT"},{name:"A5",displayName:"A5 (UART TX)",direction:"INPUT"},{name:"A16",displayName:"A16 (UART RX)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:40,SDA:41},image:"boards/qtpy-esp32s3/image.svg",totalAnalogPins:8,i2cPorts:[{i2cPortId:0,SDA:41,SCL:40}],totalGPIOPins:13},"qtpy-esp32s3-n4r2":{boardName:"qtpy-esp32s3-n4r2",mcuName:"esp32s3",referenceVoltage:2.6,displayName:"Adafruit QT Py S3 (2MB PSRAM)",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5700",documentationURL:"https://learn.adafruit.com/adafruit-qt-py-esp32-s3",installMethod:"uf2",pins:[{number:18,name:"D18",displayName:"A0",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"A1",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"A2",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"A3",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"SDA",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"SCL",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"D5 (UART TX)",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"MOSI",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"MISO",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"SCK",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"D16 (UART RX)",hasPWM:!0,hasServo:!0},{number:0,name:"D0",displayName:"Boot Pushbutton",hasPWM:!1,hasServo:!1},{number:39,name:"D39",displayName:"D39 (NeoPixel)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A18",displayName:"A0",direction:"INPUT"},{name:"A17",displayName:"A1",direction:"INPUT"},{name:"A9",displayName:"A2",direction:"INPUT"},{name:"A8",displayName:"A3",direction:"INPUT"},{name:"A7",displayName:"SDA",direction:"INPUT"},{name:"A6",displayName:"SCL",direction:"INPUT"},{name:"A5",displayName:"A5 (UART TX)",direction:"INPUT"},{name:"A16",displayName:"A16 (UART RX)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:40,SDA:41},image:"boards/qtpy-esp32s3-n4r2/image.svg",totalAnalogPins:8,i2cPorts:[{i2cPortId:0,SDA:41,SCL:40}],totalGPIOPins:13},"rpi-pico-2w":{boardName:"rpi-pico-2w",mcuName:"rp2350",referenceVoltage:3.3,displayName:"Raspberry Pi Pico 2W",vendor:"Raspberry Pi",productURL:"https://www.adafruit.com/product/6087",documentationURL:"https://learn.adafruit.com/quick-start-the-pico-w-with-wippersnapper/overview",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"GP0 (UART TX)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GP1 (UART RX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GP2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GP3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GP4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GP5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GP6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GP7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GP8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GP9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GP10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GP11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GP12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GP13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GP14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GP15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GP16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GP17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GP18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GP19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GP20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GP21",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"GP22",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"GP23",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GP24",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GP25",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"GP26",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"GP27",hasPWM:!0,hasServo:!0},{number:28,name:"D28",displayName:"GP28",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GP29",hasPWM:!0,hasServo:!0},{number:64,name:"D64",displayName:"LED (BUILT-IN)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A26",displayName:"A0 (GP0)",direction:"INPUT"},{name:"A27",displayName:"A1 (GP1)",direction:"INPUT"},{name:"A28",displayName:"A2 (GP2)",direction:"INPUT"},{name:"A29",displayName:"A3 (GP3)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:5,SDA:4},image:"boards/rpi-pico-2w/image.jpg",totalAnalogPins:4,i2cPorts:[{i2cPortId:0,SDA:4,SCL:5}],totalGPIOPins:31},"rpi-pico-w":{boardName:"rpi-pico-w",mcuName:"rp2040",referenceVoltage:3.3,displayName:"Raspberry Pi Pico W",vendor:"Raspberry Pi",productURL:"https://www.adafruit.com/product/5526",documentationURL:"https://learn.adafruit.com/quick-start-the-pico-w-with-wippersnapper/",installMethod:"uf2",pins:[{number:0,name:"D0",displayName:"GP0 (UART TX)",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GP1 (UART RX)",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GP2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GP3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GP4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GP5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GP6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GP7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GP8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GP9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GP10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GP11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GP12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GP13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GP14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GP15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GP16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GP17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GP18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GP19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GP20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GP21",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"GP22",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"GP23",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GP24",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GP25",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"GP26",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"GP27",hasPWM:!0,hasServo:!0},{number:28,name:"D28",displayName:"GP28",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GP29",hasPWM:!0,hasServo:!0},{number:64,name:"D64",displayName:"LED (BUILT-IN)",hasPWM:!1,hasServo:!1}],analogPins:[{name:"A26",displayName:"A0 (GP0)",direction:"INPUT"},{name:"A27",displayName:"A1 (GP1)",direction:"INPUT"},{name:"A28",displayName:"A2 (GP2)",direction:"INPUT"},{name:"A29",displayName:"A3 (GP3)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:5,SDA:4},image:"boards/rpi-pico-w/image.png",totalAnalogPins:4,i2cPorts:[{i2cPortId:0,SDA:4,SCL:5}],totalGPIOPins:31},"sparklemotionmini-esp32":{boardName:"sparklemotionmini-esp32",mcuName:"ESP32",referenceVoltage:3.3,displayName:"Adafruit Mini Sparkle Motion",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/6160",documentationURL:"https://learn.adafruit.com/adafruit-sparkle-motion-mini/",installMethod:"web",pins:[{number:0,name:"D0",displayName:"D0 (Button)",hasPWM:!1,hasServo:!1},{number:12,name:"D12",displayName:"D12 (LED BUILT-IN)",hasPWM:!0,hasServo:!1},{number:26,name:"D26",displayName:"D19 (UART RX)",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"D25 (UART TX)",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"D14",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"D18 (NEOPIXEL)",hasPWM:!0,hasServo:!1},{number:27,name:"D27",displayName:"D27",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"D13 (A0)",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"D33",hasPWM:!0,hasServo:!0},{number:32,name:"D32",displayName:"D32",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"D19 (SDA)",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"D22 (SCL)",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A13",displayName:"A0 (D13)",direction:"INPUT"},{name:"A14",displayName:"D14",direction:"INPUT"},{name:"A27",displayName:"D27",direction:"INPUT"},{name:"A25",displayName:"D25",direction:"INPUT"},{name:"A26",displayName:"D26",direction:"INPUT"},{name:"A32",displayName:"A32",direction:"INPUT"},{name:"A33",displayName:"A33",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:22,SDA:19},image:"boards/sparklemotionmini-esp32/image.svg",totalAnalogPins:7,i2cPorts:[{i2cPortId:0,SDA:19,SCL:22}],totalGPIOPins:12},"feather-rp2040-adalogger":{boardName:"Adafruit Feather RP2040 Adalogger",mcuName:"RP2040",referenceVoltage:3.3,displayName:"Adafruit Feather RP2040 Adalogger",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5980",documentationURL:"https://learn.adafruit.com/adafruit-feather-rp2040-adalogger",installMethod:"uf2",totalGPIOPins:22,totalAnalogPins:4,pins:[{number:0,name:"D0",displayName:"GPIO0",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13 (LED)",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16 (MOSI)",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17 (A1/SCK/SD CS)",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18 (MISO)",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GPIO19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GPIO20",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GPIO24 (A0)",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GPIO25 (A3)",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GPIO29 (A2)",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"GPIO24 (A0)",direction:"INPUT"},{name:"A1",displayName:"GPIO17 (A1/SD CS)",direction:"INPUT"},{name:"A2",displayName:"GPIO29 (A2)",direction:"INPUT"},{name:"A3",displayName:"GPIO25 (A3)",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D3",SDA:"D2"},sdCardCS:17,rtcType:"PCF8523",image:null},"generic-esp32-s2":{boardName:"Generic ESP32-S2",mcuName:"ESP32-S2",referenceVoltage:3.3,displayName:"Generic ESP32-S2 Board",vendor:"Generic",productURL:"",documentationURL:"",installMethod:"uf2",totalGPIOPins:43,totalAnalogPins:20,pins:[{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GPIO14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GPIO15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GPIO21",hasPWM:!0,hasServo:!0},{number:33,name:"D33",displayName:"GPIO33",hasPWM:!0,hasServo:!0},{number:34,name:"D34",displayName:"GPIO34",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"GPIO35",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"GPIO36",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"GPIO37",hasPWM:!0,hasServo:!0},{number:38,name:"D38",displayName:"GPIO38",hasPWM:!0,hasServo:!0},{number:39,name:"D39",displayName:"GPIO39",hasPWM:!0,hasServo:!0},{number:40,name:"D40",displayName:"GPIO40",hasPWM:!0,hasServo:!0},{number:41,name:"D41",displayName:"GPIO41",hasPWM:!0,hasServo:!0},{number:42,name:"D42",displayName:"GPIO42",hasPWM:!0,hasServo:!0},{number:43,name:"D43",displayName:"GPIO43",hasPWM:!0,hasServo:!0},{number:44,name:"D44",displayName:"GPIO44",hasPWM:!0,hasServo:!0},{number:45,name:"D45",displayName:"GPIO45",hasPWM:!0,hasServo:!0},{number:46,name:"D46",displayName:"GPIO46",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"ADC1_CH0",direction:"INPUT"},{name:"A1",displayName:"ADC1_CH1",direction:"INPUT"},{name:"A2",displayName:"ADC1_CH2",direction:"INPUT"},{name:"A3",displayName:"ADC1_CH3",direction:"INPUT"},{name:"A4",displayName:"ADC1_CH4",direction:"INPUT"},{name:"A5",displayName:"ADC1_CH5",direction:"INPUT"},{name:"A6",displayName:"ADC1_CH6",direction:"INPUT"},{name:"A7",displayName:"ADC1_CH7",direction:"INPUT"},{name:"A8",displayName:"ADC1_CH8",direction:"INPUT"},{name:"A9",displayName:"ADC1_CH9",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D9",SDA:"D8"},image:null},"generic-esp32-s3":{boardName:"Generic ESP32-S3",mcuName:"ESP32-S3",referenceVoltage:3.3,displayName:"Generic ESP32-S3 Board",vendor:"Generic",productURL:"",documentationURL:"",installMethod:"uf2",totalGPIOPins:48,totalAnalogPins:20,pins:[{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GPIO14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GPIO15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GPIO19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GPIO20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GPIO21",hasPWM:!0,hasServo:!0},{number:35,name:"D35",displayName:"GPIO35",hasPWM:!0,hasServo:!0},{number:36,name:"D36",displayName:"GPIO36",hasPWM:!0,hasServo:!0},{number:37,name:"D37",displayName:"GPIO37",hasPWM:!0,hasServo:!0},{number:38,name:"D38",displayName:"GPIO38",hasPWM:!0,hasServo:!0},{number:39,name:"D39",displayName:"GPIO39",hasPWM:!0,hasServo:!0},{number:40,name:"D40",displayName:"GPIO40",hasPWM:!0,hasServo:!0},{number:41,name:"D41",displayName:"GPIO41",hasPWM:!0,hasServo:!0},{number:42,name:"D42",displayName:"GPIO42",hasPWM:!0,hasServo:!0},{number:43,name:"D43",displayName:"GPIO43",hasPWM:!0,hasServo:!0},{number:44,name:"D44",displayName:"GPIO44",hasPWM:!0,hasServo:!0},{number:45,name:"D45",displayName:"GPIO45",hasPWM:!0,hasServo:!0},{number:46,name:"D46",displayName:"GPIO46",hasPWM:!0,hasServo:!0},{number:47,name:"D47",displayName:"GPIO47",hasPWM:!0,hasServo:!0},{number:48,name:"D48",displayName:"GPIO48",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"ADC1_CH0",direction:"INPUT"},{name:"A1",displayName:"ADC1_CH1",direction:"INPUT"},{name:"A2",displayName:"ADC1_CH2",direction:"INPUT"},{name:"A3",displayName:"ADC1_CH3",direction:"INPUT"},{name:"A4",displayName:"ADC1_CH4",direction:"INPUT"},{name:"A5",displayName:"ADC1_CH5",direction:"INPUT"},{name:"A6",displayName:"ADC1_CH6",direction:"INPUT"},{name:"A7",displayName:"ADC1_CH7",direction:"INPUT"},{name:"A8",displayName:"ADC1_CH8",direction:"INPUT"},{name:"A9",displayName:"ADC1_CH9",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D9",SDA:"D8"},image:null},"generic-rp2040":{boardName:"Generic RP2040",mcuName:"RP2040",referenceVoltage:3.3,displayName:"Generic RP2040 Board",vendor:"Generic",productURL:"",documentationURL:"",installMethod:"uf2",totalGPIOPins:30,totalAnalogPins:4,pins:[{number:0,name:"D0",displayName:"GPIO0",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GPIO14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GPIO15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GPIO19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GPIO20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GPIO21",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"GPIO22",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"GPIO23",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GPIO24",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GPIO25",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"GPIO26",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"GPIO27",hasPWM:!0,hasServo:!0},{number:28,name:"D28",displayName:"GPIO28",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GPIO29",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"ADC0",direction:"INPUT"},{name:"A1",displayName:"ADC1",direction:"INPUT"},{name:"A2",displayName:"ADC2",direction:"INPUT"},{name:"A3",displayName:"ADC3",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D3",SDA:"D2"},image:null},"generic-rp23xx":{boardName:"Generic RP23xx",mcuName:"RP23xx",referenceVoltage:3.3,displayName:"Generic RP23xx Board",vendor:"Generic",productURL:"",documentationURL:"",installMethod:"uf2",totalGPIOPins:30,totalAnalogPins:4,pins:[{number:0,name:"D0",displayName:"GPIO0",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GPIO14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GPIO15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GPIO19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GPIO20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GPIO21",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"GPIO22",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"GPIO23",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GPIO24",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GPIO25",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"GPIO26",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"GPIO27",hasPWM:!0,hasServo:!0},{number:28,name:"D28",displayName:"GPIO28",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GPIO29",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"ADC0",direction:"INPUT"},{name:"A1",displayName:"ADC1",direction:"INPUT"},{name:"A2",displayName:"ADC2",direction:"INPUT"},{name:"A3",displayName:"ADC3",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D3",SDA:"D2"},image:null},"metro-rp2350-sd":{boardName:"Adafruit Metro RP2350",mcuName:"RP2350",referenceVoltage:3.3,displayName:"Adafruit Metro RP2350 (with SD card)",vendor:"Adafruit",productURL:"https://www.adafruit.com/product/5786",documentationURL:"https://learn.adafruit.com/adafruit-metro-rp2350",installMethod:"uf2",totalGPIOPins:30,totalAnalogPins:4,pins:[{number:0,name:"D0",displayName:"GPIO0",hasPWM:!0,hasServo:!0},{number:1,name:"D1",displayName:"GPIO1",hasPWM:!0,hasServo:!0},{number:2,name:"D2",displayName:"GPIO2",hasPWM:!0,hasServo:!0},{number:3,name:"D3",displayName:"GPIO3",hasPWM:!0,hasServo:!0},{number:4,name:"D4",displayName:"GPIO4",hasPWM:!0,hasServo:!0},{number:5,name:"D5",displayName:"GPIO5",hasPWM:!0,hasServo:!0},{number:6,name:"D6",displayName:"GPIO6",hasPWM:!0,hasServo:!0},{number:7,name:"D7",displayName:"GPIO7",hasPWM:!0,hasServo:!0},{number:8,name:"D8",displayName:"GPIO8",hasPWM:!0,hasServo:!0},{number:9,name:"D9",displayName:"GPIO9",hasPWM:!0,hasServo:!0},{number:10,name:"D10",displayName:"GPIO10 (SD CS)",hasPWM:!0,hasServo:!0},{number:11,name:"D11",displayName:"GPIO11",hasPWM:!0,hasServo:!0},{number:12,name:"D12",displayName:"GPIO12",hasPWM:!0,hasServo:!0},{number:13,name:"D13",displayName:"GPIO13",hasPWM:!0,hasServo:!0},{number:14,name:"D14",displayName:"GPIO14",hasPWM:!0,hasServo:!0},{number:15,name:"D15",displayName:"GPIO15",hasPWM:!0,hasServo:!0},{number:16,name:"D16",displayName:"GPIO16",hasPWM:!0,hasServo:!0},{number:17,name:"D17",displayName:"GPIO17",hasPWM:!0,hasServo:!0},{number:18,name:"D18",displayName:"GPIO18",hasPWM:!0,hasServo:!0},{number:19,name:"D19",displayName:"GPIO19",hasPWM:!0,hasServo:!0},{number:20,name:"D20",displayName:"GPIO20",hasPWM:!0,hasServo:!0},{number:21,name:"D21",displayName:"GPIO21",hasPWM:!0,hasServo:!0},{number:22,name:"D22",displayName:"GPIO22",hasPWM:!0,hasServo:!0},{number:23,name:"D23",displayName:"GPIO23",hasPWM:!0,hasServo:!0},{number:24,name:"D24",displayName:"GPIO24",hasPWM:!0,hasServo:!0},{number:25,name:"D25",displayName:"GPIO25",hasPWM:!0,hasServo:!0},{number:26,name:"D26",displayName:"GPIO26",hasPWM:!0,hasServo:!0},{number:27,name:"D27",displayName:"GPIO27",hasPWM:!0,hasServo:!0},{number:28,name:"D28",displayName:"GPIO28",hasPWM:!0,hasServo:!0},{number:29,name:"D29",displayName:"GPIO29",hasPWM:!0,hasServo:!0}],analogPins:[{name:"A0",displayName:"ADC0",direction:"INPUT"},{name:"A1",displayName:"ADC1",direction:"INPUT"},{name:"A2",displayName:"ADC2",direction:"INPUT"},{name:"A3",displayName:"ADC3",direction:"INPUT"}],defaultI2C:{i2cPortId:0,SCL:"D3",SDA:"D2"},sdCardCS:10,image:null}}};window.jsonComponentsObject={components:{ds18x20:[{id:"ds18b20",name:"ds18b20",description:"",category:"ds18x20",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/374-01.jpg",productUrl:"https://www.adafruit.com/product/374"},{id:"ds18b20_hi_temp_waterproof",name:"ds18b20_hi_temp_waterproof",description:"",category:"ds18x20",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/642-00.jpg",productUrl:"https://www.adafruit.com/product/642"},{id:"ds18b20_waterproof",name:"ds18b20_waterproof",description:"",category:"ds18x20",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/381-01.jpg",productUrl:"https://www.adafruit.com/product/381"}],i2c:[{id:"adt7410",name:"adt7410",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/4089-05.jpg",productUrl:"https://www.adafruit.com/product/4089",address:"0x48",addresses:["0x48","0x49","0x4A","0x4B"]},{id:"aht20",name:"aht20",description:"Inexpensive temperature and humidity sensor for I2C-capable boards.",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/4566-04.jpg",productUrl:"https://www.adafruit.com/product/4566",address:"0x38",addresses:["0x38"]},{id:"aht21",name:"aht21",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"components/i2c/aht21/image.jpg",address:"0x38",addresses:["0x38"]},{id:"am2301b",name:"am2301b",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5181-05.jpg",productUrl:"https://www.adafruit.com/product/5181",address:"0x38",addresses:["0x38"]},{id:"am2315c",name:"am2315c",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5182-05.jpg",productUrl:"https://www.adafruit.com/product/5182",address:"0x38",addresses:["0x38"]},{id:"bh1750",name:"bh1750",description:"",category:"i2c",dataTypes:["light"],image:"https://www.adafruit.com/images/480x360/4681-00.jpg",productUrl:"https://www.adafruit.com/product/4681",address:"0x23",addresses:["0x23","0x5C"]},{id:"bme280",name:"bme280",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pressure","altitude"],image:"https://www.adafruit.com/images/480x360/2652-04.jpg",productUrl:"https://www.adafruit.com/product/2652",address:"0x76",addresses:["0x76","0x77"]},{id:"bme680",name:"bme680",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pressure","altitude","gas-resistance"],image:"https://www.adafruit.com/images/480x360/3660-08.jpg",productUrl:"https://www.adafruit.com/product/3660",address:"0x76",addresses:["0x76","0x77"]},{id:"bme688",name:"bme688",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pressure","altitude","gas-resistance"],image:"https://www.adafruit.com/images/480x360/5046-05.jpg",productUrl:"https://www.adafruit.com/product/5046",address:"0x76",addresses:["0x76","0x77"]},{id:"bmp280",name:"bmp280",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure","altitude"],image:"https://www.adafruit.com/images/480x360/2651-08.jpg",productUrl:"https://www.adafruit.com/product/2651",address:"0x76",addresses:["0x76","0x77"]},{id:"bmp388",name:"bmp388",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure","altitude"],image:"https://www.adafruit.com/images/480x360/3966-10.jpg",productUrl:"https://www.adafruit.com/product/3966",address:"0x76",addresses:["0x76","0x77"]},{id:"bmp390",name:"bmp390",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure","altitude"],image:"https://www.adafruit.com/images/480x360/4816-05.jpg",productUrl:"https://www.adafruit.com/product/4816",address:"0x76",addresses:["0x76","0x77"]},{id:"dht20",name:"dht20",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5183-04.jpg",productUrl:"https://www.adafruit.com/product/5183",address:"0x38",addresses:["0x38"]},{id:"dps310",name:"dps310",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"https://www.adafruit.com/images/480x360/4494-05.jpg",productUrl:"https://www.adafruit.com/product/4494",address:"0x76",addresses:["0x76","0x77"]},{id:"ds2484",name:"ds2484",description:"Adafruit DS2484 I2C to 1-Wire Bus Adapter Breakout - Converts a single DS18b20 temperature sensor to I2C",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/5976-00.jpg",productUrl:"https://www.adafruit.com/product/5976",address:"0x18",addresses:["0x18"]},{id:"ens160",name:"ens160",description:"",category:"i2c",dataTypes:["tvoc","eco2",{displayName:"AQI",sensorType:"raw"}],image:"components/i2c/ens160/image.jpg",productUrl:"https://www.adafruit.com/product/5606",address:"0x52",addresses:["0x52","0x53"]},{id:"hdc302x",name:"hdc302x",description:"Precision temperature (\xC2\xB10.1\xC2\xB0C typical) and humidity sensors (\xC2\xB10.5% typ). HDC3020 / HDC3021 / HDC3022",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5989-07.jpg",productUrl:"https://www.adafruit.com/product/5989",address:"0x44",addresses:["0x44","0x45","0x46","0x47"]},{id:"hts221",name:"hts221",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/4535-04.jpg",productUrl:"https://www.adafruit.com/product/4535",address:"0x5F",addresses:["0x5F"]},{id:"htu21d",name:"htu21d",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/1899-04.jpg",productUrl:"https://www.adafruit.com/product/1899",address:"0x40",addresses:["0x40"]},{id:"htu31d",name:"htu31d",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/2857-03.jpg",productUrl:"https://www.adafruit.com/product/2857",address:"0x40",addresses:["0x40","0x41"]},{id:"ina219",name:"ina219",description:"",category:"i2c",dataTypes:["voltage","current"],image:"https://www.adafruit.com/images/480x360/904-09.jpg",productUrl:"https://www.adafruit.com/product/904",address:"0x40",addresses:["0x40","0x41","0x44","0x45"]},{id:"lc709203f",name:"lc709203f",description:"",category:"i2c",dataTypes:[{displayName:"Battery Cell Voltage",sensorType:"voltage"},{displayName:"Battery Cell Percent",sensorType:"unitless-percent"}],image:"https://www.adafruit.com/images/480x360/4712-07.jpg",productUrl:"https://www.adafruit.com/product/4712",address:"0x0B",addresses:["0x0B"]},{id:"lps22hb",name:"lps22hb",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"https://www.adafruit.com/images/480x360/4633-09.jpg",productUrl:"https://www.adafruit.com/product/4633",address:"0x5C",addresses:["0x5C","0x5D"]},{id:"lps25hb",name:"lps25hb",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"components/i2c/lps25hb/image.jpg",productUrl:"https://www.adafruit.com/product/4530",address:"0x5C",addresses:["0x5C","0x5D"]},{id:"lps28dfw",name:"lps28dfw",description:"From 260 to 4060 hPa, this is our largest range pressure sensor (24bit).",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"components/i2c/lps28dfw/image.jpg",productUrl:"https://www.adafruit.com/product/6067",address:"0x5C",addresses:["0x5C","0x5D"]},{id:"lps33hw",name:"lps33hw",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"components/i2c/lps33hw/image.jpg",productUrl:"https://www.adafruit.com/product/4414",address:"0x5C",addresses:["0x5C","0x5D"]},{id:"lps35hw",name:"lps35hw",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"https://www.adafruit.com/images/480x360/4258-10.jpg",productUrl:"https://www.adafruit.com/product/4258",address:"0x5C",addresses:["0x5C","0x5D"]},{id:"ltr303",name:"ltr303",description:"",category:"i2c",dataTypes:[{displayName:"Ambient Light",sensorType:"light"},{displayName:"Infrared",sensorType:"raw"}],image:"components/i2c/ltr303/image.jpg",productUrl:"https://www.adafruit.com/product/5610",address:"0x29",addresses:["0x29"]},{id:"ltr329",name:"ltr329",description:"",category:"i2c",dataTypes:[{displayName:"Ambient Light",sensorType:"light"},{displayName:"Infrared",sensorType:"raw"}],image:"components/i2c/ltr329/image.jpg",productUrl:"https://www.adafruit.com/product/5591",address:"0x29",addresses:["0x29"]},{id:"ltr390",name:"ltr390",description:"",category:"i2c",dataTypes:[{displayName:"Ambient Light",sensorType:"light"},{displayName:"UV Count",sensorType:"raw"}],image:"components/i2c/ltr390/image.jpg",productUrl:"https://www.adafruit.com/product/4831",address:"0x53",addresses:["0x53"]},{id:"max17048",name:"max17048",description:"",category:"i2c",dataTypes:[{displayName:"Battery Cell Voltage",sensorType:"voltage"},{displayName:"Battery Cell Percent",sensorType:"unitless-percent"}],image:"https://www.adafruit.com/images/480x360/5580-06.jpg",productUrl:"https://www.adafruit.com/product/5580",address:"0x36",addresses:["0x36"]},{id:"mcp3421",name:"mcp3421",description:"18-bit ADC. Great for Strain Gauges, Thermocouples and Pressure sensors, between 0 and 2.048 volts",category:"i2c",dataTypes:[{displayName:"ADC Reading",sensorType:"raw"}],image:"components/i2c/mcp3421/image.jpg",productUrl:"https://www.adafruit.com/product/5870",address:"0x68",addresses:["0x68"]},{id:"mcp9601",name:"mcp9601",description:"Thermocouple / ambient temperature sensor. *Note* Needs hotplugging after i2c scans + selecting component!",category:"i2c",dataTypes:[{displayName:"Ambient Temperature (\xC2\xB0C)",sensorType:"ambient-temp"},{displayName:"Ambient Temperature (\xC2\xB0F)",sensorType:"ambient-temp-fahrenheit"},{displayName:"Type K Thermocouple (\xC2\xB0C)",sensorType:"raw"}],image:"https://www.adafruit.com/images/480x360/5165-05",productUrl:"https://www.adafruit.com/product/5165",address:"0x60",addresses:["0x60","0x61","0x62","0x63","0x64","0x65","0x66","0x67"]},{id:"mcp9808",name:"mcp9808",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/1782-03.jpg",productUrl:"https://www.adafruit.com/product/1782",address:"0x18",addresses:["0x18","0x19","0x1A","0x1B","0x1C","0x1D","0x1E","0x1F"]},{id:"mpl115a2",name:"mpl115a2",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","pressure"],image:"https://www.adafruit.com/images/480x360/992-06.jpg",productUrl:"https://www.adafruit.com/product/992",address:"0x60",addresses:["0x60"]},{id:"mprls",name:"mprls",description:"",category:"i2c",dataTypes:["pressure"],image:"https://www.adafruit.com/images/480x360/3965-04.jpg",productUrl:"https://www.adafruit.com/product/3965",address:"0x18",addresses:["0x18"]},{id:"ms8607",name:"ms8607",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pressure"],image:"https://www.adafruit.com/images/480x360/4716-01.jpg",productUrl:"https://www.adafruit.com/product/4716",address:"0x40",addresses:["0x40","0x76"]},{id:"nau7802",name:"nau7802",description:"24-bit ADC with 128x gain, used with a load cell for weight/force sensing",category:"i2c",dataTypes:[{displayName:"Weight Sensor",sensorType:"raw"}],image:"components/i2c/nau7802/image.jpg",productUrl:"https://www.adafruit.com/product/4538",address:"0x2A",addresses:["0x2A"]},{id:"pct2075",name:"pct2075",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"components/i2c/pct2075/image.jpg",productUrl:"https://www.adafruit.com/product/4369",address:"0x48",addresses:["0x48","0x49","0x4A","0x4B","0x4C","0x4D","0x4E","0x4F","0x70","0x71","0x72","0x73","0x74","0x75","0x76","0x77","0x28","0x29","0x2A","0x2B","0x2C","0x2D","0x2E","0x2F","0x35","0x36","0x37"]},{id:"pmsa003i",name:"pmsa003i",description:"",category:"i2c",dataTypes:["pm10-std","pm25-std","pm100-std"],image:"https://www.adafruit.com/images/480x360/4632-10.jpg",productUrl:"https://www.adafruit.com/product/4632",address:"0x12",addresses:["0x12"]},{id:"rotary_encoder",name:"rotary_encoder",description:"",category:"i2c",dataTypes:[{displayName:"Rotary Encoder Value",sensorType:"raw"}],image:"components/i2c/rotary_encoder/image.jpeg",productUrl:"https://www.adafruit.com/product/4991",address:"0x36",addresses:["0x36","0x37","0x38","0x39","0x3A","Ox3B","0x3C","0x3D"]},{id:"scd30",name:"scd30",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","co2"],image:"https://www.adafruit.com/images/480x360/4867-05",productUrl:"https://www.adafruit.com/product/4867",address:"0x61",addresses:["0x61"]},{id:"scd40",name:"scd40",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","co2"],image:"https://www.adafruit.com/images/480x360/5187-08.jpg",productUrl:"https://www.adafruit.com/product/5187",address:"0x62",addresses:["0x62"]},{id:"sen50",name:"sen50",description:"",category:"i2c",dataTypes:["pm10-std","pm25-std","pm100-std"],image:"components/i2c/sen50/image.png",productUrl:"https://www.digikey.com/en/products/filter/gas-sensors/530?s=N4IgjCBcoLQCxVAYygMwIYBsDOBTANCAPZQDa4ADFRQgLoC%2BjQA",address:"0x69",addresses:["0x69"]},{id:"sen54",name:"sen54",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pm10-std","pm25-std","pm100-std","voc-index"],image:"components/i2c/sen54/image.png",productUrl:"https://www.digikey.com/en/products/filter/gas-sensors/530?s=N4IgjCBcoLQCxVAYygMwIYBsDOBTANCAPZQDa4ADFRQgLoC%2BjQA",address:"0x69",addresses:["0x69"]},{id:"sen55",name:"sen55",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pm10-std","pm25-std","pm100-std","voc-index","nox-index"],image:"components/i2c/sen55/image.png",productUrl:"https://www.digikey.com/en/products/filter/gas-sensors/530?s=N4IgjCBcoLQCxVAYygMwIYBsDOBTANCAPZQDa4ADFRQgLoC%2BjQA",address:"0x69",addresses:["0x69"]},{id:"sen5x",name:"sen5x",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pm10-std","pm25-std","pm100-std","voc-index","nox-index"],image:"components/i2c/sen5x/image.png",productUrl:"https://www.digikey.com/en/products/filter/gas-sensors/530?s=N4IgjCBcoLQCxVAYygMwIYBsDOBTANCAPZQDa4ADFRQgLoC%2BjQA",address:"0x69",addresses:["0x69"]},{id:"sen66",name:"sen66",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pm10-std","pm25-std","pm100-std","voc-index","nox-index","co2"],image:"components/i2c/sen66/image.png",productUrl:"https://www.digikey.com/en/products/detail/sensirion-ag/SEN66-SIN-T/25700945?s=N4IgTCBcDaIMoFEByA2FIC6BfIA",address:"0x6B",addresses:["0x6B"]},{id:"sgp30",name:"sgp30",description:"",category:"i2c",dataTypes:["eco2","tvoc"],image:"https://www.adafruit.com/images/480x360/3709-07.jpg",productUrl:"https://www.adafruit.com/product/3709",address:"0x58",addresses:["0x58"]},{id:"sgp40",name:"sgp40",description:"",category:"i2c",dataTypes:["voc-index",{displayName:"Raw (For Reference Only)",sensorType:"raw"}],image:"https://www.adafruit.com/images/480x360/4829-06.jpg",productUrl:"https://www.adafruit.com/product/4829",address:"0x59",addresses:["0x59"]},{id:"sht20",name:"sht20",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"components/i2c/sht20/image.jpg",productUrl:"https://www.digikey.com/en/products/detail/dfrobot/SEN0227/7897986",address:"0x40",addresses:["0x40"]},{id:"sht30_mesh",name:"sht30_mesh",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/4099-09.jpg",productUrl:"https://www.adafruit.com/product/4099",address:"0x44",addresses:["0x44"]},{id:"sht30_shell",name:"sht30_shell",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5064-00.jpg",productUrl:"https://www.adafruit.com/product/5064",address:"0x44",addresses:["0x44"]},{id:"sht3x",name:"sht3x",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/2857-03.jpg",productUrl:"https://www.adafruit.com/product/2857",address:"0x44",addresses:["0x44","0x45"]},{id:"sht40",name:"sht40",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/4885-05.jpg",productUrl:"https://www.adafruit.com/product/4885",address:"0x44",addresses:["0x44"]},{id:"sht41",name:"sht41",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5776-05.jpg",productUrl:"https://www.adafruit.com/product/5776",address:"0x44",addresses:["0x44"]},{id:"sht45",name:"sht45",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/5665-00.jpg",productUrl:"https://www.adafruit.com/product/5665",address:"0x44",addresses:["0x44"]},{id:"shtc3",name:"shtc3",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/4636-05.jpg",productUrl:"https://www.adafruit.com/product/4636",address:"0x70",addresses:["0x70"]},{id:"si7021",name:"si7021",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"],image:"https://www.adafruit.com/images/480x360/3251-08.jpg",productUrl:"https://www.adafruit.com/product/3251",address:"0x40",addresses:["0x40"]},{id:"stemma_soil",name:"stemma_soil",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit",{displayName:"Capacitive Sensor",sensorType:"raw"}],image:"https://www.adafruit.com/images/480x360/4026-01.jpg",productUrl:"https://www.adafruit.com/product/4026",address:"0x36",addresses:["0x36","0x37","0x38","0x39"]},{id:"tc74a0",name:"tc74a0",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"https://www.adafruit.com/images/480x360/4375-03.jpg",productUrl:"https://www.adafruit.com/product/4375",address:"0x48",addresses:["0x48"]},{id:"tmp117",name:"tmp117",description:"",category:"i2c",dataTypes:["ambient-temp","ambient-temp-fahrenheit"],image:"components/i2c/tmp117/image.jpg",productUrl:"https://www.adafruit.com/product/4821",address:"0x48",addresses:["0x48","0x49","0x4A","0x4B"]},{id:"tsl2591",name:"tsl2591",description:"",category:"i2c",dataTypes:["light"],image:"https://www.adafruit.com/images/480x360/1980-08.jpg",productUrl:"https://www.adafruit.com/product/1980",address:"0x29",addresses:["0x29","0x39","0x49"]},{id:"vcnl4020",name:"vcnl4020",description:"Proximity sensor works from 0 to 200mm (about 7.5 inches) & light sensor with range of 0.26 to 16,000 lux.",category:"i2c",dataTypes:["light","proximity"],image:"components/i2c/vcnl4020/image.jpg",productUrl:"https://www.adafruit.com/product/5810",address:"0x13",addresses:["0x13"]},{id:"vcnl4040",name:"vcnl4040",description:"Proximity sensor works from 0 to 200mm (about 7.5 inches) & light sensor with range of 0.0125 to 6,553.5 lux",category:"i2c",dataTypes:["light","proximity"],image:"https://www.adafruit.com/images/480x360/4161-06.jpg",productUrl:"https://www.adafruit.com/product/4161",address:"0x60",addresses:["0x60"]},{id:"vcnl4200",name:"vcnl4200",description:"Proximity sensor works from 0 to 1.5m (about 59 inches) & light sensor with range of 0.003 to 1570 lux",category:"i2c",dataTypes:["light","proximity"],image:"https://www.adafruit.com/images/480x360/6064-00.jpg",productUrl:"https://www.adafruit.com/product/6064",address:"0x51",addresses:["0x51"]},{id:"veml7700",name:"veml7700",description:"",category:"i2c",dataTypes:["light"],image:"https://www.adafruit.com/images/480x360/4162-10.jpg",productUrl:"https://www.adafruit.com/product/4162",address:"0x10",addresses:["0x10"]},{id:"vl53l0x",name:"vl53l0x",description:"Time of Flight (ToF) distance sensor with about ~50 to 1200mm range",category:"i2c",dataTypes:[{displayName:"ToF Sensor",sensorType:"proximity"}],image:"components/i2c/vl53l0x/image.jpg",productUrl:"https://www.adafruit.com/product/3317",address:"0x29",addresses:["0x29"]},{id:"vl53l1x",name:"vl53l1x",description:"Time of Flight (ToF) distance sensor with about ~30 to 4000mm range",category:"i2c",dataTypes:[{displayName:"ToF Sensor",sensorType:"proximity"}],image:"components/i2c/vl53l1x/image.jpg",productUrl:"https://www.adafruit.com/product/3967",address:"0x29",addresses:["0x29"]},{id:"vl53l4cd",name:"vl53l4cd",description:"Time of Flight (ToF) distance sensor with about ~1 to 1300mm range",category:"i2c",dataTypes:[{displayName:"ToF Sensor",sensorType:"proximity"}],image:"components/i2c/vl53l4cd/image.jpg",productUrl:"https://www.adafruit.com/product/5396",address:"0x29",addresses:["0x29"]},{id:"vl53l4cx",name:"vl53l4cx",description:"Time of Flight (ToF) distance sensor with about ~1 to 6000mm range + 'multi object detection'",category:"i2c",dataTypes:[{displayName:"ToF Sensor - Object 1",sensorType:"proximity"},{displayName:"ToF Sensor - Object 2",sensorType:"raw"}],image:"https://www.adafruit.com/images/480x360/5425-02.jpg",productUrl:"https://www.adafruit.com/product/5425",address:"0x29",addresses:["0x29"]},{id:"vl6180x",name:"vl6180x",description:"Time of Flight (ToF) distance sensor with about ~5 to 200mm range",category:"i2c",dataTypes:[{displayName:"ToF Sensor",sensorType:"proximity"},"light"],image:"components/i2c/vl6180x/image.jpg",productUrl:"https://www.adafruit.com/product/3316",address:"0x29",addresses:["0x29"]}],pin:[{id:"analog_pin",name:"analog_pin",description:"",category:"pin",dataTypes:[],image:"components/pin/analog_pin/image.png"},{id:"beam_break_sensor",name:"beam_break_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/2168-04.jpg",productUrl:"https://www.adafruit.com/product/2168"},{id:"buzzer_5v",name:"buzzer_5v",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/1536-06.jpg",productUrl:"https://www.adafruit.com/product/1536"},{id:"etape_liquid_level_sensor",name:"etape_liquid_level_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/2656-03",productUrl:"https://www.adafruit.com/product/2656"},{id:"flat_vibration_switch",name:"flat_vibration_switch",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/4081-00.jpg",productUrl:"https://www.adafruit.com/product/4081"},{id:"hall_effect_sensor",name:"hall_effect_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/158-00.jpg",productUrl:"https://www.adafruit.com/product/158"},{id:"led",name:"led",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/301-00.jpg",productUrl:"https://www.adafruit.com/product/301"},{id:"light_sensor",name:"light_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/161-00.jpg",productUrl:"https://www.adafruit.com/product/161"},{id:"mosfet_driver",name:"mosfet_driver",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/5648-07",productUrl:"https://www.adafruit.com/product/5648"},{id:"non_latching_relay",name:"non_latching_relay",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/4409-05",productUrl:"https://www.adafruit.com/product/4409"},{id:"pir_sensor",name:"pir_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/189-00.jpg",productUrl:"https://www.adafruit.com/product/189"},{id:"potentiometer",name:"potentiometer",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/356-03.jpg",productUrl:"https://www.adafruit.com/product/356"},{id:"power_switch_tail",name:"power_switch_tail",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/2935-13.jpg",productUrl:"https://www.adafruit.com/product/2935"},{id:"push_button",name:"push_button",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/367-01.jpg",productUrl:"https://www.adafruit.com/product/367"},{id:"reed_switch",name:"reed_switch",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/375-01.jpg",productUrl:"https://www.adafruit.com/product/375"},{id:"tctr1000",name:"tctr1000",description:"Optical reflective sensor with digital output, 1-30mm range, and adjustable LED emitter brightness",category:"pin",dataTypes:[],image:"components/pin/tctr1000/image.jpg",productUrl:"https://www.adafruit.com/product/5913"},{id:"toggle_switch",name:"toggle_switch",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/3221-01.jpg",productUrl:"https://www.adafruit.com/product/3221"},{id:"water_sensor",name:"water_sensor",description:"",category:"pin",dataTypes:[],image:"https://www.adafruit.com/images/480x360/4965-03",productUrl:"https://www.adafruit.com/product/4965"}],pixel:[{id:"dotstar",name:"dotstar",description:"",category:"pixel",dataTypes:[],image:"https://www.adafruit.com/images/480x360/2343-03.jpg",productUrl:"https://www.adafruit.com/product/2343"},{id:"neopixel",name:"neopixel",description:"",category:"pixel",dataTypes:[],image:"components/pixel/neopixel/image.jpg",productUrl:"http://www.adafruit.com/category/168"},{id:"neorgb",name:"neorgb",description:"Speak NeoPixel to your RGB strips! Treat PWM-able common-anode LEDs as a NeoPixel [16V/3A/channel]",category:"pixel",dataTypes:[],image:"https://www.adafruit.com/images/480x360/5888-10.jpg",productUrl:"https://www.adafruit.com/product/5888"}],pwm:[{id:"dimmable_led",name:"dimmable_led",description:"",category:"pwm",dataTypes:[],image:"https://www.adafruit.com/images/480x360/301-00.jpg",productUrl:"https://www.adafruit.com/product/301"},{id:"piezo_buzzer",name:"piezo_buzzer",description:"",category:"pwm",dataTypes:[],image:"https://www.adafruit.com/images/480x360/160-01.jpg",productUrl:"https://www.adafruit.com/product/160"},{id:"rgb_led",name:"rgb_led",description:"",category:"pwm",dataTypes:[],image:"https://www.adafruit.com/images/480x360/302-00.jpg",productUrl:"https://www.adafruit.com/product/302"}],servo:[{id:"servo",name:"servo",description:"",category:"servo",dataTypes:[],image:"https://www.adafruit.com/images/480x360/169-06.jpg",productUrl:"https://www.adafruit.com/product/169"}],uart:[{id:"pm1006",name:"pm1006",description:"",category:"uart",dataTypes:["pm25-env"],image:"components/uart/pm1006/image.png",productUrl:"https://www.ikea.com/us/en/p/vindriktning-air-quality-sensor-60515911/"},{id:"pms5003",name:"pms5003",description:"",category:"uart",dataTypes:["pm10-std","pm25-std","pm100-std","pm10-env","pm25-env","pm100-env"],image:"https://www.adafruit.com/images/480x360/3686-04",productUrl:"https://www.adafruit.com/product/3686"}],ds18x20_metadata:{title:"DS18X20 Component Definition",description:"A DS18X20 WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","subcomponents","sensorResolution"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:30},published:{description:"If true, this component is supported by the current firmware version and will be displayed to all users. If false, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},subcomponents:{description:"The ambient temperature sensor (and its fahrenheit counterpart) standard on DS18X20 Components",type:"array",items:{type:"string",pattern:"^ambient-temp(-fahrenheit)?$"}},sensorResolution:{description:"The DS18X20's desired sensor read resolution, in bits.",type:"number",minimum:9,maximum:12}}},i2c_metadata:{title:"I2C Component Definition",description:"An I2C-based WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","i2cAddresses","subcomponents"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:24},published:{description:"If true, this component is supported by the current firmware version and will be displayed to all users. If false, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},i2cAddresses:{description:"List of I2C addresses (as hex string, like \"0x38\") this component can appear on.",type:"array",items:{type:"string"}},subcomponents:{description:"List of sensors on this I2C component.",type:"array",items:{$ref:"#/$defs/subcomponent"}}}},pin_metadata:{title:"Pin Component Definition",description:"A pin-based WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","mode","direction"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:24},published:{description:"If true, this component is supported by the current firmware version and will be displayed to all users. If false, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},mode:{description:"This component's mode, either DIGITAL or ANALOG.",type:"string",pattern:"^(DIGITAL|ANALOG)$"},direction:{description:"This component's direction, either INPUT or OUTPUT.",type:"string",pattern:"^(INPUT|OUTPUT)$"},autoSelectString:{description:"A hint for automatically looking up pin names that may be appropriate for this kind of component.",type:"string",minLength:3,maxLength:24},selectPullUp:{description:"If true, the user will be able to select pull up or down options.",type:"boolean"},pull:{description:"This component's pull setting, either UP or DOWN.",type:"string",pattern:"^(UP|DOWN)$"},selectReadMode:{description:"If true, the user will be able to select the read mode between pin and voltage options.",type:"boolean"},analogReadMode:{description:"For ANALOG mode components, specifies whether to read values (PIN_VALUE) or voltages (PIN_VOLTAGE). Will be a default if `selectReadMode` option is true.",type:"string",pattern:"^(PIN_VALUE|PIN_VOLTAGE)$"},defaultPeriod:{description:"If present, the component form will allow the user to set its period, with this value as the default (in seconds)",type:"number",minimum:30,maximum:86400},forceOnPeriod:{description:"If true, the user must specify a period (won't be optional in the form).",type:"boolean"},visualization:{description:"Specifies which visual component to use in the WipperSnapper interface and how to configure it",type:"object",discriminator:{propertyName:"type"},required:["type"],oneOf:[{properties:{type:{const:"switch"},offLabel:{type:"string"},offIcon:{type:"string"},onLabel:{type:"string"},onIcon:{type:"string"}},additionalProperties:!1},{properties:{type:{const:"button"},pressedLabel:{type:"string"},unpressedLabel:{type:"string"}},additionalProperties:!1},{properties:{type:{const:"slider"}},additionalProperties:!1}]}}},pixel_metadata:{title:"Pixel Component Definition",description:"Addressable Pixel WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","pixelsType","defaultPixelsOrder"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:24},published:{description:"If True, this component is supported by the current firmware version and will be displayed to all users. Otherwise, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},pixelsType:{description:"The type of addressable pixel: NEOPIXEL or DOTSTAR. Corresponds to `PixelsType` in pixels.proto",type:"string",pattern:"^(NEOPIXEL|DOTSTAR)$"},defaultPixelsOrder:{description:"The pixel strand's color ordering. Corresponds to `PixelsOrder` in pixels.proto.",type:"string",pattern:"^(GRB|GRBW|RGB|RGBW|BRG)$"},autoSelectString:{description:"A hint for automatically looking up pin names that may be appropriate for this kind of component.",type:"string",minLength:3,maxLength:24}}},pwm_metadata:{title:"PWM Component Definition",description:"A PWM WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","pwmSetting"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:24},published:{description:"If true, this component is supported by the current firmware version and will be displayed to all users. If false, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},pwmSetting:{description:"The component's pulse-width modulation setting. Either fixed frequency (variable duty cycle) or variable frequency (fixed duty cycle)",type:"string",pattern:"^(fixed|variable)Frequency$"},visualization:{description:"Specifies which visual component to use in the WipperSnapper interface and how to configure it",type:"object",discriminator:{propertyName:"type"},required:["type"],oneOf:[{properties:{type:{const:"switch-pwm"},offLabel:{type:"string"},offIcon:{type:"string"},onLabel:{type:"string"},onIcon:{type:"string"}},additionalProperties:!1},{properties:{type:{const:"button"},pressedLabel:{type:"string"},unpressedLabel:{type:"string"}},additionalProperties:!1},{properties:{type:{const:"slider-pwm"}},additionalProperties:!1},{properties:{type:{const:"color-picker"}},additionalProperties:!1}]}}},servo_metadata:{title:"Servo Component Definition",description:"A servo motor WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","frequency","minPulseWidth","maxPulseWidth"],properties:{displayName:{description:"The human-friendly name of the servo component.",type:"string",minLength:3,maxLength:24},published:{description:"If true, this component is supported by the current firmware version and will be displayed to all users. If false, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},frequency:{description:"The desired frequency of the PWM signal, in Hz.",type:"number",default:50,minimum:40,maximum:200},minPulseWidth:{description:"The minimum pulse width of the servo, in uS.",type:"number"},maxPulseWidth:{description:"The maximum pulse width of the servo, in uS.",type:"number"},visualization:{description:"Specifies the servo's specific visualization.",type:"object",required:["type"],additionalProperties:!1,properties:{type:{const:"slider-servo"}}}}},uart_metadata:{title:"UART Component Definition",description:"A UART WipperSnapper component for use in Adafruit IO",required:["displayName","vendor","subcomponents","baudRate"],properties:{displayName:{description:"The human-friendly name of this component.",type:"string",minLength:3,maxLength:30},published:{description:"If True, this component is supported by the current firmware version and will be displayed to all users. Otherwise, it is hidden behind a developer toggle so that contributors can still work on it against the production site.",type:"boolean"},description:{description:"A brief description describing this component's capabilities.",type:"string",minLength:3,maxLength:255},productURL:{description:"Link to this component's homepage.",type:"string",format:"uri"},documentationURL:{description:"Link to this component's documentation.",type:"string",format:"uri"},vendor:{description:"Name of the company that makes this component.",type:"string",minLength:3,maxLength:24},subcomponents:{description:"List of sensor subcomponents comprising the primary UART component.",type:"array",items:{$ref:"#/$defs/subcomponent"}},baudRate:{description:"The desired UART bus baud rate, in bps.",type:"number",minimum:1200,maximum:256e3},inverted:{description:"When True, this component will invert TX/RX signals on the UART bus.",type:"boolean"}}}}};const companionBoardConfigs={adalogger:{rtc:"PCF8523",sdCardCS:10,extras:"SD Card"},"datalogger-m0":{rtc:"PCF8523",sdCardCS:10,extras:"SD Card"},"ds3231-precision":{rtc:"DS3231",sdCardCS:null,extras:"Precision RTC"},"picowbell-adalogger":{rtc:"PCF8523",sdCardCS:9,extras:"SD Card, STEMMA QT"},"datalogger-shield-revb":{rtc:"PCF8523",sdCardCS:10,extras:"SD Card"},"datalogger-shield-reva":{rtc:"DS1307",sdCardCS:10,extras:"SD Card"},"audio-bff":{rtc:null,sdCardCS:"A0",extras:"Audio"},"microsd-bff":{rtc:null,sdCardCS:"TX",extras:"SD Card"},"winc1500-shield":{rtc:null,sdCardCS:"D4",extras:"WiFi"},"airlift-shield":{rtc:null,sdCardCS:"D4",extras:"WiFi"}};let customBoardsCollection={};function addCustomBoard(e,a){if(!e||"string"!=typeof e)return console.error("Invalid board ID"),!1;customBoardsCollection[e]={name:a.name||e,referenceVoltage:a.referenceVoltage||3.3,totalGPIOPins:a.totalGPIOPins||0,totalAnalogPins:a.totalAnalogPins||0,defaultI2C:{scl:a.defaultI2C?.scl||"SCL",sda:a.defaultI2C?.sda||"SDA"},pins:a.pins||[]},appState.boardsData[e]=customBoardsCollection[e];const s=document.getElementById("board-select");if(s){const t=document.createElement("option");t.value=e,t.textContent=a.name||`Custom Board: ${e}`,s.appendChild(t)}return!0}"undefined"==typeof appState&&(window.appState={selectedBoard:null,companionBoard:null,sdCardCS:null,rtcType:"soft",statusLEDBrightness:.5,i2cBuses:[],i2cMultiplexers:[],selectedComponents:[],usedPins:new Set,nextComponentId:1,componentsData:{i2c:[],ds18x20:[],pin:[],pixel:[],pwm:[],servo:[],uart:[]},boardsData:{},isImporting:!1});let componentsData=appState.componentsData;document.addEventListener("DOMContentLoaded",function(){document.getElementById("loading-indicator").classList.add("hidden"),document.getElementById("board-select").addEventListener("change",function(){const e=this.value;if(!e)return document.getElementById("board-details").classList.add("hidden"),void hideSubsequentSections();const a=appState.boardsData[e];appState.selectedBoard={id:e,...a},document.getElementById("ref-voltage").textContent=a.referenceVoltage,document.getElementById("total-gpio").textContent=a.totalGPIOPins,document.getElementById("total-analog").textContent=a.totalAnalogPins,document.getElementById("default-scl").textContent=a.defaultI2C.scl,document.getElementById("default-sda").textContent=a.defaultI2C.sda,document.getElementById("board-details").classList.remove("hidden"),appState.i2cBuses=[{id:"default",scl:a.defaultI2C.scl,sda:a.defaultI2C.sda}],document.getElementById("default-i2c-scl").textContent=a.defaultI2C.scl,document.getElementById("default-i2c-sda").textContent=a.defaultI2C.sda,appState.usedPins.add(a.defaultI2C.scl),appState.usedPins.add(a.defaultI2C.sda),document.getElementById("companion-board-section").classList.remove("hidden"),document.getElementById("manual-config-section").classList.remove("hidden"),document.getElementById("i2c-bus-section").classList.remove("hidden"),document.getElementById("components-section").classList.remove("hidden"),document.getElementById("selected-components-section").classList.remove("hidden"),document.getElementById("generate-section").classList.remove("hidden"),document.getElementById("companion-board-select").value="",document.getElementById("companion-details").classList.add("hidden"),initializeManualConfig(),populatePinsLists(),populateComponentLists()}),document.getElementById("companion-board-select").addEventListener("change",function(){const e=this.value;if(appState.companionBoard=e?{id:e,...companionBoardConfigs[e]}:null,e){const a=companionBoardConfigs[e];document.getElementById("companion-rtc").textContent=a.rtc||"None",document.getElementById("companion-sd-cs").textContent=null===a.sdCardCS?"None":a.sdCardCS,document.getElementById("companion-extras").textContent=a.extras,document.getElementById("companion-details").classList.remove("hidden"),null===a.sdCardCS?(document.getElementById("sd-missing").classList.remove("hidden"),document.getElementById("sd-present").classList.add("hidden"),appState.sdCardCS=null):(appState.sdCardCS=a.sdCardCS,document.getElementById("sd-missing").classList.add("hidden"),document.getElementById("sd-present").classList.remove("hidden"),document.getElementById("sd-cs-pin").textContent=a.sdCardCS,appState.usedPins.add(a.sdCardCS)),a.rtc?(appState.rtcType=a.rtc,document.getElementById("rtc-missing").classList.add("hidden"),document.getElementById("rtc-present").classList.remove("hidden"),document.getElementById("rtc-type").textContent=a.rtc):(document.getElementById("rtc-missing").classList.remove("hidden"),document.getElementById("rtc-present").classList.add("hidden"),appState.rtcType="soft",document.getElementById("rtc-select").value="soft")}else document.getElementById("companion-details").classList.add("hidden"),document.getElementById("sd-missing").classList.remove("hidden"),document.getElementById("sd-present").classList.add("hidden"),document.getElementById("rtc-missing").classList.remove("hidden"),document.getElementById("rtc-present").classList.add("hidden"),appState.sdCardCS=null,appState.rtcType="soft",document.getElementById("rtc-select").value="soft";populatePinsLists()}),document.getElementById("add-sd-card").addEventListener("change",function(){this.checked?document.getElementById("sd-card-pin-select").classList.remove("hidden"):(document.getElementById("sd-card-pin-select").classList.add("hidden"),appState.sdCardCS=null)}),document.getElementById("rtc-select").addEventListener("change",function(){appState.rtcType=this.value}),document.getElementById("led-brightness").addEventListener("input",function(){const e=parseFloat(this.value);appState.statusLEDBrightness=e,document.getElementById("brightness-value").textContent=e.toFixed(1)}),document.getElementById("add-i2c-bus").addEventListener("change",function(){if(this.checked)document.getElementById("additional-i2c-config").classList.remove("hidden");else{document.getElementById("additional-i2c-config").classList.add("hidden");const e=appState.i2cBuses.findIndex(e=>"default"!==e.id);if(-1!==e){const a=appState.i2cBuses[e];appState.usedPins.delete(a.scl),appState.usedPins.delete(a.sda),appState.i2cBuses.splice(e,1),updateI2CBusOptions()}}}),document.getElementById("add-mux-btn").addEventListener("click",function(){showAddMultiplexerModal()}),document.getElementById("generate-config-btn").addEventListener("click",function(){generateConfiguration()}),document.getElementById("download-config-btn").addEventListener("click",function(){downloadConfiguration()}),document.getElementById("import-btn").addEventListener("click",function(){const a=document.getElementById("import-file");if(!a.files||0===a.files.length)return void alert("Please select a file to import.");const s=a.files[0],t=new FileReader;t.onload=function(s){try{const e=s.target.result;document.getElementById("import-json").value=e,document.getElementById("import-json-btn").click(),a.value=""}catch(e){alert("Error reading file: "+e.message)}},t.onerror=function(){alert("Error reading file.")},t.readAsText(s)}),document.getElementById("import-json-btn").addEventListener("click",function(){importConfiguration()}),document.getElementById("export-btn").addEventListener("click",function(){downloadConfiguration(!0)}),document.querySelectorAll(".reset-btn").forEach(e=>{e.addEventListener("click",function(){confirm("Are you sure you want to reset the configurator? All your current configuration will be lost.")&&(resetAppState(),window.location.reload())})}),document.getElementById("modal-cancel").addEventListener("click",function(){closeModal()}),document.getElementById("modal-save").addEventListener("click",function(){saveModalData()}),document.querySelectorAll(".component-search").forEach(e=>{e.addEventListener("input",function(){const e=this.value.toLowerCase(),a=this.id.split("-")[0],s=document.getElementById(`${a}-component-list`),t=s.querySelectorAll(".component-card");t.forEach(a=>{const s=a.querySelector("h4").textContent.toLowerCase(),t=s.includes(e);a.style.display=t?"block":"none"})})}),document.getElementById("add-custom-board-btn").addEventListener("click",function(){const e=document.getElementById("custom-board-name").value.trim();if(!e)return void alert("Please enter a board name");const a="custom-"+e.toLowerCase().replace(/\s+/g,"-"),s=parseFloat(document.getElementById("custom-board-ref-voltage").value)||3.3,t=parseInt(document.getElementById("custom-board-gpio").value)||0,n=parseInt(document.getElementById("custom-board-analog").value)||0,i=document.getElementById("custom-board-scl").value.trim()||"SCL",d=document.getElementById("custom-board-sda").value.trim()||"SDA";if(addCustomBoard(a,{name:e,referenceVoltage:s,totalGPIOPins:t,totalAnalogPins:n,defaultI2C:{scl:i,sda:d},pins:[]})){alert("Custom board added successfully!"),document.getElementById("custom-board-name").value="",document.getElementById("custom-board-ref-voltage").value="3.3",document.getElementById("custom-board-gpio").value="0",document.getElementById("custom-board-analog").value="0",document.getElementById("custom-board-scl").value="",document.getElementById("custom-board-sda").value="",document.getElementById("custom-boards-list").classList.remove("hidden");const s=document.createElement("li");s.textContent=`${e} (ID: ${a})`,document.getElementById("custom-boards-items").appendChild(s)}else alert("Failed to add custom board. Please try again.")})});function hideSubsequentSections(){document.getElementById("companion-board-section").classList.add("hidden"),document.getElementById("manual-config-section").classList.add("hidden"),document.getElementById("i2c-bus-section").classList.add("hidden"),document.getElementById("components-section").classList.add("hidden"),document.getElementById("selected-components-section").classList.add("hidden"),document.getElementById("generate-section").classList.add("hidden")}function resetSubsequentSelections(){document.getElementById("companion-board-select").value="",document.getElementById("companion-details").classList.add("hidden"),document.getElementById("add-sd-card").checked=!1,document.getElementById("sd-card-pin-select").classList.add("hidden"),document.getElementById("rtc-select").value="soft",document.getElementById("led-brightness").value=.5,document.getElementById("brightness-value").textContent="0.5",document.getElementById("add-i2c-bus").checked=!1,document.getElementById("additional-i2c-config").classList.add("hidden"),appState.selectedComponents=[],appState.i2cMultiplexers=[],updateSelectedComponentsList(),appState.usedPins=new Set,appState.selectedBoard&&(appState.usedPins.add(appState.selectedBoard.defaultI2C.scl),appState.usedPins.add(appState.selectedBoard.defaultI2C.sda))}function initializeManualConfig(){document.getElementById("sd-missing").classList.remove("hidden"),document.getElementById("sd-present").classList.add("hidden"),document.getElementById("rtc-missing").classList.remove("hidden"),document.getElementById("rtc-present").classList.add("hidden"),document.getElementById("led-brightness").value=.5,document.getElementById("brightness-value").textContent="0.5",appState.statusLEDBrightness=.5}function populatePinsLists(){if(!appState.selectedBoard)return;const e=appState.selectedBoard.pins,a=document.getElementById("sd-pins-list");a.innerHTML="",e.forEach(e=>{const s=document.createElement("div");s.className="pin"+(appState.usedPins.has(e)?" used":""),s.textContent=e,appState.usedPins.has(e)||s.addEventListener("click",function(){null!==appState.sdCardCS&&appState.usedPins.delete(appState.sdCardCS),appState.sdCardCS=e,appState.usedPins.add(e);const t=a.querySelectorAll(".pin");t.forEach(e=>e.classList.remove("selected")),s.classList.add("selected"),populatePinsLists()}),a.appendChild(s)});const s=document.getElementById("scl-pins-list");s.innerHTML="",e.forEach(e=>{const a=document.createElement("div");a.className="pin"+(appState.usedPins.has(e)?" used":""),a.textContent=e,appState.usedPins.has(e)||a.addEventListener("click",function(){let t=appState.i2cBuses.find(e=>"default"!==e.id);t?(t.scl!==void 0&&appState.usedPins.delete(t.scl),t.scl=e):(t={id:"additional",scl:e,sda:void 0},appState.i2cBuses.push(t)),appState.usedPins.add(e);const n=s.querySelectorAll(".pin");n.forEach(e=>e.classList.remove("selected")),a.classList.add("selected"),populatePinsLists(),t.sda!==void 0&&updateI2CBusOptions()}),s.appendChild(a)});const t=document.getElementById("sda-pins-list");t.innerHTML="",e.forEach(e=>{const a=document.createElement("div");a.className="pin"+(appState.usedPins.has(e)?" used":""),a.textContent=e,appState.usedPins.has(e)||a.addEventListener("click",function(){let s=appState.i2cBuses.find(e=>"default"!==e.id);s?(s.sda!==void 0&&appState.usedPins.delete(s.sda),s.sda=e):(s={id:"additional",scl:void 0,sda:e},appState.i2cBuses.push(s)),appState.usedPins.add(e);const n=t.querySelectorAll(".pin");n.forEach(e=>e.classList.remove("selected")),a.classList.add("selected"),populatePinsLists(),s.scl!==void 0&&updateI2CBusOptions()}),t.appendChild(a)})}function updateI2CBusOptions(){const e=document.getElementById("i2c-bus-select");e.innerHTML="",appState.i2cBuses.forEach(a=>{if(a.scl!==void 0&&a.sda!==void 0){const s=document.createElement("option");s.value=a.id,s.textContent="default"===a.id?"Default I2C Bus (SCL: "+a.scl+", SDA: "+a.sda+")":"Additional I2C Bus (SCL: "+a.scl+", SDA: "+a.sda+")",e.appendChild(s)}}),appState.i2cMultiplexers.forEach(a=>{for(let s=0;s{const s=createComponentCard(a,"i2c");e.appendChild(s)}),appState.componentsData.ds18x20.forEach(a=>{const s=createComponentCard(a,"ds18x20");e.appendChild(s)}),appState.componentsData.pin.forEach(a=>{const s=createComponentCard(a,"pin");e.appendChild(s)}),appState.componentsData.pixel.forEach(a=>{const s=createComponentCard(a,"pixel");e.appendChild(s)}),appState.componentsData.pwm.forEach(a=>{const s=createComponentCard(a,"pwm");e.appendChild(s)}),appState.componentsData.servo.forEach(a=>{const s=createComponentCard(a,"servo");e.appendChild(s)}),appState.componentsData.uart.forEach(a=>{const s=createComponentCard(a,"uart");e.appendChild(s)});const a=document.getElementById("i2c-component-list");a.innerHTML="",appState.componentsData.i2c.forEach(e=>{const s=createComponentCard(e,"i2c");a.appendChild(s)});const s=document.getElementById("ds18x20-component-list");s.innerHTML="",appState.componentsData.ds18x20.forEach(e=>{const a=createComponentCard(e,"ds18x20");s.appendChild(a)});const t=document.getElementById("pin-component-list");t.innerHTML="",appState.componentsData.pin.forEach(e=>{const a=createComponentCard(e,"pin");t.appendChild(a)});const n=document.getElementById("pixel-component-list");n.innerHTML="",appState.componentsData.pixel.forEach(e=>{const a=createComponentCard(e,"pixel");n.appendChild(a)});const i=document.getElementById("pwm-component-list");i.innerHTML="",appState.componentsData.pwm.forEach(e=>{const a=createComponentCard(e,"pwm");i.appendChild(a)});const d=document.getElementById("servo-component-list");d.innerHTML="",appState.componentsData.servo.forEach(e=>{const a=createComponentCard(e,"servo");d.appendChild(a)});const r=document.getElementById("uart-component-list");r.innerHTML="",appState.componentsData.uart.forEach(e=>{const a=createComponentCard(e,"uart");r.appendChild(a)}),updateI2CBusOptions(),document.getElementById("all-search").addEventListener("input",function(){const e=this.value.toLowerCase(),a=document.querySelectorAll("#all-component-list .component-card");a.forEach(a=>{const s=a.querySelector("h3").textContent.toLowerCase();a.style.display=s.includes(e)?"block":"none"})})}function createComponentCard(e,a){const s=document.createElement("div");if(s.className="component-card",s.dataset.id=e.id,s.dataset.type=a,e.image){const a=document.createElement("img");a.src=e.image.startsWith("http")?e.image:"https://raw.githubusercontent.com/adafruit/Wippersnapper_Components/refs/heads/main/"+e.image,a.alt=e.name,s.appendChild(a)}const t=document.createElement("h4");if(t.textContent=e.name,s.appendChild(t),"i2c"===a&&e.address){const a=document.createElement("p");a.textContent=`Address: ${e.address}`,s.appendChild(a)}if(e.dataTypes&&0";if(d+=` +
+ + +
+
+ + +
+ `,"i2c"!==a)"ds18x20"===a?(d+=` +
+ + +
+ `,d+=` +
+ + +
+ `):"pin"===a||"pwm"===a||"servo"===a?(d+=` +
+ + +
+ `):"pixel"===a?(d+=` +
+ + +
+
+ + +
+ `):"uart"===a&&(d+=` +
+ + +
+
+ + +
+ `);else if(d+=` +
+ + +
+ `,d+=` +
+ + +
+ `,"pca9546"===e.id||"pca9548"===e.id||"tca9546"===e.id||"tca9548"===e.id){const a=e.id.includes("9548")?8:4;d+=` +
+ + +

This multiplexer has ${a} channels

+
+ `}e.dataTypes&&0 +

Select Data Types:

+
+ `,e.dataTypes.forEach(e=>{const a="string"==typeof e?e:e.displayName||e.sensorType,s="string"==typeof e?e:JSON.stringify(e);d+=` +
+ +
+ `}),d+=` +
+ + `),d+="",n.innerHTML=d,n.dataset.componentId=e.id,n.dataset.componentType=a,s.style.display="block"}function showAddMultiplexerModal(){if(0===appState.i2cBuses.length)return void alert("No I2C buses available. Please configure an I2C bus first.");const e=appState.componentsData.i2c.filter(e=>"pca9546"===e.id||"pca9548"===e.id||"tca9546"===e.id||"tca9548"===e.id);if(0===e.length)return void alert("No multiplexer components found in the component data.");const a=document.getElementById("component-modal"),s=document.getElementById("modal-title"),t=document.getElementById("modal-content");s.textContent="Add I2C Multiplexer";let n=` +
+ + +
+
+ + +
+ `,t.innerHTML=n,document.getElementById("select-multiplexer-btn").addEventListener("click",function(){const s=document.getElementById("multiplexer-select").value,t=e.find(e=>e.id===s);a.style.display="none",showComponentConfigModal(t,"i2c")}),document.getElementById("cancel-multiplexer-btn").addEventListener("click",function(){a.style.display="none"}),a.style.display="block"}function closeModal(){const e=document.getElementById("component-modal");e.style.display="none"}function saveModalData(){const e=document.getElementById("modal-content"),a=e.dataset.componentId,s=e.dataset.componentType,t=appState.componentsData[s].find(e=>e.id===a),n=document.getElementById("component-name").value,i=parseInt(document.getElementById("component-period").value),d={instanceId:appState.nextComponentId++,name:n,componentAPI:s,period:i};if("i2c"===s){const e=document.getElementById("modal-i2c-bus").value,s=document.getElementById("modal-i2c-address").value;if(d.i2cDeviceName=a,d.i2cDeviceAddress=s,e.startsWith("mux-")){const[a,s,t,n]=e.split("-"),i=appState.i2cMultiplexers.find(e=>e.id===parseInt(s));i&&(d.i2cMuxAddress=i.address,d.i2cMuxChannel=n)}else if("additional"===e){const e=appState.i2cBuses.find(e=>"additional"===e.id);e&&(d.i2cBusScl=e.scl.toString(),d.i2cBusSda=e.sda.toString())}if("pca9546"===a||"pca9548"===a||"tca9546"===a||"tca9548"===a){const e=parseInt(document.getElementById("modal-mux-channels").value),a={id:appState.nextComponentId-1,address:s,channels:e};appState.i2cMultiplexers.push(a),updateI2CBusOptions(),appState.selectedComponents=appState.selectedComponents.filter(e=>!(e.i2cMuxAddress&&e.i2cMuxAddress===d.i2cDeviceAddress))}else{const e=document.querySelectorAll("input[name=\"data-type\"]:checked");0{try{return{type:JSON.parse(a.value)}}catch(s){return{type:a.value}}}))}}else if("ds18x20"===s){const e=document.getElementById("modal-pin-select").value,a=document.getElementById("modal-resolution").value;d.pinName=`D${e}`,d.sensorResolution=parseInt(a),appState.usedPins.add(parseInt(e));const s=document.querySelectorAll("input[name=\"data-type\"]:checked");0{const s=e.value.replace(/"/g,"");d[`sensorType${a+1}`]=s}))}else if("pin"===s||"pwm"===s||"servo"===s){const e=document.getElementById("modal-pin-select").value;d.pinName=`D${e}`,appState.usedPins.add(parseInt(e))}else if("pixel"===s){const e=document.getElementById("modal-pin-select").value,a=document.getElementById("modal-pixel-count").value;d.pinName=`D${e}`,d.numPixels=parseInt(a),appState.usedPins.add(parseInt(e))}else if("uart"===s){const e=document.getElementById("modal-uart-tx").value,a=document.getElementById("modal-uart-rx").value;d.txPin=`D${e}`,d.rxPin=`D${a}`,appState.usedPins.add(parseInt(e)),appState.usedPins.add(parseInt(a));const s=document.querySelectorAll("input[name=\"data-type\"]:checked");0{try{return JSON.parse(a.value)}catch(s){return a.value}}))}appState.selectedComponents.push(d),updateSelectedComponentsList(),populatePinsLists(),closeModal()}function updateSelectedComponentsList(){const e=document.getElementById("selected-components-list");if(0===appState.selectedComponents.length)return void(e.innerHTML="

No components selected yet.

");let a="
    ";appState.selectedComponents.forEach(e=>{let s="";if("i2c"===e.componentAPI){if(s+=`
    Address: ${e.i2cDeviceAddress}`,e.i2cBusScl&&e.i2cBusSda)s+=`
    Bus: Custom (SCL: ${e.i2cBusScl}, SDA: ${e.i2cBusSda})`;else if(e.i2cMuxAddress)s+=`
    Connected to: Multiplexer ${e.i2cMuxAddress} - Channel ${e.i2cMuxChannel}`;else{const e=appState.i2cBuses.find(e=>"default"===e.id);e&&(s+=`
    Bus: Default (SCL: ${e.scl}, SDA: ${e.sda})`)}e.i2cDeviceSensorTypes&&0Data types: ",e.i2cDeviceSensorTypes.forEach((e,a)=>{const t="object"==typeof e.type?e.type.displayName||e.type.sensorType:e.type;s+=(0Pin: ${e.pinName}`,s+=`
    Resolution: ${e.sensorResolution}-bit`;const a=[];for(let s=1;s<=e.sensorTypeCount;s++)e[`sensorType${s}`]&&a.push(e[`sensorType${s}`]);0Data types: "+a.join(", "))}else"pin"===e.componentAPI||"pwm"===e.componentAPI||"servo"===e.componentAPI?s+=`
    Pin: ${e.pinName}`:"pixel"===e.componentAPI?(s+=`
    Pin: ${e.pinName}`,s+=`
    Pixels: ${e.numPixels}`):"uart"===e.componentAPI&&(s+=`
    TX Pin: ${e.txPin}, RX Pin: ${e.rxPin}`,e.sensorTypes&&0Data types: ",e.sensorTypes.forEach((e,a)=>{const t="object"==typeof e?e.displayName||e.sensorType:e;s+=(0Polling period: ${e.period} seconds`,a+=`
  • +
    +
    + ${e.name} (${e.componentAPI}) + ${s} +
    +
    + +
    +
    +
  • `}),a+="
",e.innerHTML=a}function removeComponent(e){const a=appState.selectedComponents.findIndex(a=>a.instanceId===e);if(-1===a)return;const s=appState.selectedComponents[a];if(s.pinName){const e=parseInt(s.pinName.replace("D",""));appState.usedPins.delete(e)}if(s.txPin){const e=parseInt(s.txPin.replace("D",""));appState.usedPins.delete(e)}if(s.rxPin){const e=parseInt(s.rxPin.replace("D",""));appState.usedPins.delete(e)}if("i2c"===s.componentAPI&&("pca9546"===s.i2cDeviceName||"pca9548"===s.i2cDeviceName||"tca9546"===s.i2cDeviceName||"tca9548"===s.i2cDeviceName)){const e=appState.i2cMultiplexers.findIndex(e=>e.id===s.instanceId);-1!==e&&(appState.i2cMultiplexers.splice(e,1),updateI2CBusOptions(),appState.selectedComponents=appState.selectedComponents.filter(e=>!(e.i2cMuxAddress&&e.i2cMuxAddress===s.i2cDeviceAddress)))}appState.selectedComponents.splice(a,1),updateSelectedComponentsList(),populatePinsLists()}function generateConfiguration(){if(!appState.selectedBoard)return void alert("Please select a board before generating configuration.");if(0===appState.selectedComponents.length)return void alert("Please add at least one component before generating configuration.");const e={exportedFromDevice:{referenceVoltage:appState.selectedBoard.referenceVoltage,totalGPIOPins:appState.selectedBoard.totalGPIOPins,totalAnalogPins:appState.selectedBoard.totalAnalogPins,statusLEDBrightness:appState.statusLEDBrightness},components:[]};null!==appState.sdCardCS&&(e.exportedFromDevice.sd_cs_pin=appState.sdCardCS),"soft"!==appState.rtcType&&(e.exportedFromDevice.rtc=appState.rtcType),appState.selectedComponents.forEach(a=>{const s={...a};delete s.instanceId,e.components.push(s)});const a=JSON.stringify(e,null,4);document.getElementById("config-output").textContent=a,document.getElementById("config-output-container").classList.remove("hidden"),document.getElementById("export-config").textContent=a}function downloadConfiguration(e=!1){const s=e?document.getElementById("export-config").textContent:document.getElementById("config-output").textContent;if("No configuration generated yet."===s)return void alert("Please generate a configuration first.");const t=new Blob([s],{type:"application/json"}),n=URL.createObjectURL(t),i=document.createElement("a");i.href=n,i.download="config.json",document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(n)}function importConfiguration(){const e=document.getElementById("import-json").value.trim();if(!e)return void alert("Please paste a valid JSON configuration.");try{const a=JSON.parse(e);resetAppState();const s=importConfigObject(a);appState.isImporting||(s?(alert("Configuration imported successfully. Please check the Build tab to see your configuration."),openTab(null,"BuildConfig")):(openTab(null,"BuildConfig"),setTimeout(()=>{const e=document.querySelector(".section");e&&e.scrollIntoView({behavior:"smooth"})},500)))}catch(e){alert("Error importing configuration: "+e.message)}}function resetAppState(){appState.selectedBoard=null,appState.companionBoard=null,appState.sdCardCS=null,appState.rtcType="soft",appState.statusLEDBrightness=.5,appState.i2cBuses=[],appState.i2cMultiplexers=[],appState.selectedComponents=[],appState.usedPins=new Set,appState.nextComponentId=1,appState.isImporting=!1,document.getElementById("board-select").value="",document.getElementById("companion-board-select").value="",document.getElementById("led-brightness").value=.5,document.getElementById("brightness-value").textContent="0.5",document.getElementById("add-sd-card").checked=!1,document.getElementById("add-i2c-bus").checked=!1,hideSubsequentSections(),document.getElementById("board-details").classList.add("hidden"),document.getElementById("sd-missing").classList.remove("hidden"),document.getElementById("sd-present").classList.add("hidden"),document.getElementById("rtc-missing").classList.remove("hidden"),document.getElementById("rtc-present").classList.add("hidden");const e=document.getElementById("selected-components-list");e.innerHTML="

No components selected yet.

"}function importConfigObject(e){function a(){if(this.value){if(console.log("Board selected, processing pending configurations..."),void 0!==appState.pendingSDCardPin&&setTimeout(()=>{document.getElementById("add-sd-card").checked=!0;const e=new Event("change");document.getElementById("add-sd-card").dispatchEvent(e);const a=document.getElementById("sd-cs-pin-select");a&&(a.value=appState.pendingSDCardPin,appState.sdCardCS=appState.pendingSDCardPin,appState.usedPins.add(appState.pendingSDCardPin)),document.getElementById("sd-missing").classList.add("hidden"),document.getElementById("sd-present").classList.remove("hidden"),document.getElementById("sd-cs-pin").textContent=appState.pendingSDCardPin},100),appState.pendingRTC){document.getElementById("rtc-select").value=appState.pendingRTC;const e=new Event("change");document.getElementById("rtc-select").dispatchEvent(e),document.getElementById("rtc-missing").classList.add("hidden"),document.getElementById("rtc-present").classList.remove("hidden"),document.getElementById("rtc-type").textContent=appState.pendingRTC}appState.pendingComponents&&0{if("i2c"===e.componentAPI&&("pca9546"===e.i2cDeviceName||"pca9548"===e.i2cDeviceName||"tca9546"===e.i2cDeviceName||"tca9548"===e.i2cDeviceName)){const a=e.i2cDeviceName.includes("9548")?8:4,s={id:appState.nextComponentId++,address:e.i2cMuxAddress||e.i2cDeviceAddress,channels:a};appState.i2cMultiplexers.push(s);const t={...e,instanceId:s.id};appState.selectedComponents.push(t)}}),appState.pendingComponents.forEach(e=>{if("i2c"!==e.componentAPI||"pca9546"!==e.i2cDeviceName&&"pca9548"!==e.i2cDeviceName&&"tca9546"!==e.i2cDeviceName&&"tca9548"!==e.i2cDeviceName){const a={...e,instanceId:appState.nextComponentId++};if(appState.selectedComponents.push(a),e.pinName){const a=parseInt(e.pinName.replace("D",""));appState.usedPins.add(a)}if(e.txPin){const a=parseInt(e.txPin.replace("D",""));appState.usedPins.add(a)}if(e.rxPin){const a=parseInt(e.rxPin.replace("D",""));appState.usedPins.add(a)}if(e.i2cBusScl&&e.i2cBusSda){const a=parseInt(e.i2cBusScl),s=parseInt(e.i2cBusSda),t=appState.i2cBuses.find(e=>e.scl===a&&e.sda===s);if(!t&&a&&s){const e=`bus_${appState.i2cBuses.length}`;appState.i2cBuses.push({id:e,scl:a,sda:s}),appState.usedPins.add(a),appState.usedPins.add(s)}}}}),updateSelectedComponentsList(),updateI2CBusOptions(),appState.pendingComponents=[]),i.removeEventListener("change",a)}}appState.isImporting=!0,console.log("Importing configuration...");const s=e.exportedFromDevice;let t=!1,n=null;for(const[a,i]of Object.entries(appState.boardsData))if(i.referenceVoltage===s.referenceVoltage&&i.totalGPIOPins===s.totalGPIOPins&&i.totalAnalogPins===s.totalAnalogPins){n=a,t=!0;break}if(n){document.getElementById("board-select").value=n;const e=new Event("change");document.getElementById("board-select").dispatchEvent(e)}else alert("Import successful! To complete the process, please select your microcontroller board from the Build Configuration tab."),setTimeout(()=>{const e=document.getElementById("board-select");if(e.style.boxShadow="0 0 10px #2e8b57",e.style.border="2px solid #2e8b57",e.style.animation="pulse 1.5s infinite",!document.getElementById("pulse-animation")){const e=document.createElement("style");e.id="pulse-animation",e.textContent=` + @keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(46, 139, 87, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(46, 139, 87, 0); } + 100% { box-shadow: 0 0 0 0 rgba(46, 139, 87, 0); } + } + `,document.head.appendChild(e)}const a=document.querySelector(".section");if(a){const a=document.createElement("p");a.id="board-select-helper",a.style.color="#2e8b57",a.style.fontWeight="bold",a.textContent="Please select your microcontroller board to continue the import process.";const s=document.getElementById("board-select-helper");s&&s.remove(),((e,a)=>{a.parentNode.insertBefore(e,a.nextSibling)})(a,e)}e.addEventListener("change",function(){if(this.value){this.style.boxShadow="",this.style.border="",this.style.animation="";const e=document.getElementById("board-select-helper");e&&e.remove()}})},500);void 0!==s.sd_cs_pin&&(appState.sdCardCS=s.sd_cs_pin,document.getElementById("add-sd-card").checked=!0,appState.pendingSDCardPin=s.sd_cs_pin),s.rtc&&(appState.rtcType=s.rtc,appState.pendingRTC=s.rtc),void 0!==s.statusLEDBrightness&&(appState.statusLEDBrightness=s.statusLEDBrightness,document.getElementById("led-brightness").value=s.statusLEDBrightness,document.getElementById("brightness-value").textContent=s.statusLEDBrightness),appState.pendingComponents=[],e.components&&Array.isArray(e.components)&&(appState.pendingComponents=e.components.map(e=>({...e})));const i=document.getElementById("board-select");return i.removeEventListener("change",a),i.addEventListener("change",a),setTimeout(()=>{appState.isImporting=!1},500),t}function initializeSampleComponents(){appState.componentsData.i2c=[{id:"bme280",name:"BME280",address:"0x77",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity","pressure","altitude"]},{id:"sht30",name:"SHT30",address:"0x44",dataTypes:["ambient-temp","ambient-temp-fahrenheit","relative-humidity"]},{id:"mcp9808",name:"MCP9808",address:"0x18",dataTypes:["ambient-temp","ambient-temp-fahrenheit"]},{id:"bh1750",name:"BH1750",address:"0x23",dataTypes:["light"]},{id:"sgp30",name:"SGP30",address:"0x58",dataTypes:["eco2","tvoc"]},{id:"pca9546",name:"PCA9546 4-Channel Multiplexer",address:"0x70",channels:4},{id:"pca9548",name:"PCA9548 8-Channel Multiplexer",address:"0x70",channels:8},{id:"tca9546",name:"TCA9546 4-Channel Multiplexer",address:"0x70",channels:4},{id:"tca9548",name:"TCA9548 8-Channel Multiplexer",address:"0x70",channels:8}],appState.componentsData.ds18x20=[{id:"ds18b20",name:"DS18B20",dataTypes:["object-temp","object-temp-fahrenheit"]},{id:"ds18b20_waterproof",name:"DS18B20 Waterproof",dataTypes:["object-temp","object-temp-fahrenheit"]}],appState.componentsData.pin=[{id:"led",name:"LED",dataTypes:[]},{id:"push_button",name:"Push Button",dataTypes:["digital-input"]},{id:"toggle_switch",name:"Toggle Switch",dataTypes:["digital-input"]},{id:"potentiometer",name:"Potentiometer",dataTypes:["analog-input"]}],appState.componentsData.pixel=[{id:"neopixel",name:"NeoPixel",dataTypes:[]},{id:"dotstar",name:"DotStar",dataTypes:[]}],appState.componentsData.pwm=[{id:"dimmable_led",name:"Dimmable LED",dataTypes:[]},{id:"piezo_buzzer",name:"Piezo Buzzer",dataTypes:[]}],appState.componentsData.servo=[{id:"servo",name:"Servo Motor",dataTypes:[]}],appState.componentsData.uart=[{id:"pms5003",name:"PMS5003 Air Quality Sensor",dataTypes:["pm10-std","pm25-std","pm100-std"]}]}function initializeSampleBoards(){appState.boardsData={"feather-esp32":{referenceVoltage:3.3,totalGPIOPins:21,totalAnalogPins:14,defaultI2C:{scl:22,sda:23},pins:[0,2,4,5,12,13,14,15,16,17,18,19,21,22,23,25,26,27,32,33,34,35,36,39]},"feather-esp32s2":{referenceVoltage:3.3,totalGPIOPins:22,totalAnalogPins:6,defaultI2C:{scl:42,sda:41},pins:[0,5,6,7,8,9,10,11,12,13,14,15,16,17,18,21,33,34,35,36,37,38,39,41,42]},"feather-esp32s3-tft":{referenceVoltage:3.3,totalGPIOPins:18,totalAnalogPins:6,defaultI2C:{scl:9,sda:8},pins:[1,3,4,5,6,7,8,9,10,11,12,13,14,17,18,21,38,39,40,41,42]},"feather-esp32c3":{referenceVoltage:3.3,totalGPIOPins:13,totalAnalogPins:4,defaultI2C:{scl:5,sda:4},pins:[0,1,2,3,4,5,6,7,8,9,10,18,19,20,21]},"qtpy-esp32c3":{referenceVoltage:3.3,totalGPIOPins:11,totalAnalogPins:4,defaultI2C:{scl:5,sda:4},pins:[0,1,2,3,4,5,6,7,8,9,10]}}}"undefined"==typeof loadWippersnapperData&&(console.log("No data loader detected, initializing with sample data"),document.addEventListener("DOMContentLoaded",function(){document.getElementById("loading-indicator").classList.add("hidden"),initializeSampleBoards(),initializeSampleComponents()})); \ No newline at end of file diff --git a/untested_product_image_api.py b/untested_product_image_api.py new file mode 100644 index 0000000..82fe413 --- /dev/null +++ b/untested_product_image_api.py @@ -0,0 +1,40 @@ +import requests +import re + +BASE_API = 'https://www.adafruit.com/api' + + +def get_product_image(url): + if '/product/' in url: + product_id = re.search(r'/product/(\d+)', url) + if product_id: + product_id = product_id.group(1) + api_url = f'{BASE_API}/product/{product_id}' + response = requests.get(api_url) + if response.ok: + data = response.json() + return data.get('product_image') + elif '/category/' in url: + category_id = re.search(r'/category/(\d+)', url) + if category_id: + category_id = category_id.group(1) + api_url = f'{BASE_API}/category/{category_id}' + response = requests.get(api_url) + if response.ok: + data = response.json() + products = data.get('products') + if products: + return products[0].get('product_image') + + return None + + +# Example usage: +product_url = 'https://www.adafruit.com/product/998' +category_url = 'https://www.adafruit.com/category/118' + +product_image_url = get_product_image(product_url) +print('Product Image:', product_image_url) + +category_image_url = get_product_image(category_url) +print('First Product Image from Category:', category_image_url) diff --git a/windows_fat32_create.ps1 b/windows_fat32_create.ps1 index 0195997..e1c5701 100644 --- a/windows_fat32_create.ps1 +++ b/windows_fat32_create.ps1 @@ -1,36 +1,53 @@ -# Create a fixed-size file to serve as your disk image -$file = "C:\path\to\fat32_image.img" -$size = 1MB # Adjust size as needed (1MB shown here) +# Create FAT32 image with specified size +param( + [Parameter(Mandatory=$true)] + [string]$ImagePath, + + [Parameter(Mandatory=$true)] + [int]$SizeMB +) # Create an empty file of the specified size -fsutil file createnew $file $size +$sizeBytes = $SizeMB * 1MB +fsutil file createnew $ImagePath $sizeBytes -# Use diskpart via script file -$diskpartScript = "C:\path\to\diskpart_script.txt" +# Create temporary diskpart script +$scriptPath = [System.IO.Path]::GetTempFileName() # Create the script file content @" -create vdisk file=$file maximum=1 type=fixed -select vdisk file=$file +create vdisk file=$ImagePath maximum=$SizeMB type=fixed +select vdisk file=$ImagePath attach vdisk create partition primary format fs=fat32 quick assign letter=X exit -"@ | Out-File -FilePath $diskpartScript -Encoding ascii +"@ | Out-File -FilePath $scriptPath -Encoding ascii # Run diskpart with the script -diskpart /s $diskpartScript +try { + diskpart /s $scriptPath + + # Wait for drive letter to be assigned + Start-Sleep -Seconds 1 + + # Now you can copy your files to X: + # Copy-Item -Path "C:\path\to\your_files\*" -Destination "X:\" -Recurse -# Now you can copy your files to X: -# Copy-Item -Path "C:\path\to\your_files\*" -Destination "X:\" -Recurse - -# When done, detach the disk -$detachScript = "C:\path\to\detach_script.txt" + # Return the drive letter + return "X:" +} finally { + # Detach the disk + $detachScript = [System.IO.Path]::GetTempFileName() @" -select vdisk file=$file +select vdisk file=$ImagePath detach vdisk exit "@ | Out-File -FilePath $detachScript -Encoding ascii -diskpart /s $detachScript \ No newline at end of file + diskpart /s $detachScript + Remove-Item $detachScript -Force + # Clean up the script file + Remove-Item $scriptPath -Force +} \ No newline at end of file diff --git a/windows_merge-script.py b/windows_merge-script.py new file mode 100644 index 0000000..d18d8e1 --- /dev/null +++ b/windows_merge-script.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import tempfile +import shutil +import hashlib +import json +import glob +from pathlib import Path + +# Constants +BASE_DIR = Path(r"C:\dev\arduino\Adafruit_Wippersnapper_Offline_Configurator") +OUTPUT_DIR = BASE_DIR / "merged_firmware" +FIRMWARE_DIR = BASE_DIR / "latest_firmware" +FAT_SIZE = 1024 * 1024 # 1MB FAT partition +OFFLINE_FILES = ["offline.js", "offline.html"] + +# Platform-specific parameters +PLATFORM_PARAMS = { + "RP2040": { + "family": "RP2040", + "fat_base_addr": 0x10100000, # Example, would need to be adjusted per target + "uf2_family_id": "0xe48bff56" # RP2040 family ID for RP2040/RP2350/etc + }, + "ESP32": { + "family": "ESP32", + "fat_base_addr": 0x310000, # Example, would need to be adjusted per target + "partition_size": FAT_SIZE + } +} + +# Map of board identifiers to detect specific target properties +# This could be expanded based on the ci-arduino/all_platforms.py information +BOARD_SPECIFIC_PARAMS = { + # ESP32-S2 boards might have different partition layouts + "esp32s2": { + "platform": "ESP32", + "fat_base_addr": 0x310000 # Adjust as needed + }, + # ESP32-S3 boards + "esp32s3": { + "platform": "ESP32", + "fat_base_addr": 0x310000 # Adjust as needed + }, + # RP2350 might have specific settings + "rp2350": { + "platform": "RP2040", + "fat_base_addr": 0x10100000 # Adjust as needed + } + # Add more board-specific configurations as needed +} + +# Ensure output directory exists +os.makedirs(OUTPUT_DIR, exist_ok=True) + +def create_fat_image(output_path, files_to_include): + """Create a FAT32 image containing the specified files""" + # Create a temporary directory for mounting + with tempfile.TemporaryDirectory() as mount_dir: + # Create a raw disk image + img_size_bytes = FAT_SIZE + with open(output_path, 'wb') as f: + f.write(b'\x00' * img_size_bytes) + + # Format as FAT32 using Windows diskpart + diskpart_script = tempfile.NamedTemporaryFile(delete=False, suffix='.txt') + try: + # Get absolute paths + abs_output_path = os.path.abspath(output_path) + + # Write diskpart script + with open(diskpart_script.name, 'w') as f: + f.write(f"create vdisk file=\"{abs_output_path}\" maximum={img_size_bytes // 1024 // 1024} type=fixed\n") + f.write(f"select vdisk file=\"{abs_output_path}\"\n") + f.write("attach vdisk\n") + f.write("create partition primary\n") + f.write("format fs=fat32 quick\n") + f.write("assign letter=X\n") + f.write("exit\n") + + # Run diskpart + subprocess.run(["diskpart", "/s", diskpart_script.name], check=True) + + # Copy files to the mounted drive + for file_path in files_to_include: + src_path = BASE_DIR / file_path + dest_path = Path("X:/") / os.path.basename(file_path) + shutil.copy2(src_path, dest_path) + print(f"Copied {src_path} to {dest_path}") + + # Detach the virtual disk + detach_script = tempfile.NamedTemporaryFile(delete=False, suffix='.txt') + with open(detach_script.name, 'w') as f: + f.write(f"select vdisk file=\"{abs_output_path}\"\n") + f.write("detach vdisk\n") + f.write("exit\n") + + subprocess.run(["diskpart", "/s", detach_script.name], check=True) + os.unlink(detach_script.name) + + finally: + os.unlink(diskpart_script.name) + + return output_path + +def calculate_checksum(file_path): + """Calculate MD5 checksum of a file""" + hash_md5 = hashlib.md5() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + +def convert_to_uf2(img_path, platform_type, output_path, base_addr): + """Convert raw image to UF2 format""" + # Check if required tools are available + if platform_type == "RP2040": + # Using RP2040 UF2 converter + uf2conv_script = os.environ.get("UF2CONV_PATH", "uf2conv.py") # You might need to set this env var + cmd = [ + "python", uf2conv_script, + "--base", hex(base_addr), + "--family", PLATFORM_PARAMS[platform_type]["uf2_family_id"], + "--output", output_path, + img_path + ] + else: # ESP32 + # Using TinyUF2 converter for ESP32 + tinyuf2_script = os.environ.get("TINYUF2_CONV_PATH", "tinyuf2/tools/uf2conv.py") # You might need to set this + cmd = [ + "python", tinyuf2_script, + "--base", hex(base_addr), + "--family", platform_type, + "--output", output_path, + img_path + ] + + print(f"Running command: {' '.join(cmd)}") + subprocess.run(cmd, check=True) + return output_path + +def merge_uf2_files(fw_uf2, fs_uf2, output_path): + """Merge firmware and filesystem UF2 files""" + # For simplicity we'll just concatenate the files + # In a production environment, you might want a proper UF2 merger + with open(output_path, 'wb') as outfile: + for infile in [fw_uf2, fs_uf2]: + with open(infile, 'rb') as f: + outfile.write(f.read()) + + return output_path + +def process_firmware_file(fw_path): + """Process a single firmware file, create merged version with filesystem""" + fw_name = os.path.basename(fw_path) + fw_checksum = calculate_checksum(fw_path) + output_name = f"merged_{fw_checksum}_{fw_name}" + output_path = OUTPUT_DIR / output_name + + # Determine platform type based on filename + if any(x in fw_name.lower() for x in ["esp32", "esp32s2", "esp32s3"]): + platform_type = "ESP32" + elif any(x in fw_name.lower() for x in ["rp2040", "rp2350", "rp2"]): + platform_type = "RP2040" + else: + print(f"Warning: Could not determine platform type for {fw_name}, assuming RP2040") + platform_type = "RP2040" + + print(f"Processing {fw_name} as {platform_type} firmware...") + + # Create temporary files + with tempfile.TemporaryDirectory() as temp_dir: + # Create FAT image + fat_img_path = os.path.join(temp_dir, "fat32.img") + create_fat_image(fat_img_path, OFFLINE_FILES) + + # Convert FAT image to UF2 + fs_uf2_path = os.path.join(temp_dir, "filesystem.uf2") + base_addr = PLATFORM_PARAMS[platform_type]["fat_base_addr"] + convert_to_uf2(fat_img_path, platform_type, fs_uf2_path, base_addr) + + # Merge firmware UF2 with filesystem UF2 + merge_uf2_files(fw_path, fs_uf2_path, output_path) + + print(f"Created merged firmware: {output_path}") + return output_path + +def fetch_ci_arduino_parameters(): + """ + Attempt to fetch board-specific parameters from the ci-arduino repository + This requires the repo to be locally cloned or accessible + """ + try: + ci_arduino_path = os.environ.get("CI_ARDUINO_PATH", "../ci-arduino") + platforms_file = Path(ci_arduino_path) / "all_platforms.py" + + if not platforms_file.exists(): + print(f"Warning: Could not find ci-arduino platforms file at {platforms_file}") + return {} + + # Simple parsing of the platforms file to extract partition information + # This is a basic approach - a more robust solution would use ast module + # to properly parse the Python file + board_params = {} + with open(platforms_file, 'r') as f: + content = f.read() + + # Look for board definitions with partition information + import re + board_defs = re.findall(r'(\w+)\s*=\s*{(.*?)}', content, re.DOTALL) + + for board_name, board_def in board_defs: + # Look for partition information + partition_match = re.search(r'["\'](build\.partitions)["\']:\s*["\'](.*?)["\']', board_def) + if partition_match: + partition_name = partition_match.group(2) + # Add to our board parameters + board_params[board_name.lower()] = { + "partition_name": partition_name + } + print(f"Found partition info for {board_name}: {partition_name}") + + return board_params + except Exception as e: + print(f"Error fetching ci-arduino parameters: {e}") + return {} + +def main(): + os.chdir(BASE_DIR) + print(f"Working directory: {os.getcwd()}") + + # Try to fetch board-specific parameters from ci-arduino repo + ci_params = fetch_ci_arduino_parameters() + if ci_params: + print(f"Found {len(ci_params)} board configurations from ci-arduino") + # Update our board parameters with the fetched information + for board_id, params in ci_params.items(): + if board_id in BOARD_SPECIFIC_PARAMS: + BOARD_SPECIFIC_PARAMS[board_id].update(params) + else: + # Determine platform type from board ID + platform = "ESP32" if "esp" in board_id else "RP2040" + BOARD_SPECIFIC_PARAMS[board_id] = { + "platform": platform, + "fat_base_addr": PLATFORM_PARAMS[platform]["fat_base_addr"], + **params + } + + # Find all UF2 firmware files + fw_files = list(FIRMWARE_DIR.glob("*.uf2")) + if not fw_files: + print(f"No UF2 firmware files found in {FIRMWARE_DIR}") + return + + print(f"Found {len(fw_files)} firmware files") + + # Process each firmware file + for fw_file in fw_files: + try: + process_firmware_file(fw_file) + except Exception as e: + print(f"Error processing {fw_file}: {e}") + + print("Processing complete!") + +if __name__ == "__main__": + main()