Windows 上的蓝牙编程并不方便,由于操作系统并没有提供统一的蓝牙操作接口,通常是由各个蓝牙设备商提供蓝牙栈,所以要想编程兼容这些不同的厂商蓝牙是一个问题。好在有个软件项目 32feet.NET 针对主流蓝牙协议栈提供了支持,包括 Microsfot, Widcomm, BlueSolei 等,同时还支持红外传输协议。

蓝牙虚拟串口是一个较为常见的需求,为了兼容已有使用串口设备的程序,需要将蓝牙连接转为系统上的虚拟串口,然后提供给其他程序或库使用。32feet.NET 对此也提供了支持。

32feet.NET 依赖 .NET 3.5 版本以上框架,支持 Windows 桌面版本、Wndows CE 以及 Windows Phone。

安装

如果使用 Visual Studio 2015 或者 安装有 NuGet 工具的,可以直接通过 NuGet 安装。在 NuGet 命令行中输入

1

Install-Package 32feet.NET

这样 NuGet 会自动下载安装并添加到当前 .NET 项目中。可以检查项目 References 项,如果存在 InTheHand.Net.Personal 则表明已成功加入到项目中,如果没有,可以手动添加引用,NuGet 下载存放在 SolutionName\packages\32feet.NET.x.x.x.x\ 路径下。

使用

下面主要讲解 32feet.NET 的使用,覆盖蓝牙搜索、配对、直接连接以及虚拟串口服务。

1. 检测系统蓝牙可用性

如果系统没有蓝牙设备或者蓝牙设备被禁用,那么可以通过以下函数来进行检查:

123456789101112131415

/* 定义于类 BTHelper 中 */

public static bool IsPlatformSupportBT()

{

BluetoothClient bc;

try

{

bc = new BluetoothClient();

}

catch (PlatformNotSupportedException)

{

return false;

}

bc.Close();

return true;

}

如果系统不支持蓝牙设备,那么 new BluetoothClient 会抛出 PlatformNotSupportedException 异常,通过捕获这个异常,来检测系统蓝牙设备可用性。

2. 蓝牙搜索

BluetoothClient 对象有个方法 DiscoverDevices 用于搜索蓝牙设备,该方法有多个重载版本,最终都是调用如下这个接口:

1

public BluetoothDeviceInfo[] DiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly);

第一个参数表明搜索的最大设备数,第二个参数表示是否搜索已配对设备,第三个表示是否搜索记住的设备,第四个表示是否搜索未知设备,第五个参数表示是搜索范围内可被发现的设备。

这里面重要的是第二和第五个参数,第二个代表搜索系统中已配对列表中的设备,即使它们现在并不在线。第五个参数 XP 系统上不支持,表示搜索范围内可被发现的设备。

如果我们需要获得系统中已配对列表中的蓝牙设备,可以这样调用 DiscoverDevices(255, true, false, false),这里使用了4个参数的重载版本,只将第二个参数置为 true。

如果我们需要搜索周围环境中可用的蓝牙设备,可以这样调用 DiscoverDevices(255, false, false, false, true),这个调用等同于 DiscoverDevicesInRange()。

需要注意的是,这个调用是同步阻塞的,在搜索没有结束之前函数不会返回。所以通常我们需要将这个调用放入工作线程中。例外的是,如果只是获取系统中已配对列表中的设备,这个调用会很快完成,不会占用当前线程太多时间。

库中同时提供了一个异步搜索方法,由类 BluetoothComponent 提供,由事件 DiscoverDevicesProgress 和 事件 DiscoverDevicesComplete 以及方法 DiscoverDevicesAsync 来实现。这个和用 BackgroundWorker 来实现 DiscoverDevices() 异步查找是一样的。

PS: 可以通过 VS 的 Object Browser 查看 InTheHand.Net.Personal 库中的函数接口说明,说明非常翔实。

3. 检测蓝牙设备是否在范围内

当需要检查一个蓝牙设备是否在有效范围内,可以通过查询一个 Fake Service ID 来实现。

如果蓝牙设备在范围内可访问,那么查询的结果是返回的服务记录为空,表示不支持此服务;如果蓝牙设备不在范围内,那么会抛出套接字异常。

以下为检测代码:

12345678910111213141516171819

/* 定义于类 BTHelper 中 */

public static bool TestingIfInRange(BluetoothAddress addr)

{

bool inRange = false;

Guid fakeUuid = new Guid("{F13F471D-47CB-41d6-9609-BAD0690BF891}");

BluetoothDeviceInfo device = new BluetoothDeviceInfo(addr);

try

{

ServiceRecord[] records = device.GetServiceRecords(fakeUuid);

Debug.Assert(records.Length == 0, "Why are we getting any records? len: " + records.Length);

inRange = true;

}

catch (SocketException)

{

inRange = false;

}

return inRange;

}

需要注意的是,因为蓝牙设备通信需要时间,所以这个调用也需要较长时间才能完成,未完成之前进入阻塞不返回,所以这个方法也需要放入工作线程中执行。

4. 蓝牙设备配对

蓝牙配对功能由类 BluetoothSecurity 的静态方法 PairRequest(BluetoothAddress device, string pin) 提供,其中第一个参数是目标设备地址,第二个参数是用于配对的 Pin 码。配对成功返回 true,失败返回 false。

解除配对是 BluetoothSecurity.RemoveDevice(BluetoothAddress device) 。

配对操作也需要将长时间完成,所以这个方法也需要放入工作线程中执行。

5. 蓝牙直接连接

如果不需要蓝牙虚拟串口而直接读写蓝牙数据,那么可以使用直接连接,这个可以参照官方文档:General Bluetooth Data Connections.

通过直接连接获取一个可读写的 System.IO.Stream 流对象,就可以直接对蓝牙进行读写数据操作了。以下为官方样例中的代码

1234567891011

BluetoothAddress addr = BluetoothAddress.Parse("001122334455");

Guid serviceClass;

serviceClass = BluetoothService.SerialPort;

// - or - etc// serviceClass = MyConsts.MyServiceUuid//var ep = new BluetoothEndPoint(addr, serviceClass);

var cli = new BluetoothClient();

cli.Connect(ep);

Stream peerStream = cli.GetStream();

// peerStream.Write/Read ...

6. 蓝牙虚拟串口

这部分讲解如何实现将蓝牙连接转为系统上的虚拟串口并获取串口名,还可以参阅官方文档: Bluetooth Serial Ports.

首先需要说明的是,虚拟串口和直接连接数据读写不能同时使用,如果已经使用了其中一个方法进行读写,那么另外的一个方法会失败。

生成虚拟串口需要将蓝牙设备服务设置为 BluetoothService.SerialPort,官方样例代码如下:

1234

BluetoothAddress addr = BluetoothAddress.Parse("123456789012");

BluetoothDeviceInfo device = new BluetoothDeviceInfo(addr); // Or from discovery etcbool state = true;

device.SetServiceState(BluetoothService.SerialPort, state, true);

SetServiceState 的第一个函数表示服务标识,第二个表示要设置的服务状态,true 为启用, false 为禁用,第三个参数表示如果设置失败是否抛出异常。

但在实际使用中,某些双模蓝牙在设置时,虽然设置成功但是依然会抛出 Win32Exception 异常,所以这里建议调用此函数的重载版本 SetServiceState(System.Guid service, bool state),而想要知道是否设置成功,下面会介绍其他方法来获得。

这个方法调用并不会告诉我们新生成的串口名,官方文档中的建议是通过设置前后的系统串口列表差异来获取新生成的串口名,而获取系统串口列表则可以调用静态方法 SerialPort.GetPortNames。

但是不建议用这个方法来获取虚拟串口名,因为这个不可靠而且容易出错,下面介绍另外一个可靠方法,而且这个方法也可以同时告诉我们设置虚拟串口服务是否成功。

获取蓝牙虚拟串口名

蓝牙虚拟串口在系统中都有记录,我们可以通过检索这个记录,来找到所设置的设备的虚拟串口名,同时也可以得知我们的蓝牙虚拟串口是否设置成功。

通过调用 WMI 查询,可以枚举出系统中每个串口的详细信息。这里可以通过 PowerShell 来查询:

1

C:\> Get-WmiObject -query "select DeviceID,PNPDeviceID from Win32_SerialPort"

输出样例(其中 COM66 对应的蓝牙设备地址为 00803A686519):

1234

DeviceID : COM66

PNPDeviceID : BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}\7&1D80ECD3&0&00803A686519_C00000003

......

可以看出蓝牙虚拟串口的 PNPDeviceID 是以 BTHENUM 开头,并且会将蓝牙地址存放其中。

这个查询也可以通过 C# 代码实现:

12345678910111213

using System.Management;

const string Win32_SerialPort = "Win32_SerialPort";

SelectQuery q = new SelectQuery(Win32_SerialPort);

ManagementObjectSearcher s = new ManagementObjectSearcher(q);

foreach (object cur in s.Get()) {

ManagementObject mo = (ManagementObject)cur;

object id = mo.GetPropertyValue("DeviceID");

object pnpId = mo.GetPropertyValue("PNPDeviceID");

console.WriteLine("DeviceID: {0} ", id);

console.WriteLine("PNPDeviceID: {0} ", pnpId);

console.WriteLine("");

}//for

综上,我们可以定义一个函数,这个函数通过 WMI 检索所有串口设备信息,然后返回一个 Hashtable,其中存储的键为蓝牙设备地址,存储的值为串口名。

函数定义如下:

123456789101112131415161718192021222324252627282930313233343536373839

/* 定义于类 BTHelper 中 */

public static Hashtable QueryBTHPorts()

{

const string Win32_SerialPort = "Win32_SerialPort";

SelectQuery q = new SelectQuery(Win32_SerialPort);

ManagementObjectSearcher s = new ManagementObjectSearcher(q);

Hashtable hashResult = new Hashtable();

foreach (object cur in s.Get())

{

ManagementObject mo = (ManagementObject)cur;

string id = mo.GetPropertyValue("DeviceID").ToString();

string pnpId = mo.GetPropertyValue("PNPDeviceID").ToString();

Debug.WriteLine("WMI>>DeviceID: " + id);

Debug.WriteLine("WMI>>PNPDeviceID: " + pnpId);

Debug.WriteLine("");

/* 仅处理蓝牙串口 */

if (pnpId.StartsWith("BTHENUM"))

{

/* 从 PNPDeviceID 中提取出蓝牙地址,策略是逆序字符串 & 后 _ 之前 */

/* 蓝牙地址为6字节,HEX为12位字符 */

int rBound = pnpId.LastIndexOf('_');

int lBound = pnpId.LastIndexOf('&');

Debug.Assert(rBound - lBound == 13, "Get BT Addr, this will nevery happened.");

string addr = pnpId.Substring(lBound + 1, 12);

if (!hashResult.Contains(addr))

{

hashResult.Add(addr, id);

}

else

{

Debug.WriteLine("Get BT Addr, addr" + addr + " has more than 1 ports");

}

}

}

return hashResult;

}

同样,WMI 检索也比较耗时,所以这个函数调用需要放入工作线程中执行。

有了这个函数,我们可以通过结果中查找我们设置的蓝牙设备地址,就可以得知对应的虚拟串口名,而如果结果中没有我们设置的蓝牙设备地址,那么就可以认定设置虚拟串口服务失败了。

样例代码

这里介绍通过配合使用 BackgroundWorker 来实现蓝牙设备的配对、生成虚拟串口的样例代码:

我们通过传入蓝牙设备地址给 worker,worker 在 DoWork 中帮我们处理所有步骤,过程中报告进度,并最终告诉我们结果。

worker 的 DoWork 事件代码:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495

/* 进行蓝牙连接操作的后台线程执行函数 */

/* 传入 蓝牙地址字符串 作为参数 */

/* 利用 ReportProgress 报告进度 */

/* 利用 DoWorkEventArgs.Result 报告状态 */

void bgworkerConnection_DoWork(object sender, DoWorkEventArgs e)

{

BackgroundWorker worker = sender as BackgroundWorker;

string args = e.Argument as string;

string finalportname = "";

do

{

BluetoothAddress addr = BluetoothAddress.Parse(args);

BluetoothDeviceInfo deviceinfo = new BluetoothDeviceInfo(addr);

/* 检测设备是否在服务范围内,有些设备能被发现但不能被配对连接(如果已经被其他终端配对连接) */

/* 不在范围内则报告错误信息 */

worker.ReportProgress(10, "Testing if device in range...");

if (!BTHelper.TestingIfInRange(addr))

{

string msg = string.Format("Error: Device {0}({1}) is not reachable!",

deviceinfo.DeviceName,

deviceinfo.DeviceAddress.ToString());

e.Result = msg;

return;

}

worker.ReportProgress(10, "Device is in range.");

/* 检测设备是否是已配对设备 */

if (deviceinfo.Authenticated)

{

/* 已配对设备枚举系统蓝牙串口,检测是否已绑定虚拟串口 */

worker.ReportProgress(10, "Querying Bluetooth serialport...");

Hashtable bthTable1 = BTHelper.QueryBTHPorts();

if (bthTable1.ContainsKey(addr.ToString()))

{ /* 获取绑定串口 */

finalportname = bthTable1[addr.ToString()] as string;

/* !!! 跳出 !!! */

break;

}

/* 此处 else 需考虑枚举结果中没有绑定串口的处理,如果没有绑定串口,那么重新设置串口服务 */

/* 此处 else 部分会在下面的流程处理,见下方设置串口服务部分 */

}

else /* 需要进行配对 */

{

worker.ReportProgress(10, "Start pairing...");

if (!BluetoothSecurity.PairRequest(addr, "1234"))

{ /* 配对失败 */

string msg = string.Format("Error: Can not pair to Device {0}({1})!",

deviceinfo.DeviceName,

deviceinfo.DeviceAddress.ToString());

e.Result = msg;

return;

}

}

/* 设置串口服务 */

/* SLC蓝牙设置串口服务时,如果 SetServiceState */

/* 第三个参数为 true (允许异常) 那么则一定会抛出异常,即使虚拟串口创建成功 */

/* 所以这里调用2个参数的重载版本 */

/* 之后检查串口服务是否设置成功的方法就是 设置后检索蓝牙串口 */

worker.ReportProgress(10, "Enable serialport service...");

deviceinfo.SetServiceState(BluetoothService.SerialPort, true);

/* 枚举系统蓝牙串口,检查对应蓝牙是否有串口绑定 */

Hashtable bthTable2 = BTHelper.QueryBTHPorts();

if (bthTable2.ContainsKey(addr.ToString()))

{

/* 获取绑定的串口 */

finalportname = bthTable2[addr.ToString()] as string;

/* !!! 跳出 !!! */

break;

}

else

{ /* 启用串口服务失败或者无法检测到绑定串口 */

string msg = string.Format("Error: Failed to set Serialport service for Device {0}({1})!",

deviceinfo.DeviceName,

deviceinfo.DeviceAddress.ToString());

e.Result = msg;

return;

}

} while (false);

worker.ReportProgress(10, "Open serialport...");

Debug.WriteLine(string.Format(">>PORT NMAE: {0}", finalportname));

/* 成功 */

/* 将串口名放入消息中返回 */

string okmsg = string.Format("OK: {0}", finalportname);

e.Result = okmsg;

}

代码中通过灵活使用 string 作为 Result 来表明执行结果。

worker 的 RunCompleted 事件代码:

123456789101112

void bgworkerConnection_RunCompleted(object sender, RunWorkerCompletedEventArgs e)

{

string msg = e.Result as string;

if(msg.StartsWidth("OK"))

{ /* OK */

MessageBox.Show(msg);

}

else

{ /* Error */

MessageBox.Show(msg);

}

}

后续可以通过字符串处理提取出生成的串口名。

worker 的启动代码:

1

bgworkerConnection.RunWorkerAsync(addrstring);

这样子我们就获取到了绑定的虚拟串口名,但是在使用串口中要注意的是,虚拟串口打开过程中,本质上还是发起了一个蓝牙数据连接,所以打开过程会耗时较长,所以这个虚拟串口打开过程也需要放入工作线程中处理。如果虚拟串口对应的蓝牙设备没有在线或者其他原因造成不可用,那么打开时会抛出异常,需要在代码上进行处理。

一旦蓝牙设备经过绑定并且设置虚拟串口服务成功,那么对应的串口会一直存在与系统的串口列表中,但这并不代表它是可用的,请在打开这类串口时添加异常处理代码。

(完)

c# 蓝牙虚拟串口_32feet.net 蓝牙虚拟串口编程相关推荐

  1. 使用keil,proteus,虚拟串口,完成蓝牙通信

    目的: 通过HC-06的蓝牙芯片,AT89C51的51单片机,借助keil,proteus,通过虚拟串口,在电脑上实现蓝牙串口通信,控制LED的开和关. 存在的问题: 单片机和蓝牙不能联动,暂未找出问 ...

  2. 虚拟机服务器怎么关闭蓝牙,在vmware虚拟主机里使用蓝牙

    12年前 (2010-03-17) | 25,322 views | 抢沙发 | 本文共764个字 朗读这篇文章 看到标题也许你心动了吧?这可能对一般人来说不算什么,但是对于我来说,我可是经常用到蓝牙 ...

  3. android蓝牙模块有哪些类型,蓝牙串口模块是什么?怎么用?有哪些种类?

    燚智能点评:蓝牙串口模块,分为经典蓝牙串口(4.0以前的)和BLE串口(4.0以后).经典蓝牙的速度快,和普通串口速度相当,功耗高.BLE的速度慢,也就1K每秒上下,但是功耗低. 如果做物联网产品的控 ...

  4. java连接stk外部接口_SLWSTK无线开发工具上的外扩串口如何使用(虚拟串口/VCOM)...

    标题:   SLWSTK无线开发工具上的外扩串口如何使用(虚拟串口/VCOM) 关键词:ZigBee, 串口,VCOM,虚拟串口,外扩串口,EFR32, PCB4001,  EFR32MG13P732 ...

  5. TB-02 Kit开发板及TB系列BLE蓝牙模块固件及天猫精灵三元组串口烧录工具操作方法说明及固件市场介绍(基于泰凌微TLSR8258芯片)

    摘要 这篇文章介绍如何通过烧录工具给TB系列蓝牙模块烧录固件及天猫精灵三元组,以及如何通过烧录工具的固件市场下载最新的固件及固件操作说明文档. 获取烧录工具 安信可团队针对TB系列蓝牙模块专门开发了烧 ...

  6. STM32CubeMX(2)——串口实现HC-05蓝牙模块与手机通信

    STM32实现蓝牙与手机通信 使用的蓝牙 STM32Cubemax配置 手机连接蓝牙 代码编写 总结 使用的蓝牙 本文使用的蓝牙是市面上常用的HC-05蓝牙模块 其引脚和STM32的连接为 蓝牙模块 ...

  7. 串口的使用–蓝牙模块

    串口的使用–蓝牙模块 @(MSP432P401R) 串口的配置 基本默认即可 串口的API的使用 参数 UART_Mode即串口模式 readTimeout和writeTimeout即读超时和写超时时 ...

  8. ESP32-pico-kit 串口通信 与 蓝牙串口

    文章目录 一.串口及ESP32介绍 1.串口通信 2.ESP32-pico-kit 二.串口UART 1.串口引脚 2.串口常用函数 (1)开启串口 (2)关闭串口 (3)输出数据 (4)读取数据 ( ...

  9. BlackBerry 10 BlackBerry OS 7 1 手机通过蓝牙串口读取Arduino 蓝牙传过来的温度

    目标:BlackBerry手机通过蓝牙串口读取Arduino 蓝牙传过来的温度 湿度信息 硬件:Arduino主板 + DHT11温湿度传感器 + 蓝牙模块,开发工具C语言 手机:BlackBerry ...

  10. 利用python程序、虚拟串口软件、串口通信助手,实现串口通信(两串口互相收发文字)

    一.流程简述 程序可以参考: https://blog.csdn.net/colcloud/article/details/42454839 这是我参考后简化的版本: https://github.c ...

最新文章

  1. Android支付接入(五):机锋网
  2. Laravel插件推荐
  3. (chap6 Http首部) 响应首部字段 ServerVaryWWW-Authenticate
  4. 莫队算法(Mo's_Algorithm)
  5. java 动态按钮_java-向片段动态添加按钮
  6. Java ObjectStreamField getName()方法与示例
  7. 想入职阿里的Java开发者必看,阿里巴巴面试官实战经验分享!
  8. Android蓝牙开发的一点总结
  9. 【IMU】BMI160 Driver分析及使用
  10. Service Mesh-Conduit概览
  11. czl蒻蒟的OI之路3
  12. javascript中in用法介绍
  13. Android开发艺术探索读书笔记(二)
  14. 网站服务器部署apk软件,供外网下载
  15. Linux部署人大金仓(Kingbase8)
  16. Hadoop笔记-02 安装
  17. Android联网报错:Cleartext HTTP traffic to XXXXX not permitted的解决方法
  18. 数理统计笔记7:分类数据分析-拟合优度检验和列联分析
  19. 将你的老旧Android平板或手机改造成服务器
  20. c语言食堂消费管理系统,食堂消费管理系统_食堂财务管理系统v1.0单机版

热门文章

  1. 三创赛优秀作品_厉害了!珠海这所学校的大学生夺得“三创赛”全国总决赛一等奖...
  2. 社区人物志|缪翎:见证开源世界的女性力量
  3. 九九乘法表居中c语言,excel图文教程:九九乘法表的制作方法,你会哪种?
  4. 无法重命名文件夹,错误0x80004005 未指定的错误
  5. 分享一款上班摸鱼神器,再也不怕领导突然出现在身后了~
  6. linux-centos7解决视频无法看问题(安装Adobe flash player)
  7. HttpPrinter-网页打印控件
  8. 什么是 480i、576i、480p、1080i、720p?什么是 HDTV?
  9. springboot+vue公务员考试信息管理系统java
  10. 万字长文的Git使用教程:最详细、最傻瓜、最浅显、真正手把手教!