using System.IO.Ports;
using System.Text;
namespace Server.Services
{
///
/// Service for managing serial communication with radios using SharpCAT library
///
public class SerialCommunicationService : ISerialCommunicationService, IDisposable
{
private readonly ILogger _logger;
private SharpCAT.Serial? _serialConnection;
private SerialPort? _directSerialPort;
private string? _currentPortName;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private bool _disposed = false;
public SerialCommunicationService(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
///
public string[] GetAvailablePorts()
{
try
{
var sharpCat = new SharpCAT.SharpCAT();
var ports = sharpCat.PortNames;
_logger.LogInformation("Found {PortCount} available serial ports", ports?.Length ?? 0);
return ports ?? Array.Empty();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting available serial ports");
return Array.Empty();
}
}
///
public async Task OpenPortAsync(string portName, int baudRate, Parity parity, StopBits stopBits, Handshake handshake)
{
if (string.IsNullOrWhiteSpace(portName))
throw new ArgumentException("Port name cannot be null or empty", nameof(portName));
await _semaphore.WaitAsync();
try
{
// Close existing connection if any
await ClosePortInternalAsync();
_logger.LogInformation("Attempting to open port {PortName} with baud rate {BaudRate}", portName, baudRate);
// Create direct SerialPort for more control over communication
_directSerialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
Parity = parity,
StopBits = stopBits,
Handshake = handshake,
DataBits = 8,
ReadTimeout = 5000,
WriteTimeout = 5000,
Encoding = Encoding.ASCII
};
_directSerialPort.Open();
_currentPortName = portName;
_logger.LogInformation("Successfully opened port {PortName}", portName);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to open port {PortName}", portName);
_directSerialPort?.Dispose();
_directSerialPort = null;
_currentPortName = null;
return false;
}
finally
{
_semaphore.Release();
}
}
///
public async Task ClosePortAsync()
{
await _semaphore.WaitAsync();
try
{
await ClosePortInternalAsync();
}
finally
{
_semaphore.Release();
}
}
///
public async Task SendCommandAsync(string command)
{
if (string.IsNullOrWhiteSpace(command))
throw new ArgumentException("Command cannot be null or empty", nameof(command));
await _semaphore.WaitAsync();
try
{
if (_directSerialPort == null || !_directSerialPort.IsOpen)
{
throw new InvalidOperationException("No serial port is currently open");
}
_logger.LogDebug("Sending command: {Command}", command);
// Send the command
await Task.Run(() => _directSerialPort.WriteLine(command));
// Wait a bit for response
await Task.Delay(100);
// Try to read response
string response = "";
if (_directSerialPort.BytesToRead > 0)
{
response = await Task.Run(() =>
{
try
{
return _directSerialPort.ReadExisting();
}
catch (TimeoutException)
{
return "";
}
});
}
_logger.LogDebug("Command response: {Response}", response);
return response.Trim();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending command: {Command}", command);
throw;
}
finally
{
_semaphore.Release();
}
}
///
public bool IsPortOpen()
{
return _directSerialPort?.IsOpen == true;
}
///
public string? GetCurrentPortName()
{
return _currentPortName;
}
private async Task ClosePortInternalAsync()
{
if (_directSerialPort != null)
{
try
{
if (_directSerialPort.IsOpen)
{
await Task.Run(() => _directSerialPort.Close());
}
_directSerialPort.Dispose();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error closing serial port");
}
finally
{
_directSerialPort = null;
_currentPortName = null;
}
}
if (_serialConnection != null)
{
_serialConnection = null;
}
_logger.LogInformation("Serial port closed");
}
public void Dispose()
{
if (!_disposed)
{
ClosePortInternalAsync().Wait();
_semaphore?.Dispose();
_disposed = true;
}
}
}
}