functions/private/Invoke-KlippyJsonRpcMethod.ps1

function Invoke-KlippyJsonRpcMethod {
    <#
    .SYNOPSIS
        Invokes a JSON-RPC method on Moonraker via /server/jsonrpc.

    .DESCRIPTION
        Sends a JSON-RPC 2.0 request to Moonraker and returns the result.
        Supports API key authentication and optional response normalization.

    .PARAMETER Printer
        The printer object (from Get-KlippyPrinter or Resolve-KlippyPrinterTarget).

    .PARAMETER Method
        The JSON-RPC method name (e.g., "machine.peripherals.usb").

    .PARAMETER Params
        Optional parameters hashtable for the method.

    .PARAMETER Timeout
        Request timeout in seconds. Default is 30.

    .PARAMETER NoNormalize
        Skip PascalCase normalization of response.

    .PARAMETER RawResponse
        Return the full JSON-RPC response.

    .OUTPUTS
        PSCustomObject - The result from the JSON-RPC call.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Printer,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Method,

        [Parameter()]
        [hashtable]$Params,

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

        [Parameter()]
        [switch]$NoNormalize,

        [Parameter()]
        [switch]$RawResponse
    )

    $requestId = [System.Random]::new().Next(1, 999999)
    $rpcRequest = @{
        jsonrpc = "2.0"
        method  = $Method
        id      = $requestId
    }

    if ($Params -and $Params.Count -gt 0) {
        $rpcRequest['params'] = $Params
    }

    $jsonBody = $rpcRequest | ConvertTo-Json -Depth 10 -Compress

    $headers = @{
        'Content-Type' = 'application/json'
    }

    if ($Printer.ApiKey) {
        $headers['X-Api-Key'] = $Printer.ApiKey
    }

    $baseUri = $Printer.Uri.TrimEnd('/')
    $uri = "$baseUri/server/jsonrpc"

    try {
        Write-Verbose "[$($Printer.PrinterName)] Invoking JSON-RPC $Method"
        Write-Verbose "URI: $uri"

        $invokeParams = @{
            Uri         = $uri
            Method      = 'POST'
            Headers     = $headers
            Body        = $jsonBody
            ContentType = 'application/json'
            TimeoutSec  = $Timeout
            ErrorAction = 'Stop'
        }

        $response = Invoke-RestMethod @invokeParams

        if ($RawResponse) {
            return $response
        }

        if ($response.error) {
            $errorMessage = $response.error.message
            if (-not $errorMessage) {
                $errorMessage = "JSON-RPC error invoking '$Method'"
            }
            throw "[$($Printer.PrinterName)] $errorMessage"
        }

        $result = if ($response.result) { $response.result } else { $response }

        if (-not $NoNormalize) {
            $result = ConvertTo-KlippyPascalCaseObject -InputObject $result
        }

        return $result
    }
    catch {
        throw $_
    }
}