c# - 获取蓝牙 COM 端口
问题描述
我正在尝试访问有关蓝牙串行端口的特定信息。我能够在我的蓝牙设置中找到这个窗口,它显示了蓝牙设备的端口、方向和名称(如果它与 COM 端口相关)。
目前为了尝试获取这些信息,我一直在使用 WQL 来查询一些 Windows 管理类。
# I don't really mind if it is run in a Powershell environment
gwmi -query "SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%' AND PNPClass = 'Ports'"
//or a C# environment
ManagementObjectCollection results = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE 'USB%' AND PNPClass = 'Ports'").Get();
#This is a lot slower but it gets a bit more information about the serial ports
gwmi -query "SELECT * FROM Win32_SerialPort WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%'"
但是,下面的查询不包括名称(如屏幕截图中所示)和 COM 端口的方向。是否可以使用 WQL 获取此信息?
解决方案
虽然我不确定这是否可以用 WQL 100% 完成,但我能够用 C# 编写一个小程序来处理它。
它通过在某些 Win32_PnPEntity 属性中查找模式来工作。
它从所有存储的蓝牙设备中提取一个标识符,并将该标识符与 COM 端口上的相同标识符进行比较。如果它找到它,它已经找到了传出 COM 端口。
传出端口和传入端口都具有相似的硬件 ID,其中第一部分相同。使用此信息,程序然后识别传入的 COM 端口(如果存在)。
要以问题中图像中显示的格式获取它,可以运行查询DEVPKEY_Device_BusReportedDeviceDesc
传出端口的 powershell 命令。这是'Dev B'
传出端口上名称的一部分。
这一切都是异步完成的,因此在找到结果时会填充列表。
XAML
<Window x:Class="BluetoothInformation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BluetoothInformation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:DirectionConverter x:Key="directionConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding COMPorts}"
CanUserAddRows="False"
ColumnWidth="*"
AutoGenerateColumns="False"
VerticalScrollBarVisibility="Visible"
Background="Transparent"
RowBackground="Transparent"
IsReadOnly="True">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Focusable"
Value="False"/>
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="Background"
Value="Transparent" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="COM Port"
Binding="{Binding COMPortPort}"/>
<DataGridTextColumn Header="Direction"
Binding="{Binding COMPortDirection, Converter={StaticResource directionConverter}}"/>
<DataGridTextColumn Header="Name"
Binding="{Binding COMPortName}"/>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1"
Content="Refresh"
IsEnabled="{Binding CanRefresh}"
Click="Button_Click"/>
</Grid>
</Window>
C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Management;
using System.Management.Automation;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace BluetoothInformation
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<COMPort> comPorts = new ObservableCollection<COMPort>();
public ObservableCollection<COMPort> COMPorts {
get => comPorts;
set {
comPorts = value;
RaisePropertyChanged();
}
}
private bool canRefresh = false;
public bool CanRefresh {
get => canRefresh;
set {
canRefresh = value;
RaisePropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
GetBluetoothCOMPort();
}
private string ExtractBluetoothDevice(string pnpDeviceID)
{
int startPos = pnpDeviceID.LastIndexOf('_') + 1;
return pnpDeviceID.Substring(startPos);
}
private string ExtractDevice(string pnpDeviceID)
{
int startPos = pnpDeviceID.LastIndexOf('&') + 1;
int length = pnpDeviceID.LastIndexOf('_') - startPos;
return pnpDeviceID.Substring(startPos, length);
}
private string ExtractCOMPortFromName(string name)
{
int openBracket = name.IndexOf('(');
int closeBracket = name.IndexOf(')');
return name.Substring(openBracket + 1, closeBracket - openBracket - 1);
}
private string ExtractHardwareID(string fullHardwareID)
{
int length = fullHardwareID.LastIndexOf('_');
return fullHardwareID.Substring(0, length);
}
private bool TryFindPair(string pairsName, string hardwareID, List<ManagementObject> bluetoothCOMPorts, out COMPort comPort)
{
foreach (ManagementObject bluetoothCOMPort in bluetoothCOMPorts)
{
string itemHardwareID = ((string[])bluetoothCOMPort["HardwareID"])[0];
if (hardwareID != itemHardwareID && ExtractHardwareID(hardwareID) == ExtractHardwareID(itemHardwareID))
{
comPort = new COMPort(ExtractCOMPortFromName(bluetoothCOMPort["Name"].ToString()), Direction.INCOMING, pairsName);
return true;
}
}
comPort = null;
return false;
}
private string GetDataBusName(string pnpDeviceID)
{
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript($@"Get-PnpDeviceProperty -InstanceId '{pnpDeviceID}' -KeyName 'DEVPKEY_Device_BusReportedDeviceDesc' | select-object Data");
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
Console.WriteLine(outputItem.BaseObject.GetType().FullName);
foreach (var p in outputItem.Properties)
{
if (p.Name == "Data")
{
return p.Value?.ToString();
}
}
}
}
}
return string.Empty;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
GetBluetoothCOMPort();
}
private async void GetBluetoothCOMPort()
{
CanRefresh = false;
COMPorts.Clear();
await Task.Run(() => {
ManagementObjectCollection results = new ManagementObjectSearcher(@"SELECT PNPClass, PNPDeviceID, Name, HardwareID FROM Win32_PnPEntity WHERE (Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%' AND PNPClass = 'Ports') OR (PNPClass = 'Bluetooth' AND PNPDeviceID LIKE '%BTHENUM\\DEV%')").Get();
List<ManagementObject> bluetoothCOMPorts = new List<ManagementObject>();
List<ManagementObject> bluetoothDevices = new List<ManagementObject>();
foreach (ManagementObject queryObj in results)
{
if (queryObj["PNPClass"].ToString() == "Bluetooth")
{
bluetoothDevices.Add(queryObj);
}
else if (queryObj["PNPClass"].ToString() == "Ports")
{
bluetoothCOMPorts.Add(queryObj);
}
}
foreach (ManagementObject bluetoothDevice in bluetoothDevices)
{
foreach (ManagementObject bluetoothCOMPort in bluetoothCOMPorts)
{
string comPortPNPDeviceID = bluetoothCOMPort["PNPDeviceID"].ToString();
if (ExtractBluetoothDevice(bluetoothDevice["PNPDeviceID"].ToString()) == ExtractDevice(comPortPNPDeviceID))
{
COMPort outgoingPort = new COMPort(ExtractCOMPortFromName(bluetoothCOMPort["Name"].ToString()), Direction.OUTGOING, $"{bluetoothDevice["Name"].ToString()} \'{GetDataBusName(comPortPNPDeviceID)}\'");
Dispatcher.Invoke(() => {
COMPorts.Add(outgoingPort);
});
if (TryFindPair(bluetoothDevice["Name"].ToString(), ((string[])bluetoothCOMPort["HardwareID"])[0], bluetoothCOMPorts, out COMPort incomingPort))
{
Dispatcher.Invoke(() => {
COMPorts.Add(incomingPort);
});
}
}
}
}
});
CanRefresh = true;
}
}
public class COMPort : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string comPortPort;
public string COMPortPort {
get => comPortPort;
set {
comPortPort = value;
RaisePropertyChanged();
}
}
private Direction comPortDirection;
public Direction COMPortDirection {
get => comPortDirection;
set {
comPortDirection = value;
RaisePropertyChanged();
}
}
private string comPortName;
public string COMPortName {
get => comPortName;
set {
comPortName = value;
RaisePropertyChanged();
}
}
public COMPort(string comPortPort, Direction comPortDirection, string comPortName)
{
COMPortPort = comPortPort;
COMPortDirection = comPortDirection;
COMPortName = comPortName;
}
}
[ValueConversion(typeof(Direction), typeof(string))]
public class DirectionConverter : IValueConverter
{
private const string UNDEFINED_DIRECTION = "UNDEFINED";
private const string INCOMING_DIRECTION = "Incoming";
private const string OUTGOING_DIRECTION = "Outgoing";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((Direction)value)
{
case Direction.UNDEFINED:
return UNDEFINED_DIRECTION;
case Direction.INCOMING:
return INCOMING_DIRECTION;
case Direction.OUTGOING:
return OUTGOING_DIRECTION;
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum Direction
{
UNDEFINED,
INCOMING,
OUTGOING
}
}
推荐阅读
- mysql - 库存系统的复杂 SQL 查询
- javascript - 使用 featherjs 设置生产
- java - 如何通过反射初始化类并指定类的泛型类型
- php - Codeigniter 检查编辑功能上的重复项
- php - PHP:AdjacentElementsProduct - CodeFights https://app.codesignal.com/arcade/intro/level-2/
- c# - 制作、保存和显示 bmp
- sql - 动态地将日期行旋转到列标题中,并为每个日期重复未透视的列
- google-docs-api - 我希望能够使用 API 以编程方式将 Google 演示文稿中的幻灯片插入(链接)到 Google 文档中
- scala - 解析搜索表达式上的括号 - Scala 解析器组合器
- python - 无法将寡妇时代时间转换为正常日期时间