using System.Net.Http.Json; using System.Text.Json; using Extrudex.Domain.Interfaces; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Extrudex.Infrastructure.Configuration; /// /// HTTP client for communicating with Moonraker REST API endpoints /// on Klipper-based printers (e.g., Elegoo Centauri Carbon). /// Retrieves filament usage data and printer status information. /// public class MoonrakerClient : IMoonrakerClient { private readonly HttpClient _httpClient; private readonly ILogger _logger; /// /// Creates a new MoonrakerClient with the configured HTTP client and logger. /// /// The HTTP client for making requests to Moonraker endpoints. /// Logger for diagnostic output. public MoonrakerClient(HttpClient httpClient, ILogger logger) { _httpClient = httpClient; _logger = logger; } /// public async Task> GetFilamentUsageAsync( string hostnameOrIp, int port, string? apiKey, CancellationToken cancellationToken = default) { var baseUrl = BuildBaseUrl(hostnameOrIp, port); var result = new Dictionary(); try { // Query Moonraker server info endpoint for filament usage data using var request = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}/server/history/items?limit=1"); if (!string.IsNullOrEmpty(apiKey)) { request.Headers.Add("X-Api-Key", apiKey); } using var response = await _httpClient.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); // Extract filament usage from the response // Moonraker returns job history with filament_used and similar fields if (json.TryGetProperty("result", out var resultElement)) { if (resultElement.TryGetProperty("items", out var items) && items.GetArrayLength() > 0) { var job = items[0]; // Moonraker tracks filament_used in millimeters if (job.TryGetProperty("filament_used", out var filamentUsed)) { result["mm_extruded"] = filamentUsed.GetDecimal(); } // Total duration in seconds if (job.TryGetProperty("print_duration", out var duration)) { result["print_duration_seconds"] = duration.GetDecimal(); } } } _logger.LogDebug( "Retrieved filament usage from Moonraker at {Host}:{Port}: {MetricCount} metrics", hostnameOrIp, port, result.Count); } catch (HttpRequestException ex) { _logger.LogWarning(ex, "Failed to retrieve filament usage from Moonraker at {Host}:{Port}", hostnameOrIp, port); } catch (JsonException ex) { _logger.LogWarning(ex, "Failed to parse Moonraker response from {Host}:{Port}", hostnameOrIp, port); } return result; } /// public async Task IsReachableAsync( string hostnameOrIp, int port, string? apiKey, CancellationToken cancellationToken = default) { var baseUrl = BuildBaseUrl(hostnameOrIp, port); try { using var request = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}/server/info"); if (!string.IsNullOrEmpty(apiKey)) { request.Headers.Add("X-Api-Key", apiKey); } using var response = await _httpClient.SendAsync(request, cancellationToken); return response.IsSuccessStatusCode; } catch (HttpRequestException) { _logger.LogDebug("Moonraker at {Host}:{Port} is not reachable", hostnameOrIp, port); return false; } } /// /// Builds the base URL for Moonraker API calls from hostname and port. /// private static string BuildBaseUrl(string hostnameOrIp, int port) { return $"http://{hostnameOrIp}:{port}"; } }