Private/Ftdi.IoT.ps1
|
# Ftdi.IoT.ps1 # .NET IoT library backend for FTDI devices (PS 7.4+ / .NET 8+) # Uses System.Device.Gpio and Iot.Device.Bindings for platform-agnostic hardware access. # # When $script:IotBackendAvailable is $true (set by Initialize-FtdiAssembly), # this backend is used automatically in place of Ftdi.Windows.ps1 / Ftdi.Unix.ps1. # Users never need to know or care which backend is running -- the public API is identical. # # MPSSE devices (FT232H, FT2232H, FT4232H, FT232HP): fully handled by IoT Ft232HDevice. # CBUS devices (FT232R): enumerated by IoT, but opened via FTD2XX_NET fallback on Windows. # Pin mapping (FT232H ACBUS): # PsGadget user pin 0-7 -> ACBUS0-7 -> IoT GpioController pin 8-15 (C0-C7) # ADBUS0-7 (SPI/I2C/JTAG lines) are IoT pins 0-7 and are not used for GPIO here. function Invoke-FtdiIotEnumerate { [CmdletBinding()] [OutputType([System.Object[]])] param() try { Write-Verbose "Enumerating FTDI devices via IoT FtCommon.GetDevices()..." $rawDevices = [Iot.Device.FtCommon.FtCommon]::GetDevices() if (-not $rawDevices -or $rawDevices.Count -eq 0) { Write-Verbose "No FTDI devices found via IoT backend" return @() } Write-Verbose "IoT FtCommon found $($rawDevices.Count) device(s)" $isWin32 = [System.Environment]::OSVersion.Platform -eq 'Win32NT' $driverLabel = if ($isWin32) { 'ftd2xx.dll (IoT)' } else { 'libftdi (IoT)' } $platformLabel = if ($isWin32) { 'Windows' } else { 'Unix' } # Map FtDeviceType integer value to PsGadget friendly chip name. # Integer comparison avoids PowerShell enum comparison quirks. $enrichedDevices = @() for ($i = 0; $i -lt $rawDevices.Count; $i++) { $d = $rawDevices[$i] $typeName = switch ([int]$d.Type) { 0 { 'FT232BM' } # Ft232BOrFt245B 1 { 'FT232AM' } # Ft8U232AmOrFTtU245Am 2 { 'FT100AX' } # Ft8U100Ax 4 { 'FT2232C' } # Ft2232 5 { 'FT232R' } # Ft232ROrFt245R 6 { 'FT2232H' } # Ft2232H 7 { 'FT4232H' } # Ft4232H 8 { 'FT232H' } # Ft232H 9 { 'FT-X Series' } # FtXSeries 17 { 'FT2233HP' } # Ft2233HP 18 { 'FT4233HP' } # Ft4233HP 19 { 'FT2232HP' } # Ft2232HP 20 { 'FT4232HP' } # Ft4232HP 21 { 'FT233HP' } # Ft233HP 22 { 'FT232HP' } # Ft232HP 23 { 'FT2232HA' } # Ft2232HA 24 { 'FT4232HA' } # Ft4232HA default { $d.Type.ToString() } } # PortOpened flag value = 1 $isOpen = ([int]$d.Flags -band 1) -ne 0 $caps = Get-FtdiChipCapabilities -TypeName $typeName $enriched = [PSCustomObject]@{ Index = $i Type = $typeName Description = $d.Description SerialNumber = $d.SerialNumber LocationId = $d.LocId IsOpen = $isOpen Flags = '0x{0:X8}' -f [int]$d.Flags DeviceId = '0x{0:X8}' -f [uint32]$d.Id Handle = $null Driver = $driverLabel Platform = $platformLabel IsVcp = $false # IoT FtCommon only surfaces D2XX-accessible devices GpioMethod = $caps.GpioMethod GpioPins = $caps.GpioPins HasMpsse = $caps.HasMpsse CapabilityNote = $caps.CapabilityNote RawFtDevice = $d # preserved for Invoke-FtdiIotOpen } $enrichedDevices += $enriched } return $enrichedDevices } catch { Write-Verbose "IoT FTDI enumeration failed: $($_.Exception.Message)" throw } } function Invoke-FtdiIotOpen { # Open an FTDI device using the .NET IoT Ft232HDevice class. # MPSSE devices (FT232H family): opened via Ft232HDevice with GpioController. # CBUS devices (FT232R): falls back to FTD2XX_NET on Windows (no IoT support for CBUS). [CmdletBinding()] [OutputType([System.Object])] param( [Parameter(Mandatory = $true)] [PSCustomObject]$DeviceInfo ) try { # CBUS devices (FT232R, FT-X) are not supported by the IoT Ft232HDevice class. # On Windows with FTD2XX_NET loaded: fall back to the D2XX path. # On Unix with native P/Invoke loaded: delegate to Invoke-FtdiUnixOpen. # Otherwise: throw so Connect-PsGadgetFtdi can create an appropriate stub. if (-not $DeviceInfo.HasMpsse) { $isWin32 = [System.Environment]::OSVersion.Platform -eq 'Win32NT' if ($isWin32 -and $script:D2xxLoaded) { Write-Verbose "$($DeviceInfo.Type) is a CBUS device; using FTD2XX_NET backend to open" return Invoke-FtdiWindowsOpen -DeviceInfo $DeviceInfo } if (-not $isWin32 -and $script:FtdiNativeAvailable) { Write-Verbose "$($DeviceInfo.Type) is a CBUS device; using native P/Invoke backend on Unix" return Invoke-FtdiUnixOpen -Index $DeviceInfo.Index } throw [System.NotImplementedException]::new( "$($DeviceInfo.Type) uses CBUS GPIO, which requires the FTD2XX_NET backend. " + "On Windows, install the FTDI CDM driver package (includes ftd2xx.dll). " + "On Linux/macOS with libftd2xx.so installed and loaded, this should work via " + "native P/Invoke -- ensure Initialize-FtdiNative ran successfully.") } # Obtain the raw IoT FtDevice object. # Normally stamped by Invoke-FtdiIotEnumerate; if missing (caller built their own device # info object) re-enumerate to get a fresh FtDevice. $rawFtDevice = $null if ($DeviceInfo.PSObject.Properties['RawFtDevice'] -and $DeviceInfo.RawFtDevice) { $rawFtDevice = $DeviceInfo.RawFtDevice } else { Write-Verbose "RawFtDevice missing; re-enumerating to locate $($DeviceInfo.SerialNumber)..." $allDevices = [Iot.Device.FtCommon.FtCommon]::GetDevices() $rawFtDevice = $allDevices | Where-Object { $_.SerialNumber -eq $DeviceInfo.SerialNumber } | Select-Object -First 1 } if (-not $rawFtDevice) { throw "Could not locate an IoT FtDevice for serial number '$($DeviceInfo.SerialNumber)'. " + "Try re-running Get-FTDevice and connecting again." } Write-Verbose "Opening $($DeviceInfo.Type) via IoT Ft232HDevice: $($DeviceInfo.Description)" $ft232h = [Iot.Device.Ft232H.Ft232HDevice]::new($rawFtDevice) $gpioCtrl = $ft232h.CreateGpioController() $isWin32 = [System.Environment]::OSVersion.Platform -eq 'Win32NT' $connection = [PSCustomObject]@{ Device = $ft232h GpioController = $gpioCtrl Index = $DeviceInfo.Index SerialNumber = $DeviceInfo.SerialNumber Description = $DeviceInfo.Description Type = $DeviceInfo.Type LocationId = $DeviceInfo.LocationId IsOpen = $true GpioMethod = 'IoT' GpioPins = $DeviceInfo.GpioPins HasMpsse = $DeviceInfo.HasMpsse MpsseEnabled = $true Platform = if ($isWin32) { 'Windows (IoT)' } else { 'Unix (IoT)' } Backend = 'IoT' } # Close - disposes GpioController then Ft232HDevice $connection | Add-Member -MemberType ScriptMethod -Name 'Close' -Value { if ($this.GpioController) { try { $this.GpioController.Dispose() } catch {} $this.GpioController = $null } if ($this.Device) { try { $this.Device.Dispose() } catch {} $this.Device = $null } $this.IsOpen = $false } # Reset - soft-reset the FTDI chip (clears buffers, keeps connection open) $connection | Add-Member -MemberType ScriptMethod -Name 'Reset' -Value { if (-not $this.IsOpen -or -not $this.Device) { throw 'Device is not open' } $this.Device.Reset() } # CreateI2cBus - returns an IoT I2cBus for use with Iot.Device.Bindings sensor classes $connection | Add-Member -MemberType ScriptMethod -Name 'CreateI2cBus' -Value { if (-not $this.IsOpen -or -not $this.Device) { throw 'Device is not open' } return $this.Device.CreateOrGetI2cBus() } # CreateSpiDevice - returns an IoT SpiDevice for use with Iot.Device.Bindings bindings $connection | Add-Member -MemberType ScriptMethod -Name 'CreateSpiDevice' -Value { param([object]$SpiSettings) if (-not $this.IsOpen -or -not $this.Device) { throw 'Device is not open' } return $this.Device.CreateSpiDevice($SpiSettings) } Write-Verbose "Successfully opened $($DeviceInfo.Type) via IoT backend" return $connection } catch [System.NotImplementedException] { throw # propagate cleanly for stub-mode detection upstream } catch { throw "Failed to open IoT FTDI device: $_" } } function Set-FtdiIotGpioPins { # Set ACBUS GPIO pins on an MPSSE device using the IoT GpioController. # # Pin mapping: # PsGadget ACBUS pin 0 -> IoT GpioController pin 8 (ACBUS0 / C0) # PsGadget ACBUS pin 7 -> IoT GpioController pin 15 (ACBUS7 / C7) # ADBUS0-7 (IoT pins 0-7) are used for SPI/I2C/JTAG, not for general GPIO here. [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [object]$GpioController, [Parameter(Mandatory = $true)] [int[]]$Pins, [Parameter(Mandatory = $true)] [string]$State, [Parameter(Mandatory = $false)] [int]$DurationMs ) try { # Translate ACBUS pin numbers (0-7) to IoT controller pin numbers (8-15) $iotPins = $Pins | ForEach-Object { $_ + 8 } $pinValue = if ($State -in @('HIGH', 'H', '1')) { [System.Device.Gpio.PinValue]::High } else { [System.Device.Gpio.PinValue]::Low } foreach ($pin in $iotPins) { if (-not $GpioController.IsPinOpen($pin)) { $GpioController.OpenPin($pin, [System.Device.Gpio.PinMode]::Output) } $GpioController.Write($pin, $pinValue) } if ($DurationMs) { Start-Sleep -Milliseconds $DurationMs $restoreValue = if ($pinValue -eq [System.Device.Gpio.PinValue]::High) { [System.Device.Gpio.PinValue]::Low } else { [System.Device.Gpio.PinValue]::High } foreach ($pin in $iotPins) { $GpioController.Write($pin, $restoreValue) } } return $true } catch { Write-Warning "IoT GPIO operation failed: $($_.Exception.Message)" return $false } } |