diff --git a/README.md b/README.md index 6de175e..f4e17f9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,31 @@ # This like many, are ideas. There may or may not be progress as inspiration and time allows. # SharpCAT -C#, .NET Standard based CAT control library. +C#, .NET Standard based CAT control library with ASP.NET Core Web API server. I am targeting .Net Standard so that the assembly may be used with .Net Core or the .Net framework. +## Components + +### SharpCAT Library +The core .NET Standard 2.0 library for CAT (Computer Aided Transceiver) control. + +### SharpCAT Server +A cross-platform ASP.NET Core Web API server that provides REST endpoints for CAT control operations. Located in `Server/SharpCAT.Server/`. + +**Key Features:** +- REST API for serial port management and CAT commands +- Cross-platform support (Windows, Linux, macOS) +- Swagger/OpenAPI documentation +- Built-in error handling and logging + +**Quick Start:** +```bash +cd Server/SharpCAT.Server +dotnet run +``` +Then visit `http://localhost:5188` for the Swagger UI. + ## Development Setup ### Prerequisites @@ -16,19 +37,30 @@ I am targeting .Net Standard so that the assembly may be used with .Net Core or This project is configured for development in Visual Studio Code with the .NET CLI. -**To build the project:** +**To build the entire solution:** ```bash -dotnet build SharpCAT/SharpCATLib.csproj +dotnet build +``` + +**To build just the library:** +```bash +dotnet build SharpCAT/SharpCAT.csproj +``` + +**To build and run the Web API server:** +```bash +cd Server/SharpCAT.Server +dotnet run ``` **To clean the project:** ```bash -dotnet clean SharpCAT/SharpCATLib.csproj +dotnet clean ``` **To restore packages:** ```bash -dotnet restore SharpCAT/SharpCATLib.csproj +dotnet restore ``` **VS Code Tasks:** diff --git a/Server/SharpCAT.Server/Controllers/CatController.cs b/Server/SharpCAT.Server/Controllers/CatController.cs new file mode 100644 index 0000000..7907353 --- /dev/null +++ b/Server/SharpCAT.Server/Controllers/CatController.cs @@ -0,0 +1,292 @@ +using Microsoft.AspNetCore.Mvc; +using SharpCAT.Server.Models; +using SharpCAT.Server.Services; + +namespace SharpCAT.Server.Controllers +{ + /// + /// Controller for CAT (Computer Aided Transceiver) operations + /// + [ApiController] + [Route("api/[controller]")] + [Produces("application/json")] + public class CatController : ControllerBase + { + private readonly ISerialCommunicationService _serialService; + private readonly ILogger _logger; + + public CatController(ISerialCommunicationService serialService, ILogger logger) + { + _serialService = serialService ?? throw new ArgumentNullException(nameof(serialService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Gets all available serial ports on the system + /// + /// List of available serial ports + /// Returns the list of available serial ports + [HttpGet("ports")] + [ProducesResponseType(typeof(PortListResponse), StatusCodes.Status200OK)] + public ActionResult GetPorts() + { + try + { + _logger.LogInformation("Getting available serial ports"); + var ports = _serialService.GetAvailablePorts(); + + var response = new PortListResponse + { + Ports = ports + }; + + _logger.LogInformation("Found {PortCount} available ports", response.Count); + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting available ports"); + return StatusCode(500, new ErrorResponse + { + Error = "Internal server error while getting ports", + Details = ex.Message + }); + } + } + + /// + /// Opens and configures a serial port for communication + /// + /// Port configuration parameters + /// Result of the port open operation + /// Port opened successfully + /// Invalid request parameters + /// Internal server error + [HttpPost("open")] + [ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> OpenPort([FromBody] OpenPortRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(new ErrorResponse + { + Error = "Invalid request parameters", + Details = string.Join("; ", ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)) + }); + } + + _logger.LogInformation("Attempting to open port {PortName} with baud rate {BaudRate}", + request.PortName, request.BaudRate); + + var success = await _serialService.OpenPortAsync( + request.PortName, + request.BaudRate, + request.Parity, + request.StopBits, + request.Handshake); + + var response = new PortOperationResponse + { + Success = success, + PortName = request.PortName, + IsOpen = _serialService.IsPortOpen(), + Message = success + ? $"Port {request.PortName} opened successfully" + : $"Failed to open port {request.PortName}" + }; + + if (success) + { + _logger.LogInformation("Successfully opened port {PortName}", request.PortName); + return Ok(response); + } + else + { + _logger.LogWarning("Failed to open port {PortName}", request.PortName); + return StatusCode(500, new ErrorResponse + { + Error = response.Message + }); + } + } + catch (ArgumentException ex) + { + _logger.LogWarning(ex, "Invalid argument for opening port: {PortName}", request.PortName); + return BadRequest(new ErrorResponse + { + Error = "Invalid port parameters", + Details = ex.Message + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error opening port: {PortName}", request.PortName); + return StatusCode(500, new ErrorResponse + { + Error = "Internal server error while opening port", + Details = ex.Message + }); + } + } + + /// + /// Closes the currently opened serial port + /// + /// Result of the port close operation + /// Port closed successfully + /// Internal server error + [HttpPost("close")] + [ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> ClosePort() + { + try + { + var currentPortName = _serialService.GetCurrentPortName(); + _logger.LogInformation("Attempting to close port {PortName}", currentPortName ?? "unknown"); + + await _serialService.ClosePortAsync(); + + var response = new PortOperationResponse + { + Success = true, + PortName = currentPortName, + IsOpen = _serialService.IsPortOpen(), + Message = "Port closed successfully" + }; + + _logger.LogInformation("Successfully closed port"); + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing port"); + return StatusCode(500, new ErrorResponse + { + Error = "Internal server error while closing port", + Details = ex.Message + }); + } + } + + /// + /// Sends a CAT command to the connected radio + /// + /// CAT command to send + /// Result of the command operation including any response from the radio + /// Command sent successfully + /// Invalid request parameters or no port open + /// Internal server error + [HttpPost("command")] + [ProducesResponseType(typeof(CommandResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)] + public async Task> SendCommand([FromBody] SendCommandRequest request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(new ErrorResponse + { + Error = "Invalid request parameters", + Details = string.Join("; ", ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)) + }); + } + + if (!_serialService.IsPortOpen()) + { + return BadRequest(new ErrorResponse + { + Error = "No serial port is currently open", + Details = "You must open a serial port before sending commands" + }); + } + + _logger.LogInformation("Sending CAT command: {Command}", request.Command); + + var radioResponse = await _serialService.SendCommandAsync(request.Command); + + var response = new CommandResponse + { + Success = true, + Command = request.Command, + Response = radioResponse, + Message = "Command sent successfully", + Timestamp = DateTime.UtcNow + }; + + _logger.LogInformation("CAT command sent successfully. Response: {Response}", radioResponse); + return Ok(response); + } + catch (ArgumentException ex) + { + _logger.LogWarning(ex, "Invalid command: {Command}", request.Command); + return BadRequest(new ErrorResponse + { + Error = "Invalid command", + Details = ex.Message + }); + } + catch (InvalidOperationException ex) + { + _logger.LogWarning(ex, "Cannot send command - port not open"); + return BadRequest(new ErrorResponse + { + Error = "Cannot send command", + Details = ex.Message + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending CAT command: {Command}", request.Command); + return StatusCode(500, new ErrorResponse + { + Error = "Internal server error while sending command", + Details = ex.Message + }); + } + } + + /// + /// Gets the current status of the serial port connection + /// + /// Current port status + /// Returns current port status + [HttpGet("status")] + [ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)] + public ActionResult GetStatus() + { + try + { + var isOpen = _serialService.IsPortOpen(); + var currentPort = _serialService.GetCurrentPortName(); + + var response = new PortOperationResponse + { + Success = true, + PortName = currentPort, + IsOpen = isOpen, + Message = isOpen + ? $"Port {currentPort} is open" + : "No port is currently open" + }; + + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting port status"); + return StatusCode(500, new ErrorResponse + { + Error = "Internal server error while getting status", + Details = ex.Message + }); + } + } + } +} \ No newline at end of file diff --git a/Server/SharpCAT.Server/Models/ApiModels.cs b/Server/SharpCAT.Server/Models/ApiModels.cs new file mode 100644 index 0000000..06b7109 --- /dev/null +++ b/Server/SharpCAT.Server/Models/ApiModels.cs @@ -0,0 +1,143 @@ +using System.ComponentModel.DataAnnotations; +using System.IO.Ports; + +namespace SharpCAT.Server.Models +{ + /// + /// Response model for listing available serial ports + /// + public class PortListResponse + { + /// + /// Array of available serial port names + /// + public string[] Ports { get; set; } = Array.Empty(); + + /// + /// Number of available ports + /// + public int Count => Ports.Length; + } + + /// + /// Request model for opening a serial port + /// + public class OpenPortRequest + { + /// + /// Name of the serial port to open (e.g., "COM1", "/dev/ttyUSB0") + /// + [Required] + public string PortName { get; set; } = string.Empty; + + /// + /// Baud rate for communication (default: 9600) + /// + public int BaudRate { get; set; } = 9600; + + /// + /// Parity setting (default: None) + /// + public Parity Parity { get; set; } = Parity.None; + + /// + /// Stop bits setting (default: One) + /// + public StopBits StopBits { get; set; } = StopBits.One; + + /// + /// Handshake setting (default: None) + /// + public Handshake Handshake { get; set; } = Handshake.None; + } + + /// + /// Response model for port operations + /// + public class PortOperationResponse + { + /// + /// Indicates if the operation was successful + /// + public bool Success { get; set; } + + /// + /// Human-readable message about the operation + /// + public string Message { get; set; } = string.Empty; + + /// + /// Name of the port that was operated on + /// + public string? PortName { get; set; } + + /// + /// Current status of the port (open/closed) + /// + public bool IsOpen { get; set; } + } + + /// + /// Request model for sending CAT commands + /// + public class SendCommandRequest + { + /// + /// CAT command string to send to the radio + /// + [Required] + public string Command { get; set; } = string.Empty; + } + + /// + /// Response model for CAT command operations + /// + public class CommandResponse + { + /// + /// Indicates if the command was sent successfully + /// + public bool Success { get; set; } + + /// + /// The command that was sent + /// + public string Command { get; set; } = string.Empty; + + /// + /// Response received from the radio (if any) + /// + public string? Response { get; set; } + + /// + /// Human-readable message about the operation + /// + public string Message { get; set; } = string.Empty; + + /// + /// Timestamp when the command was executed + /// + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + } + + /// + /// Response model for errors + /// + public class ErrorResponse + { + /// + /// Error message + /// + public string Error { get; set; } = string.Empty; + + /// + /// Additional details about the error + /// + public string? Details { get; set; } + + /// + /// Timestamp when the error occurred + /// + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/Server/SharpCAT.Server/Program.cs b/Server/SharpCAT.Server/Program.cs new file mode 100644 index 0000000..25070fc --- /dev/null +++ b/Server/SharpCAT.Server/Program.cs @@ -0,0 +1,67 @@ +using SharpCAT.Server.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo + { + Title = "SharpCAT Server API", + Version = "v1", + Description = "ASP.NET Core Web API for CAT (Computer Aided Transceiver) control using SharpCAT library", + Contact = new Microsoft.OpenApi.Models.OpenApiContact + { + Name = "SharpCAT Project" + } + }); + + // Include XML comments if available + var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename); + if (File.Exists(xmlPath)) + { + c.IncludeXmlComments(xmlPath); + } +}); + +// Register the serial communication service as a singleton +// to maintain connection state across requests +builder.Services.AddSingleton(); + +// Add CORS policy for cross-origin requests +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "SharpCAT Server API v1"); + c.RoutePrefix = string.Empty; // Set Swagger UI at app's root + }); +} + +app.UseCors("AllowAll"); +app.UseRouting(); +app.MapControllers(); + +// Add a simple health check endpoint +app.MapGet("/health", () => new { status = "healthy", timestamp = DateTime.UtcNow }) + .WithName("HealthCheck") + .WithOpenApi(); + +app.Run(); diff --git a/Server/SharpCAT.Server/Properties/launchSettings.json b/Server/SharpCAT.Server/Properties/launchSettings.json new file mode 100644 index 0000000..cc2744d --- /dev/null +++ b/Server/SharpCAT.Server/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14425", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5188", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Server/SharpCAT.Server/README.md b/Server/SharpCAT.Server/README.md new file mode 100644 index 0000000..4d9218a --- /dev/null +++ b/Server/SharpCAT.Server/README.md @@ -0,0 +1,150 @@ +# SharpCAT Server + +A cross-platform ASP.NET Core Web API server that provides REST endpoints for CAT (Computer Aided Transceiver) control using the SharpCAT library. + +## Features + +- **Cross-platform**: Works on Windows, Linux, and macOS +- **REST API**: Clean, documented REST endpoints for radio control +- **Serial Communication**: Leverages the SharpCAT core library for serial port operations +- **Swagger Documentation**: Built-in API documentation and testing interface +- **Error Handling**: Comprehensive error handling and logging + +## API Endpoints + +### Serial Port Management + +- `GET /api/cat/ports` - List all available serial ports +- `POST /api/cat/open` - Open and configure a serial port +- `POST /api/cat/close` - Close the currently opened port +- `GET /api/cat/status` - Get current port connection status + +### CAT Commands + +- `POST /api/cat/command` - Send arbitrary CAT commands to the radio + +### Health Check + +- `GET /health` - Simple health check endpoint + +## Quick Start + +### Prerequisites + +- [.NET 8.0 SDK](https://dotnet.microsoft.com/download) or later + +### Running the Server + +1. Navigate to the server directory: + ```bash + cd Server/SharpCAT.Server + ``` + +2. Build and run the server: + ```bash + dotnet run + ``` + +3. The server will start and listen on `http://localhost:5188` by default + +4. Open your browser to `http://localhost:5188` to access the Swagger UI for interactive API documentation + +### Example Usage + +#### List Available Ports +```bash +curl http://localhost:5188/api/cat/ports +``` + +#### Open a Serial Port +```bash +curl -X POST http://localhost:5188/api/cat/open \ + -H "Content-Type: application/json" \ + -d '{ + "portName": "COM3", + "baudRate": 9600, + "parity": "None", + "stopBits": "One", + "handshake": "None" + }' +``` + +#### Send a CAT Command +```bash +curl -X POST http://localhost:5188/api/cat/command \ + -H "Content-Type: application/json" \ + -d '{"command": "FA;"}' +``` + +#### Check Status +```bash +curl http://localhost:5188/api/cat/status +``` + +## Configuration + +### Port Configuration Options + +When opening a port, you can configure: + +- **Port Name**: Serial port name (e.g., "COM1" on Windows, "/dev/ttyUSB0" on Linux) +- **Baud Rate**: Communication speed (default: 9600) +- **Parity**: Error checking method (None, Odd, Even, Mark, Space) +- **Stop Bits**: Number of stop bits (None, One, Two, OnePointFive) +- **Handshake**: Flow control method (None, XOnXOff, RequestToSend, RequestToSendXOnXOff) + +### Logging + +The server uses ASP.NET Core's built-in logging. Log levels can be configured in `appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "SharpCAT.Server": "Debug" + } + } +} +``` + +## Development + +### Building from Source + +1. Ensure you have the .NET 8.0 SDK installed +2. Clone the repository +3. Navigate to the project root +4. Build the solution: + ```bash + dotnet build + ``` + +### Project Structure + +``` +Server/SharpCAT.Server/ +├── Controllers/ +│ └── CatController.cs # Main API controller +├── Models/ +│ └── ApiModels.cs # Request/response models +├── Services/ +│ ├── ISerialCommunicationService.cs # Service interface +│ └── SerialCommunicationService.cs # Service implementation +├── Program.cs # Application entry point +└── SharpCAT.Server.csproj # Project file +``` + +### Dependencies + +- **Microsoft.AspNetCore.OpenApi** - OpenAPI support +- **Swashbuckle.AspNetCore** - Swagger documentation +- **SharpCAT** - Core CAT control library + +## License + +This project follows the same license as the parent SharpCAT project. + +## Contributing + +Contributions are welcome! Please follow the existing code style and add appropriate tests for new features. \ No newline at end of file diff --git a/Server/SharpCAT.Server/Services/ISerialCommunicationService.cs b/Server/SharpCAT.Server/Services/ISerialCommunicationService.cs new file mode 100644 index 0000000..e8b4c17 --- /dev/null +++ b/Server/SharpCAT.Server/Services/ISerialCommunicationService.cs @@ -0,0 +1,51 @@ +using System.IO.Ports; + +namespace SharpCAT.Server.Services +{ + /// + /// Interface for serial communication services + /// + public interface ISerialCommunicationService + { + /// + /// Gets all available serial ports on the system + /// + /// Array of port names + string[] GetAvailablePorts(); + + /// + /// Opens and configures a serial port + /// + /// Name of the port to open + /// Baud rate for communication + /// Parity setting + /// Stop bits setting + /// Handshake setting + /// True if port opened successfully + Task OpenPortAsync(string portName, int baudRate, Parity parity, StopBits stopBits, Handshake handshake); + + /// + /// Closes the currently opened port + /// + Task ClosePortAsync(); + + /// + /// Sends a CAT command to the radio + /// + /// CAT command string to send + /// Response from the radio, if any + Task SendCommandAsync(string command); + + /// + /// Gets the status of the current port connection + /// + /// True if port is open and connected + bool IsPortOpen(); + + /// + /// Gets the name of the currently opened port + /// + /// Port name or null if no port is open + string? GetCurrentPortName(); + } +} \ No newline at end of file diff --git a/Server/SharpCAT.Server/Services/SerialCommunicationService.cs b/Server/SharpCAT.Server/Services/SerialCommunicationService.cs new file mode 100644 index 0000000..96193fb --- /dev/null +++ b/Server/SharpCAT.Server/Services/SerialCommunicationService.cs @@ -0,0 +1,208 @@ +using System.IO.Ports; +using System.Text; + +namespace SharpCAT.Server.Services +{ + /// + /// Service for managing serial communication with radios using SharpCAT library + /// + public class SerialCommunicationService : ISerialCommunicationService, IDisposable + { + private readonly ILogger _logger; + private 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(); + 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; + } + } + } +} \ No newline at end of file diff --git a/Server/SharpCAT.Server/SharpCAT.Server.csproj b/Server/SharpCAT.Server/SharpCAT.Server.csproj new file mode 100644 index 0000000..a865745 --- /dev/null +++ b/Server/SharpCAT.Server/SharpCAT.Server.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + true + $(NoWarn);1591 + + + + + + + + + + + + diff --git a/Server/SharpCAT.Server/SharpCAT.Server.http b/Server/SharpCAT.Server/SharpCAT.Server.http new file mode 100644 index 0000000..0f8cad5 --- /dev/null +++ b/Server/SharpCAT.Server/SharpCAT.Server.http @@ -0,0 +1,6 @@ +@SharpCAT.Server_HostAddress = http://localhost:5188 + +GET {{SharpCAT.Server_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Server/SharpCAT.Server/appsettings.Development.json b/Server/SharpCAT.Server/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Server/SharpCAT.Server/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Server/SharpCAT.Server/appsettings.json b/Server/SharpCAT.Server/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Server/SharpCAT.Server/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/SharpCAT.sln b/SharpCAT.sln new file mode 100644 index 0000000..a8a1593 --- /dev/null +++ b/SharpCAT.sln @@ -0,0 +1,33 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT", "SharpCAT\SharpCAT.csproj", "{3EA807EF-B181-4C54-8502-0A2A3EACE984}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{43CFF66C-84E6-4EC2-AE4F-005FB80D74D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT.Server", "Server\SharpCAT.Server\SharpCAT.Server.csproj", "{22E4F655-252E-42DB-ADD5-494BC825A97C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3EA807EF-B181-4C54-8502-0A2A3EACE984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EA807EF-B181-4C54-8502-0A2A3EACE984}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EA807EF-B181-4C54-8502-0A2A3EACE984}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EA807EF-B181-4C54-8502-0A2A3EACE984}.Release|Any CPU.Build.0 = Release|Any CPU + {22E4F655-252E-42DB-ADD5-494BC825A97C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22E4F655-252E-42DB-ADD5-494BC825A97C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22E4F655-252E-42DB-ADD5-494BC825A97C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22E4F655-252E-42DB-ADD5-494BC825A97C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {22E4F655-252E-42DB-ADD5-494BC825A97C} = {43CFF66C-84E6-4EC2-AE4F-005FB80D74D5} + EndGlobalSection +EndGlobal