Public/Connect-PsGadgetFtdi.ps1

# Connect-PsGadgetFtdi.ps1
# Connect to an FTDI device

function Connect-PsGadgetFtdi {
    <#
    .SYNOPSIS
    Connects to an FTDI device for GPIO and communication control.
     
    .DESCRIPTION
    Opens a direct connection to an FTDI device using the appropriate platform backend.
    Returns a connection object that can be used for MPSSE GPIO control, serial
    communication, and other FTDI operations.
     
    .PARAMETER Index
    The index of the FTDI device to connect to. Use Get-FTDevice to see available devices.
     
    .PARAMETER SerialNumber
    Alternative to Index - connect to device by its serial number
     
    .PARAMETER LocationId
    Alternative to Index/SerialNumber - connect by USB LocationId (hub+port address).
    LocationId is stable for a fixed physical USB port. Use Get-FTDevice to find the value.
 
    $Connection.Close()
     
    .EXAMPLE
    $Connection = Connect-PsGadgetFtdi -SerialNumber "ABC123"
    # Use connection for GPIO or serial operations
    $Connection.Close()
     
    .EXAMPLE
    $Connection = Connect-PsGadgetFtdi -LocationId 197634
    # Stable USB port addressing - same port always opens the same physical device
    $Connection.Close()
 
    .OUTPUTS
    System.Object
    A connection object with platform-specific device handle and control methods.
    #>

    
    [CmdletBinding(DefaultParameterSetName = 'ByIndex')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ByIndex', Position = 0)]
        [int]$Index,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'BySerial')]
        [string]$SerialNumber,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByLocation')]
        [string]$LocationId
    )
    
    try {
        # Get available devices for validation.
        # D2XX GetNumberOfDevices returns 0 on rapid back-to-back calls (known driver quirk).
        # Retry up to 3 times with a short sleep to work around this.
        $devices = @()
        for ($attempt = 1; $attempt -le 3; $attempt++) {
            $devices = @(Get-FtdiDeviceList)
            if ($devices.Count -gt 0) { break }
            if ($attempt -lt 3) {
                Write-Verbose "Get-FtdiDeviceList returned empty on attempt $attempt; retrying after 150ms..."
                Start-Sleep -Milliseconds 150
            }
        }
        if ($devices.Count -eq 0) {
            throw "No FTDI devices found. Run Get-FTDevice to check available devices."
        }
        
        # Determine target device
        $targetDevice = $null
        $deviceIndex = -1
        
        if ($PSCmdlet.ParameterSetName -eq 'ByIndex') {
            if ($Index -lt 0 -or $Index -ge $devices.Count) {
                throw "Device index $Index is out of range. Available devices: 0-$($devices.Count - 1)"
            }
            $deviceIndex = $Index
            $targetDevice = $devices[$Index]
        } elseif ($PSCmdlet.ParameterSetName -eq 'BySerial') {
            $targetDevice = $devices | Where-Object { $_.SerialNumber -eq $SerialNumber }
            if (-not $targetDevice) {
                throw "No device found with serial number '$SerialNumber'"
            }
            $deviceIndex = $targetDevice.Index
        } else {
            # ByLocation - match on LocationId (shown by Get-FTDevice)
            $targetDevice = $devices | Where-Object { "$($_.LocationId)" -eq $LocationId } | Select-Object -First 1
            if (-not $targetDevice) {
                throw "No device found with LocationId '$LocationId'. Run Get-FTDevice to see available LocationIds."
            }
            $deviceIndex = $targetDevice.Index
        }
        
        Write-Verbose "Connecting to: $($targetDevice.Description) ($($targetDevice.SerialNumber))"
        
        # Check if device is already in use
        if ($targetDevice.IsOpen) {
            Write-Warning "Device appears to be in use by another application"
        }
        
        # Call platform/backend-specific opening function.
        # Priority order on Windows:
        # 1. FTD2XX_NET - always used for GPIO / MPSSE (ACBUS raw commands)
        # 2. IoT backend - PS 7.4+ / .NET 8+
        # FtdiSharp is NOT used here. It is opened separately by Connect-PsGadgetSsd1306
        # for I2C only, on demand, using the device serial number directly.
        $connection = $null

        if (-not $connection -and $script:IotBackendAvailable) {
            Write-Verbose "Using IoT .NET backend for connection"
            try {
                $connection = Invoke-FtdiIotOpen -DeviceInfo $targetDevice
            } catch {
                $errMsg   = $_.Exception.Message
                $exType   = $_.Exception.GetType().Name
                $isNotImpl = $_.Exception -is [System.NotImplementedException]
                # Warn about a missing native library only if:
                # - NOT a NotImplementedException (which has a different actionable message)
                # - The error mentions "ftd2xx" (DllNotFoundException or P/Invoke failure)
                # - We are on Linux/macOS where libftd2xx.so is required
                if (-not $isNotImpl -and
                    $errMsg -match 'ftd2xx|Unable to load shared library' -and
                    [System.Environment]::OSVersion.Platform -ne 'Win32NT') {
                    Write-Warning (
                        "FTDI D2XX native library not found. " +
                        "Install it from https://ftdichip.com/drivers/d2xx-drivers/ " +
                        "then run: sudo cp libftd2xx.so /usr/local/lib && sudo ldconfig`n" +
                        "If the device appears as /dev/ttyUSBx, also run: sudo rmmod ftdi_sio`n" +
                        "Falling back to stub mode."
                    )
                } else {
                    Write-Verbose "IoT open failed ($exType): $errMsg -- falling back to platform backend"
                }
                $connection = $null
            }
        }
        if (-not $connection) {
            if ($PSVersionTable.PSVersion.Major -le 5 -or [System.Environment]::OSVersion.Platform -eq 'Win32NT') {
                Write-Verbose "Using Windows FTDI backend for connection"
                $connection = Invoke-FtdiWindowsOpen -DeviceInfo $targetDevice
            } else {
                Write-Verbose "Using Unix FTDI backend for connection"
                $connection = Invoke-FtdiUnixOpen -Index $deviceIndex
            }
        }
        
        if (-not $connection) {
            throw "Failed to establish connection to FTDI device"
        }
        
        Write-Verbose "Successfully connected to FTDI device $deviceIndex"
        return $connection
        
    } catch {
        Write-Error "Failed to connect to FTDI device: $_"
        throw
    }
}