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}";
}
}