functions/public/Wait-KlippyHeaterTemperature.ps1

function Wait-KlippyHeaterTemperature {
    <#
    .SYNOPSIS
        Waits for a heater to reach a target temperature.

    .DESCRIPTION
        Monitors heater temperature via WebSocket and waits until the temperature
        reaches the target within the specified tolerance.

    .PARAMETER Id
        The unique identifier of the printer.

    .PARAMETER PrinterName
        The friendly name of the printer.

    .PARAMETER InputObject
        A printer object from pipeline input.

    .PARAMETER Heater
        The heater to monitor. Common values: "extruder", "heater_bed", "extruder1".
        Default is "extruder".

    .PARAMETER Temperature
        The target temperature to wait for. If not specified, waits for the
        heater's current target temperature.

    .PARAMETER Tolerance
        Temperature tolerance in degrees. Default is 2.

    .PARAMETER Timeout
        Maximum time to wait in seconds. Default is 600 (10 minutes).

    .PARAMETER Quiet
        Suppress progress messages.

    .EXAMPLE
        Wait-KlippyHeaterTemperature -Heater "extruder" -Temperature 200
        Waits for extruder to reach 200C.

    .EXAMPLE
        Wait-KlippyHeaterTemperature -Heater "heater_bed"
        Waits for bed to reach its current target temperature.

    .EXAMPLE
        Invoke-KlippyGcode "M104 S200" | Wait-KlippyHeaterTemperature -Heater "extruder"
        Sets extruder temp and waits for it to heat up.

    .OUTPUTS
        PSCustomObject with temperature state information.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ById')]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByName', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$PrinterName,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByObject', ValueFromPipeline = $true)]
        [PSCustomObject]$InputObject,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Heater = 'extruder',

        [Parameter()]
        [ValidateRange(0, 500)]
        [double]$Temperature,

        [Parameter()]
        [ValidateRange(0.1, 20)]
        [double]$Tolerance = 2,

        [Parameter()]
        [ValidateRange(1, 3600)]
        [int]$Timeout = 600,

        [Parameter()]
        [switch]$Quiet
    )

    process {
        # Resolve printer
        $resolveParams = @{}
        switch ($PSCmdlet.ParameterSetName) {
            'ById' { $resolveParams['Id'] = $Id }
            'ByName' { $resolveParams['PrinterName'] = $PrinterName }
            'ByObject' { $resolveParams['InputObject'] = $InputObject }
        }

        $printer = Resolve-KlippyPrinterTarget @resolveParams

        # Get current heater state and target
        $targetTemp = $Temperature
        try {
            $heaterState = Get-KlippyObject -PrinterName $printer.PrinterName -ObjectName $Heater -Attributes "temperature", "target"

            if (-not $PSBoundParameters.ContainsKey('Temperature')) {
                $targetTemp = $heaterState.Target
                if ($targetTemp -eq 0) {
                    Write-Warning "[$($printer.PrinterName)] Heater '$Heater' has no target temperature set."
                    return [PSCustomObject]@{
                        PSTypeName   = 'KlippyCLI.WaitResult'
                        PrinterId    = $printer.Id
                        PrinterName  = $printer.PrinterName
                        Heater       = $Heater
                        Temperature  = $heaterState.Temperature
                        Target       = 0
                        WaitTime     = [TimeSpan]::Zero
                        Success      = $false
                        Error        = "No target temperature set"
                    }
                }
            }

            # Check if already at temperature
            $currentTemp = $heaterState.Temperature
            if ([Math]::Abs($currentTemp - $targetTemp) -le $Tolerance) {
                if (-not $Quiet) {
                    Write-Host "[$($printer.PrinterName)] $Heater already at temperature: $([Math]::Round($currentTemp, 1))C (target: $targetTemp`C)"
                }
                return [PSCustomObject]@{
                    PSTypeName   = 'KlippyCLI.WaitResult'
                    PrinterId    = $printer.Id
                    PrinterName  = $printer.PrinterName
                    Heater       = $Heater
                    Temperature  = $currentTemp
                    Target       = $targetTemp
                    WaitTime     = [TimeSpan]::Zero
                    Success      = $true
                }
            }
        }
        catch {
            Write-Error "[$($printer.PrinterName)] Failed to get heater state for '$Heater': $_"
            return
        }

        if (-not $Quiet) {
            Write-Host "[$($printer.PrinterName)] Waiting for $Heater to reach $targetTemp`C..." -NoNewline
        }

        $startTime = [datetime]::UtcNow
        $ws = $null
        $lastTemp = 0

        try {
            $ws = New-KlippyWebSocketClient -Printer $printer

            # Subscribe to the heater
            $subscribeObj = @{}
            $subscribeObj[$Heater] = @('temperature', 'target')
            $null = $ws.Subscribe($subscribeObj)

            $deadline = [datetime]::UtcNow.AddSeconds($Timeout)
            $lastProgressTime = [datetime]::MinValue

            while ([datetime]::UtcNow -lt $deadline) {
                $message = $ws.Receive(1000)

                if ($null -ne $message -and $message.method -eq 'notify_status_update' -and $message.params) {
                    $status = $message.params[0]
                    $heaterData = $status.$Heater

                    if ($heaterData.temperature) {
                        $lastTemp = $heaterData.temperature

                        # Check if at target
                        if ([Math]::Abs($lastTemp - $targetTemp) -le $Tolerance) {
                            if (-not $Quiet) {
                                Write-Host " Done! ($([Math]::Round($lastTemp, 1))C)"
                            }

                            return [PSCustomObject]@{
                                PSTypeName   = 'KlippyCLI.WaitResult'
                                PrinterId    = $printer.Id
                                PrinterName  = $printer.PrinterName
                                Heater       = $Heater
                                Temperature  = $lastTemp
                                Target       = $targetTemp
                                WaitTime     = [datetime]::UtcNow - $startTime
                                Success      = $true
                            }
                        }

                        # Progress update every 5 seconds
                        if (-not $Quiet -and ([datetime]::UtcNow - $lastProgressTime).TotalSeconds -ge 5) {
                            Write-Host " $([Math]::Round($lastTemp, 1))C" -NoNewline
                            $lastProgressTime = [datetime]::UtcNow
                        }
                    }
                }
            }

            # Timeout
            if (-not $Quiet) {
                Write-Host " Timeout!"
            }

            Write-Warning "[$($printer.PrinterName)] Timeout waiting for $Heater to reach $targetTemp`C. Current: $([Math]::Round($lastTemp, 1))C"

            return [PSCustomObject]@{
                PSTypeName   = 'KlippyCLI.WaitResult'
                PrinterId    = $printer.Id
                PrinterName  = $printer.PrinterName
                Heater       = $Heater
                Temperature  = $lastTemp
                Target       = $targetTemp
                WaitTime     = [datetime]::UtcNow - $startTime
                Success      = $false
            }
        }
        catch {
            if (-not $Quiet) {
                Write-Host " Error!"
            }
            Write-Error "[$($printer.PrinterName)] Error waiting for temperature: $_"

            return [PSCustomObject]@{
                PSTypeName   = 'KlippyCLI.WaitResult'
                PrinterId    = $printer.Id
                PrinterName  = $printer.PrinterName
                Heater       = $Heater
                Temperature  = $lastTemp
                Target       = $targetTemp
                WaitTime     = [datetime]::UtcNow - $startTime
                Success      = $false
                Error        = $_.Exception.Message
            }
        }
        finally {
            if ($ws) {
                $ws.Close()
            }
        }
    }
}