Compare commits

...

22 commits

Author SHA1 Message Date
Erick Kinnee 2c83ce3791
Merge pull request #24 from ekinnee/copilot/fix-afb7dc88-de08-4e38-937f-79124626152a
Add SerialController with GET /api/serial/ports endpoint
2025-08-06 22:32:56 -05:00
copilot-swe-agent[bot] 1e54b4ca10 Add SerialController with GET /api/serial/ports endpoint
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-07 03:17:42 +00:00
copilot-swe-agent[bot] 65648538c6 Initial plan 2025-08-07 03:11:08 +00:00
Erick Kinnee 416fcd98c9
Merge pull request #23 from ekinnee/copilot/fix-edfc2c6e-7e60-445f-a0c3-de881f3f3755
Add SharpCAT.Client library for typed C# API access to Server endpoints
2025-08-06 22:10:52 -05:00
copilot-swe-agent[bot] 99ac9e3b59 Add SharpCAT.Client library with full API implementation
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-07 03:06:27 +00:00
copilot-swe-agent[bot] 1c02dc4cac Initial plan 2025-08-07 02:57:06 +00:00
Erick Kinnee 4192efdd0c
Merge pull request #22 from ekinnee/copilot/fix-1377b357-2690-4bb4-acf3-57a77c4064f0
Move SharpCAT.Server to top-level Server directory and update project structure
2025-08-06 20:20:00 -05:00
copilot-swe-agent[bot] cae56ac617 Move SharpCAT.Server contents to top-level Server directory with updated namespaces
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-07 00:31:29 +00:00
copilot-swe-agent[bot] 5d4f39cb67 Initial plan 2025-08-07 00:23:04 +00:00
Erick Kinnee 88b7e3f6b9
Merge pull request #21 from ekinnee/copilot/fix-034c66c2-62f1-4625-8b2b-149c6993420a
Rename core CAT library directory from 'SharpCAT' to 'Library'
2025-08-06 19:13:20 -05:00
copilot-swe-agent[bot] 950d840238 Rename SharpCAT directory to Library and update all references
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-07 00:09:27 +00:00
copilot-swe-agent[bot] ff341a3fed Initial plan 2025-08-07 00:03:24 +00:00
Erick Kinnee 07e2d349bf
Update README.md 2025-08-06 18:58:44 -05:00
Erick Kinnee 339b72374f
Merge pull request #20 from ekinnee/copilot/fix-6b4ece58-38fe-43bc-b7fa-45b5cdd7d643
Implement cross-platform ASP.NET Core Web API server for SharpCAT with REST endpoints
2025-08-06 18:53:26 -05:00
copilot-swe-agent[bot] c20d3a12bb Implement ASP.NET Core Web API server for SharpCAT with REST endpoints
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-06 23:26:53 +00:00
copilot-swe-agent[bot] ce86879a3b Initial plan 2025-08-06 23:15:04 +00:00
Erick Kinnee b9287c3ecf
Merge pull request #19 from ekinnee/copilot/fix-1bc956c7-b256-461c-82f1-04d6eaeb84fb
Rename core library from SharpCATLib to SharpCAT throughout codebase
2025-08-06 18:10:03 -05:00
copilot-swe-agent[bot] 82983da270 Complete rename from SharpCATLib to SharpCAT throughout codebase
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-06 22:34:00 +00:00
copilot-swe-agent[bot] a16d2e4b92 Initial plan 2025-08-06 22:26:37 +00:00
Erick Kinnee c80f3e89a8
Merge pull request #16 from ekinnee/copilot/fix-a7a2af84-ec4c-4531-b98b-c3e17850ff95
Remove VirtualSerial2 directory and all references from repository
2025-08-06 17:02:04 -05:00
copilot-swe-agent[bot] bcbafbf8b3 Remove VirtualSerial2 directory and all its contents
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
2025-08-06 21:58:05 +00:00
copilot-swe-agent[bot] 1881bfb8cc Initial plan 2025-08-06 21:53:39 +00:00
62 changed files with 2012 additions and 3067 deletions

10
.vscode/tasks.json vendored
View file

@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/SharpCAT/SharpCATLib.csproj",
"${workspaceFolder}/Library/Library.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -23,7 +23,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/SharpCAT/SharpCATLib.csproj",
"${workspaceFolder}/Library/Library.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -37,7 +37,7 @@
"watch",
"run",
"--project",
"${workspaceFolder}/SharpCAT/SharpCATLib.csproj"
"${workspaceFolder}/Library/Library.csproj"
],
"problemMatcher": "$msCompile"
},
@ -47,7 +47,7 @@
"type": "process",
"args": [
"clean",
"${workspaceFolder}/SharpCAT/SharpCATLib.csproj",
"${workspaceFolder}/Library/Library.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -59,7 +59,7 @@
"type": "process",
"args": [
"restore",
"${workspaceFolder}/SharpCAT/SharpCATLib.csproj"
"${workspaceFolder}/Library/Library.csproj"
],
"problemMatcher": "$msCompile"
}

216
Client/Models/ApiModels.cs Normal file
View file

@ -0,0 +1,216 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace SharpCAT.Client.Models
{
/// <summary>
/// Parity settings for serial communication
/// </summary>
public enum Parity
{
/// <summary>
/// No parity checking
/// </summary>
None = 0,
/// <summary>
/// Odd parity checking
/// </summary>
Odd = 1,
/// <summary>
/// Even parity checking
/// </summary>
Even = 2,
/// <summary>
/// Mark parity checking
/// </summary>
Mark = 3,
/// <summary>
/// Space parity checking
/// </summary>
Space = 4
}
/// <summary>
/// Stop bits settings for serial communication
/// </summary>
public enum StopBits
{
/// <summary>
/// No stop bits
/// </summary>
None = 0,
/// <summary>
/// One stop bit
/// </summary>
One = 1,
/// <summary>
/// Two stop bits
/// </summary>
Two = 2,
/// <summary>
/// One and a half stop bits
/// </summary>
OnePointFive = 3
}
/// <summary>
/// Handshake settings for serial communication
/// </summary>
public enum Handshake
{
/// <summary>
/// No flow control
/// </summary>
None = 0,
/// <summary>
/// XOn/XOff software flow control
/// </summary>
XOnXOff = 1,
/// <summary>
/// RTS hardware flow control
/// </summary>
RequestToSend = 2,
/// <summary>
/// Both RTS and XOn/XOff flow control
/// </summary>
RequestToSendXOnXOff = 3
}
/// <summary>
/// Response model for listing available serial ports
/// </summary>
public class PortListResponse
{
/// <summary>
/// Array of available serial port names
/// </summary>
public string[] Ports { get; set; } = new string[0];
/// <summary>
/// Number of available ports
/// </summary>
public int Count => Ports.Length;
}
/// <summary>
/// Request model for opening a serial port
/// </summary>
public class OpenPortRequest
{
/// <summary>
/// Name of the serial port to open (e.g., "COM1", "/dev/ttyUSB0")
/// </summary>
[Required]
public string PortName { get; set; } = string.Empty;
/// <summary>
/// Baud rate for communication (default: 9600)
/// </summary>
public int BaudRate { get; set; } = 9600;
/// <summary>
/// Parity setting (default: None)
/// </summary>
public Parity Parity { get; set; } = Parity.None;
/// <summary>
/// Stop bits setting (default: One)
/// </summary>
public StopBits StopBits { get; set; } = StopBits.One;
/// <summary>
/// Handshake setting (default: None)
/// </summary>
public Handshake Handshake { get; set; } = Handshake.None;
}
/// <summary>
/// Response model for port operations
/// </summary>
public class PortOperationResponse
{
/// <summary>
/// Indicates if the operation was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Human-readable message about the operation
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// Name of the port that was operated on
/// </summary>
public string? PortName { get; set; }
/// <summary>
/// Current status of the port (open/closed)
/// </summary>
public bool IsOpen { get; set; }
}
/// <summary>
/// Request model for sending CAT commands
/// </summary>
public class SendCommandRequest
{
/// <summary>
/// CAT command string to send to the radio
/// </summary>
[Required]
public string Command { get; set; } = string.Empty;
}
/// <summary>
/// Response model for CAT command operations
/// </summary>
public class CommandResponse
{
/// <summary>
/// Indicates if the command was sent successfully
/// </summary>
public bool Success { get; set; }
/// <summary>
/// The command that was sent
/// </summary>
public string Command { get; set; } = string.Empty;
/// <summary>
/// Response received from the radio (if any)
/// </summary>
public string? Response { get; set; }
/// <summary>
/// Human-readable message about the operation
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// Timestamp when the command was executed
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
/// <summary>
/// Response model for errors
/// </summary>
public class ErrorResponse
{
/// <summary>
/// Error message
/// </summary>
public string Error { get; set; } = string.Empty;
/// <summary>
/// Additional details about the error
/// </summary>
public string? Details { get; set; }
/// <summary>
/// Timestamp when the error occurred
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
}

196
Client/README.md Normal file
View file

@ -0,0 +1,196 @@
# SharpCAT.Client
A .NET Standard 2.0 client library that provides a typed C# API for consuming the SharpCAT Server HTTP API endpoints. This library makes it easy to integrate CAT (Computer Aided Transceiver) control into your .NET applications.
## Features
- **Typed API**: Strongly-typed C# methods for all SharpCAT Server endpoints
- **Async/Await Support**: Modern async programming with cancellation token support
- **Error Handling**: Comprehensive error handling with custom exceptions
- **Cross-Platform**: .NET Standard 2.0 compatible (works with .NET Core, .NET Framework, .NET 5+)
- **HTTP Client Integration**: Uses System.Net.Http.Json for JSON serialization
- **Frequency Helpers**: Convenient methods for getting/setting radio frequencies
- **XML Documentation**: Full IntelliSense support with comprehensive documentation
## Installation
Add the SharpCAT.Client project reference to your application:
```xml
<ProjectReference Include="path/to/SharpCAT.Client/SharpCAT.Client.csproj" />
```
## Quick Start
### Basic Usage
```csharp
using SharpCAT.Client;
using SharpCAT.Client.Models;
// Create client instance
using var client = new SharpCATClient("http://localhost:5188");
// Get available serial ports
var ports = await client.GetPortsAsync();
Console.WriteLine($"Available ports: {string.Join(", ", ports.Ports)}");
// Open a serial port
var openRequest = new OpenPortRequest
{
PortName = "COM3",
BaudRate = 9600
};
var openResult = await client.OpenPortAsync(openRequest);
Console.WriteLine($"Port opened: {openResult.Success}");
// Send a raw CAT command
var commandResult = await client.SendCommandAsync("FA;");
Console.WriteLine($"Command response: {commandResult.Response}");
// Use convenience methods for frequency operations
var frequency = await client.GetFrequencyAsync();
Console.WriteLine($"Current frequency: {frequency} Hz");
await client.SetFrequencyAsync(14074000); // Set to 14.074 MHz
```
### Advanced Usage
```csharp
// Use with dependency injection and HttpClientFactory
services.AddHttpClient<SharpCATClient>(client =>
{
client.BaseAddress = new Uri("http://localhost:5188");
});
// Handle errors
try
{
var result = await client.SendCommandAsync("invalid_command");
}
catch (SharpCATClientException ex)
{
Console.WriteLine($"Error: {ex.Message}");
if (ex.Details != null)
Console.WriteLine($"Details: {ex.Details}");
}
// Use cancellation tokens
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var status = await client.GetStatusAsync(cts.Token);
```
## API Reference
### Core Methods
#### Port Management
- `GetPortsAsync()` - Get all available serial ports
- `OpenPortAsync(request)` - Open and configure a serial port
- `ClosePortAsync()` - Close the current serial port
- `GetStatusAsync()` - Get current port connection status
#### CAT Commands
- `SendCommandAsync(command)` - Send a raw CAT command string
- `SendCommandAsync(request)` - Send a CAT command with full request object
#### Frequency Operations (Convenience Methods)
- `GetFrequencyAsync()` - Get current frequency (VFO A)
- `SetFrequencyAsync(frequencyHz)` - Set frequency (VFO A)
- `GetFrequencyBAsync()` - Get current frequency (VFO B)
- `SetFrequencyBAsync(frequencyHz)` - Set frequency (VFO B)
### Configuration Options
When opening a port, you can configure:
```csharp
var request = new OpenPortRequest
{
PortName = "COM3", // Required
BaudRate = 9600, // Default: 9600
Parity = Parity.None, // Default: None
StopBits = StopBits.One, // Default: One
Handshake = Handshake.None // Default: None
};
```
### Error Handling
The client throws `SharpCATClientException` for various error conditions:
- HTTP request failures
- Network timeouts
- API error responses
- Deserialization failures
```csharp
try
{
var result = await client.GetPortsAsync();
}
catch (SharpCATClientException ex)
{
// Handle SharpCAT-specific errors
Console.WriteLine($"SharpCAT Error: {ex.Message}");
}
catch (Exception ex)
{
// Handle other errors
Console.WriteLine($"Unexpected error: {ex.Message}");
}
```
## Common CAT Commands
Here are some common CAT commands you can send using `SendCommandAsync()`:
| Command | Description | Example Response |
|---------|-------------|------------------|
| `FA;` | Get VFO A frequency | `FA00014074000;` |
| `FB;` | Get VFO B frequency | `FB00007074000;` |
| `FA14074000;` | Set VFO A frequency | No response or `FA14074000;` |
| `MD;` | Get operating mode | `MD2;` (USB) |
| `MD2;` | Set mode to USB | No response |
| `IF;` | Get radio information | `IF00014074000...;` |
**Note**: The exact commands and responses depend on your radio model. Consult your radio's CAT documentation for complete command reference.
## Thread Safety
The `SharpCATClient` class is thread-safe for concurrent read operations, but write operations (like sending commands) should be serialized to avoid conflicts at the radio level.
## Disposal
The client implements `IDisposable`. When you create a client with a base address string or URI, it owns the internal `HttpClient` and will dispose it. If you pass in your own `HttpClient`, the client will not dispose it.
```csharp
// Client owns HttpClient - will be disposed
using var client = new SharpCATClient("http://localhost:5188");
// You own HttpClient - manage disposal yourself
var httpClient = new HttpClient();
var client = new SharpCATClient(httpClient);
// Don't forget to dispose httpClient when done
```
## Requirements
- .NET Standard 2.0 or later
- SharpCAT Server running and accessible
- Network connectivity to the server
## Dependencies
- System.Net.Http.Json (5.0.0)
- System.Text.Json (5.0.2)
- System.ComponentModel.Annotations (5.0.0)
## 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 documentation for new features.

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="5.0.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
</Project>

325
Client/SharpCATClient.cs Normal file
View file

@ -0,0 +1,325 @@
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
{
/// <summary>
/// Client for accessing SharpCAT Server API endpoints
/// </summary>
public class SharpCATClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly bool _ownsHttpClient;
/// <summary>
/// Gets the base address of the SharpCAT server
/// </summary>
public Uri BaseAddress => _httpClient.BaseAddress ?? throw new InvalidOperationException("Base address not set");
/// <summary>
/// Initializes a new instance of the SharpCATClient with the specified base address
/// </summary>
/// <param name="baseAddress">Base URL of the SharpCAT server (e.g., "http://localhost:5188")</param>
public SharpCATClient(string baseAddress) : this(new Uri(baseAddress))
{
}
/// <summary>
/// Initializes a new instance of the SharpCATClient with the specified base address
/// </summary>
/// <param name="baseAddress">Base URI of the SharpCAT server</param>
public SharpCATClient(Uri baseAddress)
{
_httpClient = new HttpClient { BaseAddress = baseAddress };
_ownsHttpClient = true;
}
/// <summary>
/// Initializes a new instance of the SharpCATClient with an existing HttpClient
/// </summary>
/// <param name="httpClient">Configured HttpClient instance</param>
public SharpCATClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_ownsHttpClient = false;
}
/// <summary>
/// Gets all available serial ports on the system
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of available serial ports</returns>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<PortListResponse> GetPortsAsync(CancellationToken cancellationToken = default)
{
try
{
var response = await _httpClient.GetFromJsonAsync<PortListResponse>("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);
}
}
/// <summary>
/// Opens and configures a serial port for communication
/// </summary>
/// <param name="request">Port configuration parameters</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result of the port open operation</returns>
/// <exception cref="ArgumentNullException">Thrown when request is null</exception>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<PortOperationResponse> 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<PortOperationResponse>(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync<ErrorResponse>(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);
}
}
/// <summary>
/// Closes the currently opened serial port
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result of the port close operation</returns>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<PortOperationResponse> ClosePortAsync(CancellationToken cancellationToken = default)
{
try
{
var httpResponse = await _httpClient.PostAsync("api/cat/close", null, cancellationToken);
if (httpResponse.IsSuccessStatusCode)
{
var response = await httpResponse.Content.ReadFromJsonAsync<PortOperationResponse>(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync<ErrorResponse>(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);
}
}
/// <summary>
/// Gets the current status of the serial port connection
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Current port status</returns>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<PortOperationResponse> GetStatusAsync(CancellationToken cancellationToken = default)
{
try
{
var response = await _httpClient.GetFromJsonAsync<PortOperationResponse>("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);
}
}
/// <summary>
/// Sends a CAT command to the connected radio
/// </summary>
/// <param name="request">CAT command to send</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result of the command operation including any response from the radio</returns>
/// <exception cref="ArgumentNullException">Thrown when request is null</exception>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<CommandResponse> 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<CommandResponse>(cancellationToken: cancellationToken);
return response ?? throw new SharpCATClientException("Failed to deserialize response");
}
else
{
var errorResponse = await httpResponse.Content.ReadFromJsonAsync<ErrorResponse>(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);
}
}
/// <summary>
/// Sends a CAT command to the connected radio
/// </summary>
/// <param name="command">CAT command string to send</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result of the command operation including any response from the radio</returns>
/// <exception cref="ArgumentNullException">Thrown when command is null</exception>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<CommandResponse> 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);
}
/// <summary>
/// Gets the current frequency from the radio (VFO A)
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Current frequency in Hz, or null if the command failed or returned no data</returns>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<long?> 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;
}
/// <summary>
/// Sets the frequency for the radio (VFO A)
/// </summary>
/// <param name="frequencyHz">Frequency in Hz</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if the command was sent successfully</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when frequency is out of valid range</exception>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<bool> 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;
}
/// <summary>
/// Gets the current frequency from the radio (VFO B)
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Current frequency in Hz, or null if the command failed or returned no data</returns>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<long?> 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;
}
/// <summary>
/// Sets the frequency for the radio (VFO B)
/// </summary>
/// <param name="frequencyHz">Frequency in Hz</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if the command was sent successfully</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when frequency is out of valid range</exception>
/// <exception cref="SharpCATClientException">Thrown when the API call fails</exception>
public async Task<bool> 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;
}
/// <summary>
/// Disposes the client and releases resources
/// </summary>
public void Dispose()
{
if (_ownsHttpClient)
{
_httpClient?.Dispose();
}
}
}
}

View file

@ -0,0 +1,60 @@
using System;
namespace SharpCAT.Client
{
/// <summary>
/// Exception thrown by SharpCAT client operations
/// </summary>
public class SharpCATClientException : Exception
{
/// <summary>
/// Additional details about the error
/// </summary>
public string? Details { get; }
/// <summary>
/// Initializes a new instance of the SharpCATClientException class
/// </summary>
public SharpCATClientException()
{
}
/// <summary>
/// Initializes a new instance of the SharpCATClientException class with a specified error message
/// </summary>
/// <param name="message">The message that describes the error</param>
public SharpCATClientException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the SharpCATClientException class with a specified error message and details
/// </summary>
/// <param name="message">The message that describes the error</param>
/// <param name="details">Additional details about the error</param>
public SharpCATClientException(string message, string? details) : base(message)
{
Details = details;
}
/// <summary>
/// Initializes a new instance of the SharpCATClientException class with a specified error message and a reference to the inner exception
/// </summary>
/// <param name="message">The message that describes the error</param>
/// <param name="innerException">The exception that is the cause of the current exception</param>
public SharpCATClientException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the SharpCATClientException class with a specified error message, details, and a reference to the inner exception
/// </summary>
/// <param name="message">The message that describes the error</param>
/// <param name="details">Additional details about the error</param>
/// <param name="innerException">The exception that is the cause of the current exception</param>
public SharpCATClientException(string message, string? details, Exception innerException) : base(message, innerException)
{
Details = details;
}
}
}

View file

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace SharpCATLib
namespace SharpCAT
{
class FlrigProtocol
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Models
namespace SharpCAT.Models
{
internal class CATCommand
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Models
namespace SharpCAT.Models
{
//Base Radio Model
public partial class CATRadio : IRadio

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Models
namespace SharpCAT.Models
{
internal class CIVCommand
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Models
namespace SharpCAT.Models
{
internal class CIVRadio : IRadio
{

View file

@ -1,5 +1,5 @@
using System;
namespace SharpCATLib.Models
namespace SharpCAT.Models
{
partial interface IRadio
{

View file

@ -1,6 +1,6 @@
using SharpCATLib.Models;
using SharpCAT.Models;
namespace SharpCATLib.Radios.Icom
namespace SharpCAT.Radios.Icom
{
internal class ID4100a : CIVRadio
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Radios.Icom
namespace SharpCAT.Radios.Icom
{
internal class ID880H
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib.Radios.Kenwood
namespace SharpCAT.Radios.Kenwood
{
internal class THD74A
{

View file

@ -1,6 +1,6 @@
using SharpCATLib.Models;
using SharpCAT.Models;
namespace SharpCATLib.Radios.Yaesu
namespace SharpCAT.Radios.Yaesu
{
public class FT818 : CATRadio
{

View file

@ -1,7 +1,7 @@
using System;
using System.IO.Ports;
namespace SharpCATLib
namespace SharpCAT
{
public class Serial
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib
namespace SharpCAT
{
internal class SerialClient
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib
namespace SharpCAT
{
internal class SerialServer
{

View file

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.IO.Ports;
namespace SharpCATLib
namespace SharpCAT
{
public class SharpCAT
{

View file

@ -1,4 +1,4 @@
namespace SharpCATLib
namespace SharpCAT
{
internal class Socket
{

150
README.md
View file

@ -1,93 +1,129 @@
# 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.
I am targeting .Net Standard so that the assembly may be used with .Net Core or the .Net framework.
**SharpCAT** is a C#/.NET cross-platform library and Web API server for CAT (Computer Aided Transceiver) radio control. It supports serial port CAT operations, modern async programming, and provides a REST API for remote radio control.
## Development Setup
---
## Features
- **Core Library**: .NET Standard-based, works with .NET Core and .NET Framework
- **Cross-platform Server**: ASP.NET Core Web API server, works on Windows, Linux, and macOS
- **REST API**: Endpoints for serial port management and sending arbitrary CAT commands
- **Swagger/OpenAPI**: Interactive API docs and testing interface
- **Async/Await**: Modern async support for non-blocking IO
- **Extensible**: Designed for adding radios and protocols (FT818, ID-4100A, TH-D74A, etc.)
- **Built-in Logging and Error Handling**
---
## Quick Start
### Prerequisites
- [.NET 8.0 SDK](https://dotnet.microsoft.com/download) or later
- [Visual Studio Code](https://code.visualstudio.com/)
- [C# Extension for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp)
### Building and Running
### Running the Web API Server
This project is configured for development in Visual Studio Code with the .NET CLI.
**To build the project:**
```bash
dotnet build SharpCAT/SharpCATLib.csproj
cd Server/SharpCAT.Server
dotnet run
```
The server listens on `http://localhost:5188` by default.
Open [http://localhost:5188](http://localhost:5188) in your browser for the Swagger UI.
---
## Usage Examples
### List Available Serial Ports
```bash
curl http://localhost:5188/api/cat/ports
```
**To clean the project:**
### Open a Serial Port
```bash
dotnet clean SharpCAT/SharpCATLib.csproj
curl -X POST http://localhost:5188/api/cat/open \
-H "Content-Type: application/json" \
-d '{"portName": "COM3", "baudRate": 9600}'
```
**To restore packages:**
### Send a CAT Command
```bash
dotnet restore SharpCAT/SharpCATLib.csproj
curl -X POST http://localhost:5188/api/cat/command \
-H "Content-Type: application/json" \
-d '{"command": "FA;"}'
```
**VS Code Tasks:**
The repository includes pre-configured VS Code tasks (`Ctrl+Shift+P` → "Tasks: Run Task"):
- **build** - Builds the project (default build task: `Ctrl+Shift+B`)
- **clean** - Cleans build outputs
- **restore** - Restores NuGet packages
- **publish** - Publishes the library
- **watch** - Watches for changes and rebuilds automatically
---
I'm starting with the FT818, I then plan on adding my ID-4100a, and TH-D74A.
## Project Structure
If you wish to help let me know, or create a pull request. I'm not a pro developer, just a hack
that normally builds small tools for himself.
```
Library/ # Core CAT library (cross-platform)
Server/SharpCAT.Server/ # ASP.NET Core Web API server
```
Ideas on how to do this are appreciated!
---
# Why?
Yes, there's Hamlib, and yes there's HamLibSharp.
## Development
https://github.com/N0NB/hamlib
### Build Everything
https://github.com/k5jae/HamLibSharp
```bash
dotnet build
```
I don't speak C++ and PInvoke is nasty.
### Build Just the Core Library
That being said, there's also not a pure .Net (C#) CAT control lib out there that I know of.
```bash
dotnet build Library/Library.csproj
```
# What needs done?
The project is in an early very phase. The following is in no particular order, except the IRadio bit.
### VS Code Tasks
1. Implement an IRadio interface.
This repo includes VS Code tasks for building, cleaning, restoring, publishing, and watching changes.
2. Settle on how the radios and commands are defined.
---
JSON?
Or just use a .cs file (current)?
3. Need to support opening / using an arbitrary number of radios.
## Roadmap & Ideas
RigControl can do 2.
4. Fully support asynchronous operations.
- Implement generic IRadio interface and radio-specific drivers
- Support multiple concurrent radios
- Fully async serial operations
- Remote sharing of serial ports
- Implement flrig protocol support
- Potential AGWPE integration
- Service/daemon operation support
- Additional radio protocols (CAT, CI-V, etc.)
There's some handy events exposed by .net for data received on the Serial Port.
5. Add support for remote sharing of serial ports.
---
6. Implemtment the flrig control protocol.
Use this in place of flrig if desired.
## Why?
7. Maybe implement AGWPE?
While Hamlib and HamLibSharp exist, there is a need for a modern, pure .NET CAT control library and server for direct integration with C# projects, without C++/PInvoke dependencies.
8. Implement the ability to run as a service.
---
9. Implement CAT and CIV control.
## Contributing
This will come with the first Icom radio implemented I figure.
I've switched radios, so I have a Yaesu FT991a, Baofeng BF-T1, WLN KD-C1, Baofeng UV-5x3, TH-D72a, FT-2DR and an D878UV.
Contributions are very welcome! Please open issues, submit PRs, or share your ideas.
---
## License
This project follows the same license as the parent SharpCAT project.
---
## Credits
- Inspired by FT818, ID-4100A, TH-D74A, Yaesu FT991a, and others
- Thanks to all open source contributors and radio amateurs for protocol documentation and ideas
---
*If you wish to help, let me know or create a pull request. I'm not a pro developer, just a hobbyist building tools for myself and others.*

View file

@ -0,0 +1,292 @@
using Microsoft.AspNetCore.Mvc;
using Server.Models;
using Server.Services;
namespace Server.Controllers
{
/// <summary>
/// Controller for CAT (Computer Aided Transceiver) operations
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class CatController : ControllerBase
{
private readonly ISerialCommunicationService _serialService;
private readonly ILogger<CatController> _logger;
public CatController(ISerialCommunicationService serialService, ILogger<CatController> logger)
{
_serialService = serialService ?? throw new ArgumentNullException(nameof(serialService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Gets all available serial ports on the system
/// </summary>
/// <returns>List of available serial ports</returns>
/// <response code="200">Returns the list of available serial ports</response>
[HttpGet("ports")]
[ProducesResponseType(typeof(PortListResponse), StatusCodes.Status200OK)]
public ActionResult<PortListResponse> 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
});
}
}
/// <summary>
/// Opens and configures a serial port for communication
/// </summary>
/// <param name="request">Port configuration parameters</param>
/// <returns>Result of the port open operation</returns>
/// <response code="200">Port opened successfully</response>
/// <response code="400">Invalid request parameters</response>
/// <response code="500">Internal server error</response>
[HttpPost("open")]
[ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<PortOperationResponse>> 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
});
}
}
/// <summary>
/// Closes the currently opened serial port
/// </summary>
/// <returns>Result of the port close operation</returns>
/// <response code="200">Port closed successfully</response>
/// <response code="500">Internal server error</response>
[HttpPost("close")]
[ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<PortOperationResponse>> 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
});
}
}
/// <summary>
/// Sends a CAT command to the connected radio
/// </summary>
/// <param name="request">CAT command to send</param>
/// <returns>Result of the command operation including any response from the radio</returns>
/// <response code="200">Command sent successfully</response>
/// <response code="400">Invalid request parameters or no port open</response>
/// <response code="500">Internal server error</response>
[HttpPost("command")]
[ProducesResponseType(typeof(CommandResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<CommandResponse>> 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
});
}
}
/// <summary>
/// Gets the current status of the serial port connection
/// </summary>
/// <returns>Current port status</returns>
/// <response code="200">Returns current port status</response>
[HttpGet("status")]
[ProducesResponseType(typeof(PortOperationResponse), StatusCodes.Status200OK)]
public ActionResult<PortOperationResponse> 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
});
}
}
}
}

View file

@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Mvc;
using Server.Models;
using Server.Services;
namespace Server.Controllers
{
/// <summary>
/// Controller for serial port management operations
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class SerialController : ControllerBase
{
private readonly ISerialCommunicationService _serialService;
private readonly ILogger<SerialController> _logger;
public SerialController(ISerialCommunicationService serialService, ILogger<SerialController> logger)
{
_serialService = serialService ?? throw new ArgumentNullException(nameof(serialService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Gets all available serial ports on the server
/// </summary>
/// <returns>JSON array of available serial port names</returns>
/// <response code="200">Returns the list of available serial ports</response>
/// <response code="500">Internal server error</response>
[HttpGet("ports")]
[ProducesResponseType(typeof(PortListResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public ActionResult<PortListResponse> 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
});
}
}
}
}

143
Server/Models/ApiModels.cs Normal file
View file

@ -0,0 +1,143 @@
using System.ComponentModel.DataAnnotations;
using System.IO.Ports;
namespace Server.Models
{
/// <summary>
/// Response model for listing available serial ports
/// </summary>
public class PortListResponse
{
/// <summary>
/// Array of available serial port names
/// </summary>
public string[] Ports { get; set; } = Array.Empty<string>();
/// <summary>
/// Number of available ports
/// </summary>
public int Count => Ports.Length;
}
/// <summary>
/// Request model for opening a serial port
/// </summary>
public class OpenPortRequest
{
/// <summary>
/// Name of the serial port to open (e.g., "COM1", "/dev/ttyUSB0")
/// </summary>
[Required]
public string PortName { get; set; } = string.Empty;
/// <summary>
/// Baud rate for communication (default: 9600)
/// </summary>
public int BaudRate { get; set; } = 9600;
/// <summary>
/// Parity setting (default: None)
/// </summary>
public Parity Parity { get; set; } = Parity.None;
/// <summary>
/// Stop bits setting (default: One)
/// </summary>
public StopBits StopBits { get; set; } = StopBits.One;
/// <summary>
/// Handshake setting (default: None)
/// </summary>
public Handshake Handshake { get; set; } = Handshake.None;
}
/// <summary>
/// Response model for port operations
/// </summary>
public class PortOperationResponse
{
/// <summary>
/// Indicates if the operation was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Human-readable message about the operation
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// Name of the port that was operated on
/// </summary>
public string? PortName { get; set; }
/// <summary>
/// Current status of the port (open/closed)
/// </summary>
public bool IsOpen { get; set; }
}
/// <summary>
/// Request model for sending CAT commands
/// </summary>
public class SendCommandRequest
{
/// <summary>
/// CAT command string to send to the radio
/// </summary>
[Required]
public string Command { get; set; } = string.Empty;
}
/// <summary>
/// Response model for CAT command operations
/// </summary>
public class CommandResponse
{
/// <summary>
/// Indicates if the command was sent successfully
/// </summary>
public bool Success { get; set; }
/// <summary>
/// The command that was sent
/// </summary>
public string Command { get; set; } = string.Empty;
/// <summary>
/// Response received from the radio (if any)
/// </summary>
public string? Response { get; set; }
/// <summary>
/// Human-readable message about the operation
/// </summary>
public string Message { get; set; } = string.Empty;
/// <summary>
/// Timestamp when the command was executed
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
/// <summary>
/// Response model for errors
/// </summary>
public class ErrorResponse
{
/// <summary>
/// Error message
/// </summary>
public string Error { get; set; } = string.Empty;
/// <summary>
/// Additional details about the error
/// </summary>
public string? Details { get; set; }
/// <summary>
/// Timestamp when the error occurred
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
}

67
Server/Program.cs Normal file
View file

@ -0,0 +1,67 @@
using 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<ISerialCommunicationService, SerialCommunicationService>();
// 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();

View file

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

150
Server/README.md Normal file
View file

@ -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.

20
Server/Server.csproj Normal file
View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.18" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Library.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,51 @@
using System.IO.Ports;
namespace Server.Services
{
/// <summary>
/// Interface for serial communication services
/// </summary>
public interface ISerialCommunicationService
{
/// <summary>
/// Gets all available serial ports on the system
/// </summary>
/// <returns>Array of port names</returns>
string[] GetAvailablePorts();
/// <summary>
/// Opens and configures a serial port
/// </summary>
/// <param name="portName">Name of the port to open</param>
/// <param name="baudRate">Baud rate for communication</param>
/// <param name="parity">Parity setting</param>
/// <param name="stopBits">Stop bits setting</param>
/// <param name="handshake">Handshake setting</param>
/// <returns>True if port opened successfully</returns>
Task<bool> OpenPortAsync(string portName, int baudRate, Parity parity, StopBits stopBits, Handshake handshake);
/// <summary>
/// Closes the currently opened port
/// </summary>
Task ClosePortAsync();
/// <summary>
/// Sends a CAT command to the radio
/// </summary>
/// <param name="command">CAT command string to send</param>
/// <returns>Response from the radio, if any</returns>
Task<string> SendCommandAsync(string command);
/// <summary>
/// Gets the status of the current port connection
/// </summary>
/// <returns>True if port is open and connected</returns>
bool IsPortOpen();
/// <summary>
/// Gets the name of the currently opened port
/// </summary>
/// <returns>Port name or null if no port is open</returns>
string? GetCurrentPortName();
}
}

View file

@ -0,0 +1,208 @@
using System.IO.Ports;
using System.Text;
namespace Server.Services
{
/// <summary>
/// Service for managing serial communication with radios using SharpCAT library
/// </summary>
public class SerialCommunicationService : ISerialCommunicationService, IDisposable
{
private readonly ILogger<SerialCommunicationService> _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<SerialCommunicationService> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
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<string>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting available serial ports");
return Array.Empty<string>();
}
}
/// <inheritdoc />
public async Task<bool> 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();
}
}
/// <inheritdoc />
public async Task ClosePortAsync()
{
await _semaphore.WaitAsync();
try
{
await ClosePortInternalAsync();
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc />
public async Task<string> 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();
}
}
/// <inheritdoc />
public bool IsPortOpen()
{
return _directSerialPort?.IsOpen == true;
}
/// <inheritdoc />
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;
}
}
}
}

View file

@ -0,0 +1,6 @@
@SharpCAT.Server_HostAddress = http://localhost:5188
GET {{SharpCAT.Server_HostAddress}}/weatherforecast/
Accept: application/json
###

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
Server/appsettings.json Normal file
View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

39
SharpCAT.sln Normal file
View file

@ -0,0 +1,39 @@

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}") = "Library", "Library\Library.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}") = "Server", "Server\Server.csproj", "{22E4F655-252E-42DB-ADD5-494BC825A97C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT.Client", "Client\SharpCAT.Client.csproj", "{661C070A-612A-41CD-B9DC-9EF9FA05121A}"
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
{661C070A-612A-41CD-B9DC-9EF9FA05121A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{661C070A-612A-41CD-B9DC-9EF9FA05121A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{661C070A-612A-41CD-B9DC-9EF9FA05121A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{661C070A-612A-41CD-B9DC-9EF9FA05121A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{22E4F655-252E-42DB-ADD5-494BC825A97C} = {43CFF66C-84E6-4EC2-AE4F-005FB80D74D5}
EndGlobalSection
EndGlobal

View file

@ -1,187 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{65A037CB-9245-442A-A791-5CFC34E97BF7}</ProjectGuid>
<RootNamespace>$(MSBuildProjectName)</RootNamespace>
<UMDF_VERSION_MAJOR>2</UMDF_VERSION_MAJOR>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
<SampleGuid>{D3E77C62-C6F0-4210-824E-1875C7B48EC5}</SampleGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>False</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>True</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>False</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>True</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<OutDir>$(IntDir)</OutDir>
</PropertyGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ItemGroup Label="WrappedTaskItems" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>VirtualSerial2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>VirtualSerial2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>VirtualSerial2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>VirtualSerial2um</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\device.c" />
<ClCompile Include="..\driver.c" />
<ClCompile Include="..\queue.c" />
<ClCompile Include="..\ringbuffer.c" />
<ResourceCompile Include="virtualserial2um.rc" />
</ItemGroup>
<ItemGroup>
<Inf Exclude="@(Inf)" Include="*.inx" />
<FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" />
</ItemGroup>
<ItemGroup>
<None Exclude="@(None)" Include="*.txt;*.htm;*.html" />
<None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" />
<None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" />
</ItemGroup>
<ItemGroup>
<ClInclude Exclude="@(ClInclude)" Include="*.h;*.hpp;*.hxx;*.hm;*.inl;*.xsd" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;*</Extensions>
<UniqueIdentifier>{75562F82-D2DC-4C96-8D1E-01C195D61A2C}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
<UniqueIdentifier>{0748F304-5FE6-4C01-B27A-3CD7B1A268B7}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml</Extensions>
<UniqueIdentifier>{87F14B94-F7A4-42E8-9931-8ADDA3989EBD}</UniqueIdentifier>
</Filter>
<Filter Include="Driver Files">
<Extensions>inf;inv;inx;mof;mc;</Extensions>
<UniqueIdentifier>{30F678DB-C12C-461E-8128-CE409AAC9C42}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\device.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\driver.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\queue.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\ringbuffer.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="virtualserial2um.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View file

@ -1,17 +0,0 @@
//---------------------------------------------------------------------------
// Virtualserial.rc
//
// Copyright (c) Microsoft Corporation, All Rights Reserved
//---------------------------------------------------------------------------
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_DLL
#define VER_FILESUBTYPE VFT_UNKNOWN
#define VER_FILEDESCRIPTION_STR "WDF:UMDF VirtualSerial User-Mode v2 Driver Sample"
#define VER_INTERNALNAME_STR "VirtualSerial2um"
#define VER_ORIGINALFILENAME_STR "Virtualserial2um.dll"
#include "common.ver"

View file

@ -1,17 +0,0 @@
//---------------------------------------------------------------------------
// FakeModem.rc
//
// Copyright (c) Microsoft Corporation, All Rights Reserved
//---------------------------------------------------------------------------
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_DLL
#define VER_FILESUBTYPE VFT_UNKNOWN
#define VER_FILEDESCRIPTION_STR "WDF:UMDF Fake Modem User-Mode Driver Sample"
#define VER_INTERNALNAME_STR "FakeModem2um"
#define VER_ORIGINALFILENAME_STR "FakeModem2um.dll"
#include "common.ver"

View file

@ -1,187 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{CD78D78F-B132-4E6F-A11F-B62185A6152A}</ProjectGuid>
<RootNamespace>$(MSBuildProjectName)</RootNamespace>
<UMDF_VERSION_MAJOR>2</UMDF_VERSION_MAJOR>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">Win32</Platform>
<SampleGuid>{3E00ED06-5DB5-444F-8FFA-D098A0218DF2}</SampleGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>False</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>True</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>False</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries>True</UseDebugLibraries>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<DriverType>UMDF</DriverType>
<PlatformToolset>WindowsUserModeDriver10.0</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<OutDir>$(IntDir)</OutDir>
</PropertyGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ItemGroup Label="WrappedTaskItems" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>fakemodem2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>fakemodem2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>fakemodem2um</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>fakemodem2um</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
<ExceptionHandling>
</ExceptionHandling>
</ClCompile>
<Midl>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_UNICODE;UNICODE;_FAKE_MODEM=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DDK_INC_PATH)\wdm;..\..\inc</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);$(SDK_LIB_PATH)\mincore.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\device.c" />
<ClCompile Include="..\driver.c" />
<ClCompile Include="..\queue.c" />
<ClCompile Include="..\ringbuffer.c" />
<ResourceCompile Include="fakemodem2um.rc" />
</ItemGroup>
<ItemGroup>
<Inf Exclude="@(Inf)" Include="*.inx" />
<FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" />
</ItemGroup>
<ItemGroup>
<None Exclude="@(None)" Include="*.txt;*.htm;*.html" />
<None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" />
<None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" />
</ItemGroup>
<ItemGroup>
<ClInclude Exclude="@(ClInclude)" Include="*.h;*.hpp;*.hxx;*.hm;*.inl;*.xsd" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;*</Extensions>
<UniqueIdentifier>{5A926099-4361-4BDC-BF0F-D98AD6F55C3A}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
<UniqueIdentifier>{9C5FC831-BCE8-4E84-8BED-0B1182AA7358}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml</Extensions>
<UniqueIdentifier>{43B83C13-0033-4A5E-A0DB-F0BA1B304FB0}</UniqueIdentifier>
</Filter>
<Filter Include="Driver Files">
<Extensions>inf;inv;inx;mof;mc;</Extensions>
<UniqueIdentifier>{83A3125A-7FA1-4416-B24A-FA9C1EECAAF0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\device.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\driver.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\queue.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\ringbuffer.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="fakemodem2um.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View file

@ -1,64 +0,0 @@
---
page_type: sample
urlFragment: virtual-serial-driver-sample-v2
description: "Demonstrates UMDF version 2 serial drivers and includes a simple virtual serial driver (ComPort) and a controller-less modem driver (FakeModem)."
languages:
- cpp
products:
- windows
- windows-wdk
---
# Virtual serial driver sample (V2)
This sample demonstrates these two serial drivers:
- A simple virtual serial driver (ComPort)
- A controller-less modem driver (FakeModem).This driver supports sending and receiving AT commands using the ReadFile and WriteFile calls or via a TAPI interface using an application such as, HyperTerminal.
This sample driver is a minimal driver meant to demonstrate the usage of the User-Mode Driver Framework. It is not intended for use in a production environment.
For more information, see the [Serial Controller Driver Design Guide](https://docs.microsoft.com/windows-hardware/drivers/serports/).
## Code tour
### comsup.cpp and comsup.h
- COM Support code - specifically base classes which provide implementations for the standard COM interfaces **IUnknown** and **IClassFactory** which are used throughout the sample.
- The implementation of **IClassFactory** is designed to create instances of the CMyDriver class. If you should change the name of your base driver class, you would also need to modify this file.
### dllsup.cpp
- DLL Support code - provides the DLL's entry point as well as the single required export (**DllGetClassObject**).
- These depend on comsup.cpp to perform the necessary class creation.
### exports.def
- This file lists the functions that the driver DLL exports.
### internal.h
- This is the main header file for the sample driver.
### driver.cpp and driver.h
- Definition and implementation of the driver callback class (CMyDriver) for the sample. This includes **DriverEntry** and events on the framework driver object.
### device.cpp and driver.h
- Definition and implementation of the device callback class (CMyDriver) for the sample. This includes events on the framework device object.
### queue.cpp and queue.h
- Definition and implementation of the base queue callback class (CMyQueue). This includes events on the framework I/O queue object.
### VirtualSerial.rc /FakeModem.rc
- This file defines resource information for the sample driver.
### VirtualSerial.inf / FakeModem.inf
- INF file that contains installation information for this driver.

View file

@ -1,46 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0
MinimumVisualStudioVersion = 12.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ComPort", "ComPort", "{F7F21610-DDE7-4709-9C48-68A0ABD1FF65}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FakeModem", "FakeModem", "{704FA9C6-88FA-4381-8FA5-3407E62FAF20}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VirtualSerial2um", "ComPort\VirtualSerial2um.vcxproj", "{65A037CB-9245-442A-A791-5CFC34E97BF7}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fakemodem2um", "FakeModem\fakemodem2um.vcxproj", "{CD78D78F-B132-4E6F-A11F-B62185A6152A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Debug|Win32.ActiveCfg = Debug|Win32
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Debug|Win32.Build.0 = Debug|Win32
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Release|Win32.ActiveCfg = Release|Win32
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Release|Win32.Build.0 = Release|Win32
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Debug|x64.ActiveCfg = Debug|x64
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Debug|x64.Build.0 = Debug|x64
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Release|x64.ActiveCfg = Release|x64
{65A037CB-9245-442A-A791-5CFC34E97BF7}.Release|x64.Build.0 = Release|x64
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Debug|Win32.ActiveCfg = Debug|Win32
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Debug|Win32.Build.0 = Debug|Win32
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Release|Win32.ActiveCfg = Release|Win32
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Release|Win32.Build.0 = Release|Win32
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Debug|x64.ActiveCfg = Debug|x64
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Debug|x64.Build.0 = Debug|x64
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Release|x64.ActiveCfg = Release|x64
{CD78D78F-B132-4E6F-A11F-B62185A6152A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{65A037CB-9245-442A-A791-5CFC34E97BF7} = {F7F21610-DDE7-4709-9C48-68A0ABD1FF65}
{CD78D78F-B132-4E6F-A11F-B62185A6152A} = {704FA9C6-88FA-4381-8FA5-3407E62FAF20}
EndGlobalSection
EndGlobal

View file

@ -1,449 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved.
Module Name:
Device.c
Abstract:
This module contains the implementation of the VirtualSerial sample
driver's device callback object.
The VirtualSerial sample device does very little. It does not implement
either of the PNP interfaces so once the device is setup, it won't ever get
any callbacks until the device is removed.
Environment:
Windows Driver Framework
--*/
#include "internal.h"
NTSTATUS
DeviceCreate(
_In_ WDFDRIVER Driver,
_In_ PWDFDEVICE_INIT DeviceInit,
_Out_ PDEVICE_CONTEXT *DeviceContext
)
/*++
Routine Description:
This method creates and initializs an instance of the VirtualSerial driver's
device callback object.
Arguments:
FxDeviceInit - the settings for the device.
Device - a location to store the referenced pointer to the device object.
Return Value:
Status
--*/
{
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
WDFDEVICE device;
PDEVICE_CONTEXT deviceContext;
UNREFERENCED_PARAMETER (Driver);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(
&deviceAttributes,
DEVICE_CONTEXT);
deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice;
deviceAttributes.EvtCleanupCallback = EvtDeviceCleanup;
status = WdfDeviceCreate(&DeviceInit,
&deviceAttributes,
&device);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfDeviceCreate failed 0x%x", status);
return status;
}
deviceContext = GetDeviceContext(device);
deviceContext->Device = device;
*DeviceContext = deviceContext;
return status;
}
NTSTATUS
DeviceConfigure(
_In_ PDEVICE_CONTEXT DeviceContext
)
/*++
Routine Description:
This method is called after the device callback object has been initialized
and returned to the driver. It would setup the device's queues and their
corresponding callback objects.
Arguments:
FxDevice - the framework device object for which we're handling events.
Return Value:
status
--*/
{
NTSTATUS status;
WDFDEVICE device = DeviceContext->Device;
WDFKEY key;
LPGUID guid;
errno_t errorNo;
DECLARE_CONST_UNICODE_STRING(portName, REG_VALUENAME_PORTNAME);
DECLARE_UNICODE_STRING_SIZE (comPort, 10);
DECLARE_UNICODE_STRING_SIZE (symbolicLinkName, SYMBOLIC_LINK_NAME_LENGTH);
#ifdef _FAKE_MODEM
//
// Compiled as fake modem
//
guid = (LPGUID) &GUID_DEVINTERFACE_MODEM;
#else
//
// Compiled as virtual serial port
//
guid = (LPGUID) &GUID_DEVINTERFACE_COMPORT;
#endif
//
// Create device interface
//
status = WdfDeviceCreateDeviceInterface(
device,
guid,
NULL);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Cannot create device interface");
goto Exit;
}
//
// Read the COM port number from the registry, which has been automatically
// created by "MsPorts!PortsClassInstaller" if INF file says "Class=Ports"
//
status = WdfDeviceOpenRegistryKey(
device,
PLUGPLAY_REGKEY_DEVICE,
KEY_QUERY_VALUE,
WDF_NO_OBJECT_ATTRIBUTES,
&key);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to retrieve device hardware key root");
goto Exit;
}
status = WdfRegistryQueryUnicodeString(
key,
&portName,
NULL,
&comPort);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to read PortName");
goto Exit;
}
//
// Manually create the symbolic link name. Length is the length in
// bytes not including the NULL terminator.
//
// 6054 and 26035 are code analysis warnings that comPort.Buffer might
// not be NULL terminated, while we know that they are.
//
#pragma warning(suppress: 6054 26035)
symbolicLinkName.Length = (USHORT)((wcslen(comPort.Buffer) * sizeof(wchar_t))
+ sizeof(SYMBOLIC_LINK_NAME_PREFIX) - sizeof(UNICODE_NULL));
if (symbolicLinkName.Length >= symbolicLinkName.MaximumLength) {
Trace(TRACE_LEVEL_ERROR, "Error: Buffer overflow when creating COM port name. Size"
" is %d, buffer length is %d", symbolicLinkName.Length, symbolicLinkName.MaximumLength);
status = STATUS_BUFFER_OVERFLOW;
goto Exit;
}
errorNo = wcscpy_s(symbolicLinkName.Buffer,
SYMBOLIC_LINK_NAME_LENGTH,
SYMBOLIC_LINK_NAME_PREFIX);
if (errorNo != 0) {
Trace(TRACE_LEVEL_ERROR,
"Failed to copy %ws to buffer with error %d",
SYMBOLIC_LINK_NAME_PREFIX, errorNo);
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
errorNo = wcscat_s(symbolicLinkName.Buffer,
SYMBOLIC_LINK_NAME_LENGTH,
comPort.Buffer);
if (errorNo != 0) {
Trace(TRACE_LEVEL_ERROR,
"Failed to copy %ws to buffer with error %d",
comPort.Buffer, errorNo);
status = STATUS_INVALID_PARAMETER;
goto Exit;
}
//
// Create symbolic link
//
status = WdfDeviceCreateSymbolicLink(
device,
&symbolicLinkName);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Cannot create symbolic link %ws", symbolicLinkName.Buffer);
goto Exit;
}
status = DeviceGetPdoName(DeviceContext);
if (!NT_SUCCESS(status)) {
goto Exit;
}
status = DeviceWriteLegacyHardwareKey(
DeviceContext->PdoName,
comPort.Buffer,
DeviceContext->Device);
if (NT_SUCCESS(status)) {
DeviceContext->CreatedLegacyHardwareKey = TRUE;
}
status = QueueCreate(DeviceContext);
if (!NT_SUCCESS(status)) {
goto Exit;
}
Exit:
return status;
}
NTSTATUS
DeviceGetPdoName(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
NTSTATUS status;
WDFDEVICE device = DeviceContext->Device;
WDF_OBJECT_ATTRIBUTES attributes;
WDFMEMORY memory;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = device;
status = WdfDeviceAllocAndQueryProperty(
device,
DevicePropertyPhysicalDeviceObjectName,
NonPagedPoolNx,
&attributes,
&memory);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to query PDO name");
goto Exit;
}
DeviceContext->PdoName = (PWCHAR) WdfMemoryGetBuffer(memory, NULL);
Trace(TRACE_LEVEL_ERROR,
"PDO Name is %ws", DeviceContext->PdoName);
Exit:
return status;
}
NTSTATUS
DeviceWriteLegacyHardwareKey(
_In_ PWSTR PdoName,
_In_ PWSTR ComPort,
_In_ WDFDEVICE Device
)
{
WDFKEY key = NULL;
NTSTATUS status;
UNICODE_STRING pdoString = {0};
UNICODE_STRING comPort = {0};
DECLARE_CONST_UNICODE_STRING(deviceSubkey, SERIAL_DEVICE_MAP);
RtlInitUnicodeString(&pdoString, PdoName);
RtlInitUnicodeString(&comPort, ComPort);
status = WdfDeviceOpenDevicemapKey(Device,
&deviceSubkey,
KEY_SET_VALUE,
WDF_NO_OBJECT_ATTRIBUTES,
&key);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to open DEVICEMAP\\SERIALCOMM key 0x%x", status);
goto exit;
}
status = WdfRegistryAssignUnicodeString(key,
&pdoString,
&comPort);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to write to DEVICEMAP\\SERIALCOMM key 0x%x", status);
goto exit;
}
exit:
if (key != NULL) {
WdfRegistryClose(key);
key = NULL;
}
return status;
}
VOID
EvtDeviceCleanup(
_In_ WDFOBJECT Object
)
{
WDFDEVICE device = (WDFDEVICE) Object;
PDEVICE_CONTEXT deviceContext = GetDeviceContext(device);
NTSTATUS status;
WDFKEY key = NULL;
UNICODE_STRING pdoString = {0};
DECLARE_CONST_UNICODE_STRING(deviceSubkey, SERIAL_DEVICE_MAP);
if (deviceContext->CreatedLegacyHardwareKey == TRUE) {
RtlInitUnicodeString(&pdoString, deviceContext->PdoName);
status = WdfDeviceOpenDevicemapKey(device,
&deviceSubkey,
KEY_SET_VALUE,
WDF_NO_OBJECT_ATTRIBUTES,
&key);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to open DEVICEMAP\\SERIALCOMM key 0x%x", status);
goto exit;
}
status = WdfRegistryRemoveValue(key,
&pdoString);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to delete %S key, 0x%x", pdoString.Buffer, status);
goto exit;
}
status = WdfRegistryRemoveKey(key);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: Failed to delete %S, 0x%x", SERIAL_DEVICE_MAP, status);
goto exit;
}
}
exit:
if (key != NULL) {
WdfRegistryClose(key);
key = NULL;
}
return;
}
ULONG
GetBaudRate(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
return DeviceContext->BaudRate;
}
VOID
SetBaudRate(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ ULONG BaudRate
)
{
DeviceContext->BaudRate = BaudRate;
}
ULONG *
GetModemControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
return &DeviceContext->ModemControlRegister;
}
ULONG *
GetFifoControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
return &DeviceContext->FifoControlRegister;
}
ULONG *
GetLineControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
return &DeviceContext->LineControlRegister;
}
VOID
SetValidDataMask(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ UCHAR Mask
)
{
DeviceContext->ValidDataMask = Mask;
}
VOID
SetTimeouts(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ SERIAL_TIMEOUTS Timeouts
)
{
DeviceContext->Timeouts = Timeouts;
}
VOID
GetTimeouts(
_In_ PDEVICE_CONTEXT DeviceContext,
_Out_ SERIAL_TIMEOUTS *Timeouts
)
{
*Timeouts = DeviceContext->Timeouts;
}

View file

@ -1,122 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved
Module Name:
Device.h
Abstract:
This module contains the type definitions for the VirtualSerial sample
driver's device callback class.
Environment:
Windows Driver Framework
--*/
#pragma once
#define SYMBOLIC_LINK_NAME_LENGTH 32
#define SYMBOLIC_LINK_NAME_PREFIX L"\\DosDevices\\Global\\"
#define REG_PATH_DEVICEMAP L"HARDWARE\\DEVICEMAP"
#define SERIAL_DEVICE_MAP L"SERIALCOMM"
#define REG_VALUENAME_PORTNAME L"PortName"
#define REG_PATH_SERIALCOMM REG_PATH_DEVICEMAP L"\\" SERIAL_DEVICE_MAP
typedef struct _DEVICE_CONTEXT
{
WDFDEVICE Device;
ULONG BaudRate;
ULONG ModemControlRegister;
ULONG FifoControlRegister;
ULONG LineControlRegister;
UCHAR ValidDataMask;
SERIAL_TIMEOUTS Timeouts;
BOOLEAN CreatedLegacyHardwareKey;
PWSTR PdoName;
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext);
NTSTATUS
DeviceCreate(
_In_ WDFDRIVER Driver,
_In_ PWDFDEVICE_INIT DeviceInit,
_Out_ PDEVICE_CONTEXT *DeviceContext
);
NTSTATUS
DeviceConfigure(
_In_ PDEVICE_CONTEXT DeviceContext
);
NTSTATUS
DeviceGetPdoName(
_In_ PDEVICE_CONTEXT DeviceContext
);
NTSTATUS
DeviceWriteLegacyHardwareKey(
_In_ PWSTR PdoName,
_In_ PWSTR ComPort,
_In_ WDFDEVICE Device
);
EVT_WDF_DEVICE_CONTEXT_CLEANUP EvtDeviceCleanup;
ULONG
GetBaudRate(
_In_ PDEVICE_CONTEXT DeviceContext
);
VOID
SetBaudRate(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ ULONG BaudRate
);
ULONG *
GetModemControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
);
ULONG *
GetFifoControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
);
ULONG *
GetLineControlRegisterPtr(
_In_ PDEVICE_CONTEXT DeviceContext
);
VOID
SetValidDataMask(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ UCHAR Mask
);
VOID
SetTimeouts(
_In_ PDEVICE_CONTEXT DeviceContext,
_In_ SERIAL_TIMEOUTS Timeouts
);
VOID
GetTimeouts(
_In_ PDEVICE_CONTEXT DeviceContext,
_Out_ SERIAL_TIMEOUTS *Timeouts
);

View file

@ -1,72 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved.
Module Name:
Driver.c
Abstract:
This module contains the implementation of the VirtualSerial Sample's
core driver callback object.
Environment:
Windows Driver Framework
--*/
#include <initguid.h>
#include "internal.h"
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
WDF_DRIVER_CONFIG driverConfig;
WDF_DRIVER_CONFIG_INIT(&driverConfig,
EvtDeviceAdd);
status = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&driverConfig,
WDF_NO_HANDLE);
if (!NT_SUCCESS(status)) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfDriverCreate failed 0x%x", status);
return status;
}
return status;
}
NTSTATUS
EvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
PDEVICE_CONTEXT deviceContext;
status = DeviceCreate(Driver,
DeviceInit,
&deviceContext);
if (!NT_SUCCESS(status)) {
return status;
}
status = DeviceConfigure(deviceContext);
if (!NT_SUCCESS(status)) {
return status;
}
return status;
}

View file

@ -1,30 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved
Module Name:
Driver.h
Abstract:
This module contains the type definitions for the VirtualSerial sample's
driver callback class.
Environment:
Windows Driver Framework
--*/
#pragma once
//
// This class handles driver events for the VirtualSerial sample. In particular
// it supports the OnDeviceAdd event, which occurs when the driver is called
// to setup per-device handlers for a new device stack.
//
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd;

View file

@ -1,58 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved
Module Name:
Internal.h
Abstract:
This module contains the local type definitions for the VirtualSerial
driver sample.
Environment:
Windows Driver Framework
--*/
#pragma once
#ifdef _KERNEL_MODE
#include <ntddk.h>
#else
#include <windows.h>
#endif
#include <wdf.h>
#define _NTDEF_
//
// Include the type specific headers.
//
#include "serial.h"
#include "driver.h"
#include "device.h"
#include "ringbuffer.h"
#include "queue.h"
//
// Tracing and Assert
//
#define Trace(level, _fmt_, ...) \
DbgPrintEx(DPFLTR_DEFAULT_ID, level, \
_fmt_ "\n", __VA_ARGS__)
#define TRACE_LEVEL_ERROR DPFLTR_ERROR_LEVEL
#define TRACE_LEVEL_INFO DPFLTR_INFO_LEVEL
#ifndef ASSERT
#define ASSERT(exp) { \
if (!(exp)) { \
RtlAssert(#exp, __FILE__, __LINE__, NULL); \
} \
}
#endif

View file

@ -1,963 +0,0 @@
/*++
Copyright (c) Microsoft Corporation, All Rights Reserved
Module Name:
Queue.c
Abstract:
This file implements the I/O queue interface and performs
the read/write/ioctl operations.
Environment:
Windows Driver Framework
--*/
#include "internal.h"
NTSTATUS
QueueCreate(
_In_ PDEVICE_CONTEXT DeviceContext
)
{
NTSTATUS status;
WDFDEVICE device = DeviceContext->Device;
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_OBJECT_ATTRIBUTES queueAttributes;
WDFQUEUE queue;
PQUEUE_CONTEXT queueContext;
//
// Create the default queue
//
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchParallel);
queueConfig.EvtIoRead = EvtIoRead;
queueConfig.EvtIoWrite = EvtIoWrite;
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(
&queueAttributes,
QUEUE_CONTEXT);
status = WdfIoQueueCreate(
device,
&queueConfig,
&queueAttributes,
&queue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfIoQueueCreate failed 0x%x", status);
return status;
}
queueContext = GetQueueContext(queue);
queueContext->Queue = queue;
queueContext->DeviceContext = DeviceContext;
//
// Create a manual queue to hold pending read requests. By keeping
// them in the queue, framework takes care of cancelling them if the app
// exits
//
WDF_IO_QUEUE_CONFIG_INIT(
&queueConfig,
WdfIoQueueDispatchManual);
status = WdfIoQueueCreate(
device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfIoQueueCreate manual queue failed 0x%x", status);
return status;
}
queueContext->ReadQueue = queue;
//
// Create another manual queue to hold pending IOCTL_SERIAL_WAIT_ON_MASK
//
WDF_IO_QUEUE_CONFIG_INIT(
&queueConfig,
WdfIoQueueDispatchManual);
status = WdfIoQueueCreate(
device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfIoQueueCreate manual queue failed 0x%x", status);
return status;
}
queueContext->WaitMaskQueue = queue;
RingBufferInitialize(&queueContext->RingBuffer,
queueContext->Buffer,
sizeof(queueContext->Buffer));
return status;
}
NTSTATUS
RequestCopyFromBuffer(
_In_ WDFREQUEST Request,
_In_ PVOID SourceBuffer,
_In_ size_t NumBytesToCopyFrom
)
{
NTSTATUS status;
WDFMEMORY memory;
status = WdfRequestRetrieveOutputMemory(Request, &memory);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestRetrieveOutputMemory failed 0x%x", status);
return status;
}
status = WdfMemoryCopyFromBuffer(memory, 0,
SourceBuffer, NumBytesToCopyFrom);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfMemoryCopyFromBuffer failed 0x%x", status);
return status;
}
WdfRequestSetInformation(Request, NumBytesToCopyFrom);
return status;
}
NTSTATUS
RequestCopyToBuffer(
_In_ WDFREQUEST Request,
_In_ PVOID DestinationBuffer,
_In_ size_t NumBytesToCopyTo
)
{
NTSTATUS status;
WDFMEMORY memory;
status = WdfRequestRetrieveInputMemory(Request, &memory);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestRetrieveInputMemory failed 0x%x", status);
return status;
}
status = WdfMemoryCopyToBuffer(memory, 0,
DestinationBuffer, NumBytesToCopyTo);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfMemoryCopyToBuffer failed 0x%x", status);
return status;
}
WdfRequestSetInformation(Request, NumBytesToCopyTo);
return status;
}
VOID
EvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
NTSTATUS status;
PQUEUE_CONTEXT queueContext = GetQueueContext(Queue);
PDEVICE_CONTEXT deviceContext = queueContext->DeviceContext;
UNREFERENCED_PARAMETER (OutputBufferLength);
UNREFERENCED_PARAMETER (InputBufferLength);
Trace(TRACE_LEVEL_INFO,
"EvtIoDeviceControl 0x%x", IoControlCode);
switch (IoControlCode)
{
case IOCTL_SERIAL_SET_BAUD_RATE:
{
//
// This is a driver for a virtual serial port. Since there is no
// actual hardware, we just store the baud rate and don't do
// anything with it.
//
SERIAL_BAUD_RATE baudRateBuffer = {0};
status = RequestCopyToBuffer(Request,
&baudRateBuffer,
sizeof(baudRateBuffer));
if( NT_SUCCESS(status) ) {
SetBaudRate(deviceContext, baudRateBuffer.BaudRate);
};
break;
}
case IOCTL_SERIAL_GET_BAUD_RATE:
{
SERIAL_BAUD_RATE baudRateBuffer = {0};
baudRateBuffer.BaudRate = GetBaudRate(deviceContext);
status = RequestCopyFromBuffer(Request,
&baudRateBuffer,
sizeof(baudRateBuffer));
break;
}
case IOCTL_SERIAL_SET_MODEM_CONTROL:
{
//
// This is a driver for a virtual serial port. Since there is no
// actual hardware, we just store the modem control register
// configuration and don't do anything with it.
//
ULONG *modemControlRegister = GetModemControlRegisterPtr(deviceContext);
ASSERT(modemControlRegister);
status = RequestCopyToBuffer(Request,
modemControlRegister,
sizeof(ULONG));
break;
}
case IOCTL_SERIAL_GET_MODEM_CONTROL:
{
ULONG *modemControlRegister = GetModemControlRegisterPtr(deviceContext);
ASSERT(modemControlRegister);
status = RequestCopyFromBuffer(Request,
modemControlRegister,
sizeof(ULONG));
break;
}
case IOCTL_SERIAL_SET_FIFO_CONTROL:
{
//
// This is a driver for a virtual serial port. Since there is no
// actual hardware, we just store the FIFO control register
// configuration and don't do anything with it.
//
ULONG *fifoControlRegister = GetFifoControlRegisterPtr(deviceContext);
ASSERT(fifoControlRegister);
status = RequestCopyToBuffer(Request,
fifoControlRegister,
sizeof(ULONG));
break;
}
case IOCTL_SERIAL_GET_LINE_CONTROL:
{
status = QueueProcessGetLineControl(
queueContext,
Request);
break;
}
case IOCTL_SERIAL_SET_LINE_CONTROL:
{
status = QueueProcessSetLineControl(
queueContext,
Request);
break;
}
case IOCTL_SERIAL_GET_TIMEOUTS:
{
SERIAL_TIMEOUTS timeoutValues = {0};
status = RequestCopyFromBuffer(Request,
(void*) &timeoutValues,
sizeof(timeoutValues));
break;
}
case IOCTL_SERIAL_SET_TIMEOUTS:
{
SERIAL_TIMEOUTS timeoutValues = {0};
status = RequestCopyToBuffer(Request,
(void*) &timeoutValues,
sizeof(timeoutValues));
if( NT_SUCCESS(status) )
{
if ((timeoutValues.ReadIntervalTimeout == MAXULONG) &&
(timeoutValues.ReadTotalTimeoutMultiplier == MAXULONG) &&
(timeoutValues.ReadTotalTimeoutConstant == MAXULONG))
{
status = STATUS_INVALID_PARAMETER;
}
}
if( NT_SUCCESS(status) ) {
SetTimeouts(deviceContext, timeoutValues);
}
break;
}
case IOCTL_SERIAL_WAIT_ON_MASK:
{
//
// NOTE: A wait-on-mask request should not be completed until either:
// 1) A wait event occurs; or
// 2) A set-wait-mask request is received
//
// This is a driver for a virtual serial port. Since there is no
// actual hardware, we complete the request with some failure code.
//
WDFREQUEST savedRequest;
status = WdfIoQueueRetrieveNextRequest(
queueContext->WaitMaskQueue,
&savedRequest);
if (NT_SUCCESS(status)) {
WdfRequestComplete(savedRequest,
STATUS_UNSUCCESSFUL);
}
//
// Keep the request in a manual queue and the framework will take
// care of cancelling them when the app exits
//
status = WdfRequestForwardToIoQueue(
Request,
queueContext->WaitMaskQueue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestForwardToIoQueue failed 0x%x", status);
WdfRequestComplete(Request, status);
}
//
// Instead of "break", use "return" to prevent the current request
// from being completed.
//
return;
}
case IOCTL_SERIAL_SET_WAIT_MASK:
{
//
// NOTE: If a wait-on-mask request is already pending when set-wait-mask
// request is processed, the pending wait-on-event request is completed
// with STATUS_SUCCESS and the output wait event mask is set to zero.
//
WDFREQUEST savedRequest;
status = WdfIoQueueRetrieveNextRequest(
queueContext->WaitMaskQueue,
&savedRequest);
if (NT_SUCCESS(status)) {
ULONG eventMask = 0;
status = RequestCopyFromBuffer(
savedRequest,
&eventMask,
sizeof(eventMask));
WdfRequestComplete(savedRequest, status);
}
//
// NOTE: The application expects STATUS_SUCCESS for these IOCTLs.
//
status = STATUS_SUCCESS;
break;
}
case IOCTL_SERIAL_SET_QUEUE_SIZE:
case IOCTL_SERIAL_SET_DTR:
case IOCTL_SERIAL_SET_RTS:
case IOCTL_SERIAL_CLR_RTS:
case IOCTL_SERIAL_SET_XON:
case IOCTL_SERIAL_SET_XOFF:
case IOCTL_SERIAL_SET_CHARS:
case IOCTL_SERIAL_GET_CHARS:
case IOCTL_SERIAL_GET_HANDFLOW:
case IOCTL_SERIAL_SET_HANDFLOW:
case IOCTL_SERIAL_RESET_DEVICE:
//
// NOTE: The application expects STATUS_SUCCESS for these IOCTLs.
//
status = STATUS_SUCCESS;
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
//
// complete the request
//
WdfRequestComplete(Request, status);
}
VOID
EvtIoWrite(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t Length
)
{
NTSTATUS status;
PQUEUE_CONTEXT queueContext = GetQueueContext(Queue);
WDFMEMORY memory;
WDFREQUEST savedRequest;
size_t availableData = 0;
Trace(TRACE_LEVEL_INFO,
"EvtIoWrite 0x%p", Request);
status = WdfRequestRetrieveInputMemory(Request, &memory);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestRetrieveInputMemory failed 0x%x", status);
return;
}
//
// Process input
//
status = QueueProcessWriteBytes(
queueContext,
(PUCHAR)WdfMemoryGetBuffer(memory, NULL),
Length);
if( !NT_SUCCESS(status) ) {
return;
}
WdfRequestCompleteWithInformation(Request, status, Length);
//
// Get the amount of data available in the ring buffer
//
RingBufferGetAvailableData(
&queueContext->RingBuffer,
&availableData);
if (availableData == 0) {
return;
}
//
// Continue with the next request, if there is one pending
//
for ( ; ; ) {
status = WdfIoQueueRetrieveNextRequest(
queueContext->ReadQueue,
&savedRequest);
if (!NT_SUCCESS(status)) {
break;
}
status = WdfRequestForwardToIoQueue(
savedRequest,
Queue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestForwardToIoQueue failed 0x%x", status);
WdfRequestComplete(savedRequest, status);
}
}
}
VOID
EvtIoRead(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t Length
)
{
NTSTATUS status;
PQUEUE_CONTEXT queueContext = GetQueueContext(Queue);
WDFMEMORY memory;
size_t bytesCopied = 0;
Trace(TRACE_LEVEL_INFO,
"EvtIoRead 0x%p", Request);
status = WdfRequestRetrieveOutputMemory(Request, &memory);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestRetrieveOutputMemory failed 0x%x", status);
WdfRequestComplete(Request, status);
return;
}
status = RingBufferRead(&queueContext->RingBuffer,
(BYTE*)WdfMemoryGetBuffer(memory, NULL),
Length,
&bytesCopied);
if( !NT_SUCCESS(status) ) {
WdfRequestComplete(Request, status);
return;
}
if (bytesCopied > 0) {
//
// Data was read from buffer succesfully
//
WdfRequestCompleteWithInformation(Request, status, bytesCopied);
return;
}
else {
//
// No data to read. Queue the request for later processing.
//
status = WdfRequestForwardToIoQueue(Request,
queueContext->ReadQueue);
if( !NT_SUCCESS(status) ) {
Trace(TRACE_LEVEL_ERROR,
"Error: WdfRequestForwardToIoQueue failed 0x%x", status);
WdfRequestComplete(Request, status);
}
}
}
NTSTATUS
QueueProcessWriteBytes(
_In_ PQUEUE_CONTEXT QueueContext,
_In_reads_bytes_(Length)
PUCHAR Characters,
_In_ size_t Length
)
/*++
Routine Description:
This function is called when the framework receives IRP_MJ_WRITE
requests from the system. The write event handler(FmEvtIoWrite) calls ProcessWriteBytes.
It parses the Characters passed in and looks for the for sequences "AT" -ok ,
"ATA" --CONNECT, ATD<number> -- CONNECT and sets the state of the device appropriately.
These bytes are placed in the read Buffer to be processed later since this device
works in a loopback fashion.
Arguments:
Characters - Pointer to the write IRP's system buffer.
Length - Length of the IO operation
The default property of the queue is to not dispatch
zero lenght read & write requests to the driver and
complete is with status success. So we will never get
a zero length request.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
UCHAR currentCharacter;
UCHAR connectString[] = "\r\nCONNECT\r\n";
UCHAR connectStringCch = ARRAY_SIZE(connectString) - 1;
UCHAR okString[] = "\r\nOK\r\n";
UCHAR okStringCch = ARRAY_SIZE(okString) - 1;
while (Length != 0) {
currentCharacter = *(Characters++);
Length--;
if(currentCharacter == '\0') {
continue;
}
status = RingBufferWrite(&QueueContext->RingBuffer,
&currentCharacter,
sizeof(currentCharacter));
if( !NT_SUCCESS(status) ) {
return status;
}
switch (QueueContext->CommandMatchState) {
case COMMAND_MATCH_STATE_IDLE:
if ((currentCharacter == 'a') || (currentCharacter == 'A')) {
//
// got an A
//
QueueContext->CommandMatchState = COMMAND_MATCH_STATE_GOT_A;
QueueContext->ConnectCommand = FALSE;
QueueContext->IgnoreNextChar = FALSE;
}
break;
case COMMAND_MATCH_STATE_GOT_A:
if ((currentCharacter == 't') || (currentCharacter == 'T')) {
//
// got a T
//
QueueContext->CommandMatchState = COMMAND_MATCH_STATE_GOT_T;
}
else {
QueueContext->CommandMatchState = COMMAND_MATCH_STATE_IDLE;
}
break;
case COMMAND_MATCH_STATE_GOT_T:
if (! QueueContext->IgnoreNextChar) {
//
// the last char was not a special char
// check for CONNECT command
//
if ((currentCharacter == 'A') || (currentCharacter == 'a')) {
QueueContext->ConnectCommand = TRUE;
}
if ((currentCharacter == 'D') || (currentCharacter == 'd')) {
QueueContext->ConnectCommand = TRUE;
}
}
QueueContext->IgnoreNextChar = TRUE;
if (currentCharacter == '\r') {
//
// got a CR, send a response to the command
//
QueueContext->CommandMatchState = COMMAND_MATCH_STATE_IDLE;
if (QueueContext->ConnectCommand) {
//
// place <cr><lf>CONNECT<cr><lf> in the buffer
//
status = RingBufferWrite(&QueueContext->RingBuffer,
connectString,
connectStringCch);
if( !NT_SUCCESS(status) ) {
return status;
}
//
// connected now raise CD
//
QueueContext->CurrentlyConnected = TRUE;
QueueContext->ConnectionStateChanged = TRUE;
}
else {
//
// place <cr><lf>OK<cr><lf> in the buffer
//
status = RingBufferWrite(&QueueContext->RingBuffer,
okString,
okStringCch);
if( !NT_SUCCESS(status) ) {
return status;
}
}
}
break;
default:
break;
}
}
return status;
}
NTSTATUS
QueueProcessGetLineControl(
_In_ PQUEUE_CONTEXT QueueContext,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PDEVICE_CONTEXT deviceContext;
SERIAL_LINE_CONTROL lineControl = {0};
ULONG lineControlSnapshot;
ULONG *lineControlRegister;
deviceContext = QueueContext->DeviceContext;
lineControlRegister = GetLineControlRegisterPtr(deviceContext);
ASSERT(lineControlRegister);
//
// Take a snapshot of the line control register variable
//
lineControlSnapshot = *lineControlRegister;
//
// Decode the word length
//
if ((lineControlSnapshot & SERIAL_DATA_MASK) == SERIAL_5_DATA)
{
lineControl.WordLength = 5;
}
else if ((lineControlSnapshot & SERIAL_DATA_MASK) == SERIAL_6_DATA)
{
lineControl.WordLength = 6;
}
else if ((lineControlSnapshot & SERIAL_DATA_MASK) == SERIAL_7_DATA)
{
lineControl.WordLength = 7;
}
else if ((lineControlSnapshot & SERIAL_DATA_MASK) == SERIAL_8_DATA)
{
lineControl.WordLength = 8;
}
//
// Decode the parity
//
if ((lineControlSnapshot & SERIAL_PARITY_MASK) == SERIAL_NONE_PARITY)
{
lineControl.Parity = NO_PARITY;
}
else if ((lineControlSnapshot & SERIAL_PARITY_MASK) == SERIAL_ODD_PARITY)
{
lineControl.Parity = ODD_PARITY;
}
else if ((lineControlSnapshot & SERIAL_PARITY_MASK) == SERIAL_EVEN_PARITY)
{
lineControl.Parity = EVEN_PARITY;
}
else if ((lineControlSnapshot & SERIAL_PARITY_MASK) == SERIAL_MARK_PARITY)
{
lineControl.Parity = MARK_PARITY;
}
else if ((lineControlSnapshot & SERIAL_PARITY_MASK) == SERIAL_SPACE_PARITY)
{
lineControl.Parity = SPACE_PARITY;
}
//
// Decode the length of the stop bit
//
if (lineControlSnapshot & SERIAL_2_STOP)
{
if (lineControl.WordLength == 5)
{
lineControl.StopBits = STOP_BITS_1_5;
}
else
{
lineControl.StopBits = STOP_BITS_2;
}
}
else
{
lineControl.StopBits = STOP_BIT_1;
}
//
// Copy the information that was decoded to the caller's buffer
//
status = RequestCopyFromBuffer(Request,
(void*) &lineControl,
sizeof(lineControl));
return status;
}
NTSTATUS
QueueProcessSetLineControl(
_In_ PQUEUE_CONTEXT QueueContext,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PDEVICE_CONTEXT deviceContext;
SERIAL_LINE_CONTROL lineControl = {0};
ULONG *lineControlRegister;
UCHAR lineControlData = 0;
UCHAR lineControlStop = 0;
UCHAR lineControlParity = 0;
ULONG lineControlSnapshot;
ULONG lineControlNew;
ULONG lineControlPrevious;
ULONG i;
deviceContext = QueueContext->DeviceContext;
lineControlRegister = GetLineControlRegisterPtr(deviceContext);
ASSERT(lineControlRegister);
//
// This is a driver for a virtual serial port. Since there is no
// actual hardware, we just store the line control register
// configuration and don't do anything with it.
//
status = RequestCopyToBuffer(Request,
(void*) &lineControl,
sizeof(lineControl));
//
// Bits 0 and 1 of the line control register
//
if( NT_SUCCESS(status) )
{
switch (lineControl.WordLength)
{
case 5:
lineControlData = SERIAL_5_DATA;
SetValidDataMask(deviceContext, 0x1f);
break;
case 6:
lineControlData = SERIAL_6_DATA;
SetValidDataMask(deviceContext, 0x3f);
break;
case 7:
lineControlData = SERIAL_7_DATA;
SetValidDataMask(deviceContext, 0x7f);
break;
case 8:
lineControlData = SERIAL_8_DATA;
SetValidDataMask(deviceContext, 0xff);
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
}
//
// Bit 2 of the line control register
//
if( NT_SUCCESS(status) )
{
switch (lineControl.StopBits)
{
case STOP_BIT_1:
lineControlStop = SERIAL_1_STOP;
break;
case STOP_BITS_1_5:
if (lineControlData != SERIAL_5_DATA)
{
status = STATUS_INVALID_PARAMETER;
break;
}
lineControlStop = SERIAL_1_5_STOP;
break;
case STOP_BITS_2:
if (lineControlData == SERIAL_5_DATA)
{
status = STATUS_INVALID_PARAMETER;
break;
}
lineControlStop = SERIAL_2_STOP;
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
}
//
// Bits 3, 4 and 5 of the line control register
//
if( NT_SUCCESS(status) )
{
switch (lineControl.Parity)
{
case NO_PARITY:
lineControlParity = SERIAL_NONE_PARITY;
break;
case EVEN_PARITY:
lineControlParity = SERIAL_EVEN_PARITY;
break;
case ODD_PARITY:
lineControlParity = SERIAL_ODD_PARITY;
break;
case SPACE_PARITY:
lineControlParity = SERIAL_SPACE_PARITY;
break;
case MARK_PARITY:
lineControlParity = SERIAL_MARK_PARITY;
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
}
//
// Update our line control register variable atomically
//
i=0;
do {
i++;
if ((i & 0xf) == 0) {
//
// We've been spinning in a loop for a while trying to
// update the line control register variable atomically.
// Yield the CPU for other threads for a while.
//
#ifdef _KERNEL_MODE
LARGE_INTEGER interval;
interval.QuadPart = 0;
KeDelayExecutionThread(UserMode, FALSE, &interval);
#else
SwitchToThread();
#endif
}
lineControlSnapshot = *lineControlRegister;
lineControlNew = (lineControlSnapshot & SERIAL_LCR_BREAK) |
(lineControlData | lineControlParity | lineControlStop);
lineControlPrevious = InterlockedCompareExchange(
(LONG *) lineControlRegister,
lineControlNew,
lineControlSnapshot);
} while (lineControlPrevious != lineControlSnapshot);
return status;
}

View file

@ -1,113 +0,0 @@
/*++
Copyright (c) Microsoft Corporation, All Rights Reserved
Module Name:
queue.h
Abstract:
This file defines the queue callback interface.
Environment:
Windows Driver Framework
--*/
#pragma once
#include "internal.h"
// Set ring buffer size
#define DATA_BUFFER_SIZE 1024
//
// Device states
//
#define COMMAND_MATCH_STATE_IDLE 0
#define COMMAND_MATCH_STATE_GOT_A 1
#define COMMAND_MATCH_STATE_GOT_T 2
//
// Define useful macros
//
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#endif
#define MAXULONG 0xffffffff
typedef struct _QUEUE_CONTEXT
{
UCHAR CommandMatchState;
BOOLEAN ConnectCommand;
BOOLEAN IgnoreNextChar;
BOOLEAN ConnectionStateChanged;
BOOLEAN CurrentlyConnected;
RING_BUFFER RingBuffer; // Ring buffer for pending data
BYTE Buffer[DATA_BUFFER_SIZE];
WDFQUEUE Queue; // Default parallel queue
WDFQUEUE ReadQueue; // Manual queue for pending reads
WDFQUEUE WaitMaskQueue; // Manual queue for pending ioctl wait-on-mask
PDEVICE_CONTEXT DeviceContext;
} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, GetQueueContext);
EVT_WDF_IO_QUEUE_IO_READ EvtIoRead;
EVT_WDF_IO_QUEUE_IO_WRITE EvtIoWrite;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControl;
NTSTATUS
QueueCreate(
_In_ PDEVICE_CONTEXT DeviceContext
);
NTSTATUS
QueueProcessWriteBytes(
_In_ PQUEUE_CONTEXT QueueContext,
_In_reads_bytes_(Length)
PUCHAR Characters,
_In_ size_t Length
);
NTSTATUS
QueueProcessGetLineControl(
_In_ PQUEUE_CONTEXT QueueContext,
_In_ WDFREQUEST Request
);
NTSTATUS
QueueProcessSetLineControl(
_In_ PQUEUE_CONTEXT QueueContext,
_In_ WDFREQUEST Request
);
NTSTATUS
RequestCopyFromBuffer(
_In_ WDFREQUEST Request,
_In_ PVOID SourceBuffer,
_In_ size_t NumBytesToCopyFrom
);
NTSTATUS
RequestCopyToBuffer(
_In_ WDFREQUEST Request,
_In_ PVOID DestinationBuffer,
_In_ size_t NumBytesToCopyTo
);

View file

@ -1,338 +0,0 @@
/*++
Copyright (c) Microsoft Corporation, All Rights Reserved
Module Name:
RingBuffer.c
Abstract:
This file implements the Ring Buffer
Environment:
--*/
#include "internal.h"
VOID
RingBufferInitialize(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(BufferSize)
BYTE* Buffer,
_In_ size_t BufferSize
)
{
Self->Size = BufferSize;
Self->Base = Buffer;
Self->End = Buffer + BufferSize;
Self->Head = Buffer;
Self->Tail = Buffer;
}
VOID
RingBufferGetAvailableSpace(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableSpace
)
{
BYTE* headSnapshot = NULL;
BYTE* tailSnapshot = NULL;
BYTE* tailPlusOne = NULL;
ASSERT(AvailableSpace);
//
// Take a snapshot of the head and tail pointers. We will compute the
// available space based on this snapshot. This is safe to do in a
// single-producer, single-consumer model, because -
// * A producer will call GetAvailableSpace() to determine whether
// there is enough space to write the data it is trying to write.
// The only other thread that could modify the amount of space
// available is the consumer thread, which can only increase the
// amount of space available. Hence it is safe for the producer
// to write based on this snapshot.
// * A consumer thread will call GetAvailableSpace() to determine
// whether there is enough data in the buffer for it to read.
// (Available data = Buffer size - Available space). The only
// other thread that could modify the amount of space available
// is the producer thread, which can only decrease the amount of
// space available (thereby increasing the amount of data
// available. Hence it is safe for the consumer to read based on
// this snapshot.
//
headSnapshot = Self->Head;
tailSnapshot = Self->Tail;
//
// In order to distinguish between a full buffer and an empty buffer,
// we always leave the last byte of the buffer unused. So, an empty
// buffer is denoted by -
// tail == head
// ... and a full buffer is denoted by -
// (tail+1) == head
//
tailPlusOne = ((tailSnapshot+1) == Self->End) ? Self->Base : (tailSnapshot+1);
if (tailPlusOne == headSnapshot)
{
//
// Buffer full
//
*AvailableSpace = 0;
}
else if (tailSnapshot == headSnapshot)
{
//
// Buffer empty
// The -1 in the computation below is to account for the fact that
// we always leave the last byte of the ring buffer unused in order
// to distinguish between an empty buffer and a full buffer.
//
*AvailableSpace = Self->Size - 1;
}
else
{
if (tailSnapshot > headSnapshot)
{
//
// Data has not wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = Self->Size - (tailSnapshot - headSnapshot) - 1;
}
else
{
//
// Data has wrapped around the end of the buffer
// The -1 in the computation below is to account for the fact
// that we always leave the last byte of the ring buffer unused
// in order to distinguish between an empty buffer and a full
// buffer.
//
*AvailableSpace = (headSnapshot - tailSnapshot) - 1;
}
}
}
VOID
RingBufferGetAvailableData(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableData
)
{
size_t availableSpace;
ASSERT(AvailableData);
RingBufferGetAvailableSpace(Self, &availableSpace);
//
// The -1 in the arithmetic below accounts for the fact that we always
// keep 1 byte of the ring buffer unused in order to distinguish
// between a full buffer and an empty buffer.
//
*AvailableData = Self->Size - availableSpace - 1;
}
NTSTATUS
RingBufferWrite(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(DataSize)
BYTE* Data,
_In_ size_t DataSize
)
{
size_t availableSpace;
size_t bytesToCopy;
size_t spaceFromCurrToEnd;
ASSERT(Data && (0 != DataSize));
if (Self->Tail >= Self->End)
{
return STATUS_INTERNAL_ERROR;
}
//
// Get the amount of space available in the buffer
//
RingBufferGetAvailableSpace(Self, &availableSpace);
//
// If there is not enough space to fit in all the data passed in by the
// caller then copy as much as possible and throw away the rest
//
if (availableSpace < DataSize)
{
bytesToCopy = availableSpace;
}
else
{
bytesToCopy = DataSize;
}
if (bytesToCopy)
{
//
// The buffer has some space at least
//
if ((Self->Tail + bytesToCopy) > Self->End)
{
//
// The data being written will wrap around the end of the buffer.
// So the copy has to be done in two steps -
// * X bytes from current position to end of the buffer
// * the remaining (bytesToCopy - X) from the start of the buffer
//
//
// The first step of the copy ...
//
spaceFromCurrToEnd = Self->End - Self->Tail;
RtlCopyMemory(Self->Tail, Data, spaceFromCurrToEnd);
Data += spaceFromCurrToEnd;
bytesToCopy -= spaceFromCurrToEnd;
//
// The second step of the copy ...
//
RtlCopyMemory(Self->Base, Data, bytesToCopy);
//
// Advance the tail pointer
//
Self->Tail = Self->Base + bytesToCopy;
}
else
{
//
// Data does NOT wrap around the end of the buffer. Just copy it
// over in a single step
//
RtlCopyMemory(Self->Tail, Data, bytesToCopy);
//
// Advance the tail pointer
//
Self->Tail += bytesToCopy;
if (Self->Tail == Self->End)
{
//
// We have exactly reached the end of the buffer. The next
// write should wrap around and start from the beginning.
//
Self->Tail = Self->Base;
}
}
ASSERT(Self->Tail < Self->End);
}
return STATUS_SUCCESS;
}
NTSTATUS
RingBufferRead(
_In_ PRING_BUFFER Self,
_Out_writes_bytes_to_(DataSize, *BytesCopied)
BYTE* Data,
_In_ size_t DataSize,
_Out_ size_t *BytesCopied
)
{
size_t availableData;
size_t dataFromCurrToEnd;
ASSERT(Data && (DataSize != 0));
if (Self->Head >= Self->End)
{
return STATUS_INTERNAL_ERROR;
}
//
// Get the amount of data available in the buffer
//
RingBufferGetAvailableData(Self, &availableData);
if (availableData == 0)
{
*BytesCopied = 0;
return STATUS_SUCCESS;
}
if (DataSize > availableData)
{
DataSize = availableData;
}
*BytesCopied = DataSize;
if ((Self->Head + DataSize) > Self->End)
{
//
// The data requested by the caller is wrapped around the end of the
// buffer. So we'll do the copy in two steps -
// * Copy X bytes from the current position to the end buffer into
// the caller's buffer
// * Copy (DataSize - X) bytes from the beginning to the buffer into
// the caller's buffer
//
//
// The first step of the copy ...
//
dataFromCurrToEnd = Self->End - Self->Head;
RtlCopyMemory(Data, Self->Head, dataFromCurrToEnd);
Data += dataFromCurrToEnd;
DataSize -= dataFromCurrToEnd;
//
// The second step of the copy ...
//
RtlCopyMemory(Data, Self->Base, DataSize);
//
// Advance the head pointer
//
Self->Head = Self->Base + DataSize;
}
else
{
//
// The data in the buffer is NOT wrapped around the end of the buffer.
// Simply copy the data over to the caller's buffer in a single step.
//
RtlCopyMemory(Data, Self->Head, DataSize);
//
// Advance the head pointer
//
Self->Head += DataSize;
if (Self->Head == Self->End)
{
//
// We have exactly reached the end of the buffer. The next
// read should wrap around and start from the beginning.
//
Self->Head = Self->Base;
}
}
ASSERT(Self->Head < Self->End);
return STATUS_SUCCESS;
}

View file

@ -1,117 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved
Module Name:
Ringbuffer.h
--*/
#pragma once
typedef struct _RING_BUFFER
{
//
// The size in bytes of the ring buffer.
//
size_t Size;
//
// A pointer to the base of the ring buffer.
//
BYTE* Base;
//
// A pointer to the byte beyond the end of the ring buffer. Used for
// quick comparisons when determining if we need to wrap.
//
BYTE* End;
//
// A pointer to the current read point in the ring buffer.
//
// Updates to this are not protected by any lock. This is different from
// the write pointer, which is protected by the "pending read pointer"
// lock. The reason for this difference is that in this driver, we do not
// keep write requests pending. If there is not enough space to write all
// the data that was requested, we write as much as we can and drop the
// rest (lossy data transfer).
//
// If we had multiple threads modifying this pointer, then that would
// provide yet another reason for protecting updates to the pointer using a
// lock. However, in this driver, at any given time we have only one thread
// that modifies this pointer (the thread that runs the read callback).
// This is true because we use a sequential queue for read requests. If we
// were to change our read queue to be a parallel queue, this would no
// longer be true.
//
//
BYTE* Head;
//
// A pointer to the current write point in the ring buffer.
//
// Updates to this pointer are protected by the "pending read pointer
// lock", because we do not want a consumer thread to mark a read request
// as pending while we are in the process of writing data to the buffer.
// The reason is that the write that we are currently performing might
// actually supply enough data to satisfy the read request, in which case
// it should not be marked pending at all.
// If the read request were to be marked pending in the situation described
// above, then we would need some trigger to later retrieve the request and
// complete it. In our driver, arrival of data is the only event that can
// trigger this. So if no more data arrives, the request will remain
// pending forever, even though there is enough data in the buffer to
// complete it. Hence we do not keep a read request pending in situations
// where the read buffer contains enough data to satisfy it.
//
// If we had multiple threads modifying this pointer, then that would
// provide yet another reason for protecting updates to the pointer using a
// lock. However, in this driver, at any given time we have only one thread
// that modifies this pointer (the thread that runs the write callback).
// This is true because we use a sequential queue for write requests. If we
// were to change our write queue to be a parallel queue, this would no
// longer be true.
//
BYTE* Tail;
} RING_BUFFER, *PRING_BUFFER;
VOID
RingBufferInitialize(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(BufferSize)
BYTE* Buffer,
_In_ size_t BufferSize
);
NTSTATUS
RingBufferWrite(
_In_ PRING_BUFFER Self,
_In_reads_bytes_(DataSize)
BYTE* Data,
_In_ size_t DataSize
);
NTSTATUS
RingBufferRead(
_In_ PRING_BUFFER Self,
_Out_writes_bytes_to_(DataSize, *BytesCopied)
BYTE* Data,
_In_ size_t DataSize,
_Out_ size_t *BytesCopied
);
VOID
RingBufferGetAvailableSpace(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableSpace
);
VOID
RingBufferGetAvailableData(
_In_ PRING_BUFFER Self,
_Out_ size_t *AvailableData
);

View file

@ -1,128 +0,0 @@
/*++
Copyright (C) Microsoft Corporation, All Rights Reserved
Module Name:
Serial.h
Abstract:
Type definitions and data for the serial port driver
--*/
#pragma once
//
// This defines the bit used to control whether the device is sending
// a break. When this bit is set the device is sending a space (logic 0).
//
// Most protocols will assume that this is a hangup.
//
#define SERIAL_LCR_BREAK 0x40
//
// These defines are used to set the line control register.
//
#define SERIAL_5_DATA ((UCHAR)0x00)
#define SERIAL_6_DATA ((UCHAR)0x01)
#define SERIAL_7_DATA ((UCHAR)0x02)
#define SERIAL_8_DATA ((UCHAR)0x03)
#define SERIAL_DATA_MASK ((UCHAR)0x03)
#define SERIAL_1_STOP ((UCHAR)0x00)
#define SERIAL_1_5_STOP ((UCHAR)0x04) // Only valid for 5 data bits
#define SERIAL_2_STOP ((UCHAR)0x04) // Not valid for 5 data bits
#define SERIAL_STOP_MASK ((UCHAR)0x04)
#define SERIAL_NONE_PARITY ((UCHAR)0x00)
#define SERIAL_ODD_PARITY ((UCHAR)0x08)
#define SERIAL_EVEN_PARITY ((UCHAR)0x18)
#define SERIAL_MARK_PARITY ((UCHAR)0x28)
#define SERIAL_SPACE_PARITY ((UCHAR)0x38)
#define SERIAL_PARITY_MASK ((UCHAR)0x38)
#ifdef _KERNEL_MODE
#include <ntddser.h>
#else
////////////////////////////////////////////////////////////////////////////////
//
// Instead of #include <ntddser.h>, the following are copied from that header,
// as ntddser.h is conflicted with winioctl.h which is included from wdf.h
//
////////////////////////////////////////////////////////////////////////////////
#define IOCTL_SERIAL_SET_BAUD_RATE CTL_CODE(FILE_DEVICE_SERIAL_PORT, 1,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_QUEUE_SIZE CTL_CODE(FILE_DEVICE_SERIAL_PORT, 2,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_LINE_CONTROL CTL_CODE(FILE_DEVICE_SERIAL_PORT, 3,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_BREAK_ON CTL_CODE(FILE_DEVICE_SERIAL_PORT, 4,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_BREAK_OFF CTL_CODE(FILE_DEVICE_SERIAL_PORT, 5,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_IMMEDIATE_CHAR CTL_CODE(FILE_DEVICE_SERIAL_PORT, 6,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_TIMEOUTS CTL_CODE(FILE_DEVICE_SERIAL_PORT, 7,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_TIMEOUTS CTL_CODE(FILE_DEVICE_SERIAL_PORT, 8,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_DTR CTL_CODE(FILE_DEVICE_SERIAL_PORT, 9,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_CLR_DTR CTL_CODE(FILE_DEVICE_SERIAL_PORT,10,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_RESET_DEVICE CTL_CODE(FILE_DEVICE_SERIAL_PORT,11,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_RTS CTL_CODE(FILE_DEVICE_SERIAL_PORT,12,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_CLR_RTS CTL_CODE(FILE_DEVICE_SERIAL_PORT,13,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_XOFF CTL_CODE(FILE_DEVICE_SERIAL_PORT,14,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_XON CTL_CODE(FILE_DEVICE_SERIAL_PORT,15,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_WAIT_MASK CTL_CODE(FILE_DEVICE_SERIAL_PORT,16,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_WAIT_MASK CTL_CODE(FILE_DEVICE_SERIAL_PORT,17,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_WAIT_ON_MASK CTL_CODE(FILE_DEVICE_SERIAL_PORT,18,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_PURGE CTL_CODE(FILE_DEVICE_SERIAL_PORT,19,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_BAUD_RATE CTL_CODE(FILE_DEVICE_SERIAL_PORT,20,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_LINE_CONTROL CTL_CODE(FILE_DEVICE_SERIAL_PORT,21,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_CHARS CTL_CODE(FILE_DEVICE_SERIAL_PORT,22,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_CHARS CTL_CODE(FILE_DEVICE_SERIAL_PORT,23,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_HANDFLOW CTL_CODE(FILE_DEVICE_SERIAL_PORT,24,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_HANDFLOW CTL_CODE(FILE_DEVICE_SERIAL_PORT,25,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_MODEMSTATUS CTL_CODE(FILE_DEVICE_SERIAL_PORT,26,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_COMMSTATUS CTL_CODE(FILE_DEVICE_SERIAL_PORT,27,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_XOFF_COUNTER CTL_CODE(FILE_DEVICE_SERIAL_PORT,28,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_PROPERTIES CTL_CODE(FILE_DEVICE_SERIAL_PORT,29,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_DTRRTS CTL_CODE(FILE_DEVICE_SERIAL_PORT,30,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_GET_MODEM_CONTROL CTL_CODE(FILE_DEVICE_SERIAL_PORT,37,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_MODEM_CONTROL CTL_CODE(FILE_DEVICE_SERIAL_PORT,38,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_SERIAL_SET_FIFO_CONTROL CTL_CODE(FILE_DEVICE_SERIAL_PORT,39,METHOD_BUFFERED,FILE_ANY_ACCESS)
typedef struct _SERIAL_BAUD_RATE {
ULONG BaudRate;
} SERIAL_BAUD_RATE,*PSERIAL_BAUD_RATE;
typedef struct _SERIAL_LINE_CONTROL {
UCHAR StopBits;
UCHAR Parity;
UCHAR WordLength;
} SERIAL_LINE_CONTROL,*PSERIAL_LINE_CONTROL;
typedef struct _SERIAL_TIMEOUTS {
ULONG ReadIntervalTimeout;
ULONG ReadTotalTimeoutMultiplier;
ULONG ReadTotalTimeoutConstant;
ULONG WriteTotalTimeoutMultiplier;
ULONG WriteTotalTimeoutConstant;
} SERIAL_TIMEOUTS,*PSERIAL_TIMEOUTS;
#define STOP_BIT_1 0
#define STOP_BITS_1_5 1
#define STOP_BITS_2 2
#define NO_PARITY 0
#define ODD_PARITY 1
#define EVEN_PARITY 2
#define MARK_PARITY 3
#define SPACE_PARITY 4
#endif // #ifdef _KERNEL_MODE, #include <ntddser.h>
//
// DEFINE_GUID(GUID_DEVINTERFACE_MODEM, 0x2c7089aa, 0x2e0e, 0x11d1, 0xb1, 0x14, 0x00, 0xc0, 0x4f, 0xc2, 0xaa, 0xe4);
//
#include <ntddmodm.h>