using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using SharpCAT.Client.Models;
namespace SharpCAT.Client
{
///
/// Client for accessing SharpCAT Server API endpoints
///
public class SharpCATClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly bool _ownsHttpClient;
///
/// Gets the base address of the SharpCAT server
///
public Uri BaseAddress => _httpClient.BaseAddress ?? throw new InvalidOperationException("Base address not set");
///
/// Initializes a new instance of the SharpCATClient with the specified base address
///
/// Base URL of the SharpCAT server (e.g., "http://localhost:5188")
public SharpCATClient(string baseAddress) : this(new Uri(baseAddress))
{
}
///
/// Initializes a new instance of the SharpCATClient with the specified base address
///
/// Base URI of the SharpCAT server
public SharpCATClient(Uri baseAddress)
{
_httpClient = new HttpClient { BaseAddress = baseAddress };
_ownsHttpClient = true;
}
///
/// Initializes a new instance of the SharpCATClient with an existing HttpClient
///
/// Configured HttpClient instance
public SharpCATClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_ownsHttpClient = false;
}
///
/// Gets all available serial ports on the system
///
/// Cancellation token
/// List of available serial ports
/// Thrown when the API call fails
public async Task GetPortsAsync(CancellationToken cancellationToken = default)
{
try
{
var response = await _httpClient.GetFromJsonAsync("api/cat/ports", cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
catch (HttpRequestException ex)
{
throw new SharpCATClientException("Failed to get ports", ex);
}
catch (TaskCanceledException ex)
{
throw new SharpCATClientException("Request timed out", ex);
}
}
///
/// Opens and configures a serial port for communication
///
/// Port configuration parameters
/// Cancellation token
/// Result of the port open operation
/// Thrown when request is null
/// Thrown when the API call fails
public async Task OpenPortAsync(OpenPortRequest request, CancellationToken cancellationToken = default)
{
if (request == null) throw new ArgumentNullException(nameof(request));
try
{
var httpResponse = await _httpClient.PostAsJsonAsync("api/cat/open", request, cancellationToken);
if (httpResponse.IsSuccessStatusCode)
{
var response = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
throw new SharpCATClientException($"API returned error: {errorResponse?.Error ?? "Unknown error"}", errorResponse?.Details);
}
}
catch (HttpRequestException ex)
{
throw new SharpCATClientException("Failed to open port", ex);
}
catch (TaskCanceledException ex)
{
throw new SharpCATClientException("Request timed out", ex);
}
}
///
/// Closes the currently opened serial port
///
/// Cancellation token
/// Result of the port close operation
/// Thrown when the API call fails
public async Task ClosePortAsync(CancellationToken cancellationToken = default)
{
try
{
var httpResponse = await _httpClient.PostAsync("api/cat/close", null, cancellationToken);
if (httpResponse.IsSuccessStatusCode)
{
var response = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
throw new SharpCATClientException($"API returned error: {errorResponse?.Error ?? "Unknown error"}", errorResponse?.Details);
}
}
catch (HttpRequestException ex)
{
throw new SharpCATClientException("Failed to close port", ex);
}
catch (TaskCanceledException ex)
{
throw new SharpCATClientException("Request timed out", ex);
}
}
///
/// Gets the current status of the serial port connection
///
/// Cancellation token
/// Current port status
/// Thrown when the API call fails
public async Task GetStatusAsync(CancellationToken cancellationToken = default)
{
try
{
var response = await _httpClient.GetFromJsonAsync("api/cat/status", cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
catch (HttpRequestException ex)
{
throw new SharpCATClientException("Failed to get status", ex);
}
catch (TaskCanceledException ex)
{
throw new SharpCATClientException("Request timed out", ex);
}
}
///
/// Sends a CAT command to the connected radio
///
/// CAT command to send
/// Cancellation token
/// Result of the command operation including any response from the radio
/// Thrown when request is null
/// Thrown when the API call fails
public async Task SendCommandAsync(SendCommandRequest request, CancellationToken cancellationToken = default)
{
if (request == null) throw new ArgumentNullException(nameof(request));
try
{
var httpResponse = await _httpClient.PostAsJsonAsync("api/cat/command", request, cancellationToken);
if (httpResponse.IsSuccessStatusCode)
{
var response = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
throw new SharpCATClientException($"API returned error: {errorResponse?.Error ?? "Unknown error"}", errorResponse?.Details);
}
}
catch (HttpRequestException ex)
{
throw new SharpCATClientException("Failed to send command", ex);
}
catch (TaskCanceledException ex)
{
throw new SharpCATClientException("Request timed out", ex);
}
}
///
/// Sends a CAT command to the connected radio
///
/// CAT command string to send
/// Cancellation token
/// Result of the command operation including any response from the radio
/// Thrown when command is null
/// Thrown when the API call fails
public async Task SendCommandAsync(string command, CancellationToken cancellationToken = default)
{
if (command == null) throw new ArgumentNullException(nameof(command));
var request = new SendCommandRequest { Command = command };
return await SendCommandAsync(request, cancellationToken);
}
///
/// Gets the current frequency from the radio (VFO A)
///
/// Cancellation token
/// Current frequency in Hz, or null if the command failed or returned no data
/// Thrown when the API call fails
public async Task GetFrequencyAsync(CancellationToken cancellationToken = default)
{
var response = await SendCommandAsync("FA;", cancellationToken);
if (!response.Success || string.IsNullOrEmpty(response.Response))
return null;
// Parse frequency response: FA00014074000; -> 14074000 Hz
var frequencyStr = response.Response;
if (!string.IsNullOrEmpty(frequencyStr) && frequencyStr.StartsWith("FA") && frequencyStr.EndsWith(";"))
{
var freqPart = frequencyStr.Substring(2, frequencyStr.Length - 3);
if (long.TryParse(freqPart, out var frequency))
{
return frequency;
}
}
return null;
}
///
/// Sets the frequency for the radio (VFO A)
///
/// Frequency in Hz
/// Cancellation token
/// True if the command was sent successfully
/// Thrown when frequency is out of valid range
/// Thrown when the API call fails
public async Task SetFrequencyAsync(long frequencyHz, CancellationToken cancellationToken = default)
{
if (frequencyHz < 0 || frequencyHz > 999999999999)
throw new ArgumentOutOfRangeException(nameof(frequencyHz), "Frequency must be between 0 and 999,999,999,999 Hz");
// Format frequency as 11-digit string with leading zeros
var command = $"FA{frequencyHz:D11};";
var response = await SendCommandAsync(command, cancellationToken);
return response.Success;
}
///
/// Gets the current frequency from the radio (VFO B)
///
/// Cancellation token
/// Current frequency in Hz, or null if the command failed or returned no data
/// Thrown when the API call fails
public async Task GetFrequencyBAsync(CancellationToken cancellationToken = default)
{
var response = await SendCommandAsync("FB;", cancellationToken);
if (!response.Success || string.IsNullOrEmpty(response.Response))
return null;
// Parse frequency response: FB00014074000; -> 14074000 Hz
var frequencyStr = response.Response;
if (!string.IsNullOrEmpty(frequencyStr) && frequencyStr.StartsWith("FB") && frequencyStr.EndsWith(";"))
{
var freqPart = frequencyStr.Substring(2, frequencyStr.Length - 3);
if (long.TryParse(freqPart, out var frequency))
{
return frequency;
}
}
return null;
}
///
/// Sets the frequency for the radio (VFO B)
///
/// Frequency in Hz
/// Cancellation token
/// True if the command was sent successfully
/// Thrown when frequency is out of valid range
/// Thrown when the API call fails
public async Task SetFrequencyBAsync(long frequencyHz, CancellationToken cancellationToken = default)
{
if (frequencyHz < 0 || frequencyHz > 999999999999)
throw new ArgumentOutOfRangeException(nameof(frequencyHz), "Frequency must be between 0 and 999,999,999,999 Hz");
// Format frequency as 11-digit string with leading zeros
var command = $"FB{frequencyHz:D11};";
var response = await SendCommandAsync(command, cancellationToken);
return response.Success;
}
///
/// Disposes the client and releases resources
///
public void Dispose()
{
if (_ownsHttpClient)
{
_httpClient?.Dispose();
}
}
}
}