大家知道,Windows Mobile 6.0 SDK自带了cellular emulator和fake GPS,使得我们可以在模拟器上调试radio级的phone call和基于GPS的地理位置应用。
我们来看看实现Bluetooth的架构,参考下图(来源于),主要包括emulator端和PC端:
1.?Emulator端
Microsoft Remote Tools Framework remote agent
2. ?PC端
Microsoft Remote Tools Framework desktop plugin
USB Driver runtime
USB Driver
Connected Bluetooth USB device
??? 注意:作者采用了这个开源的工程来控制Bluetooth的接入。
??? 在Windows Mobile模拟器上使用蓝牙的必要条件有:
1.?Visual studio 2005 with SP1 或者 Visual studio 2008
2. Windows Mobile 5.0 Pocket PC/Smartphone SDK and/or Windows Mobile 6 Standard/Professional SDK emulator images
3.?
4.?
5.?一个具有USB接口的Bluetooth dongle
在Windows Mobile模拟器上使用蓝牙,即在PC端安装下载的的蓝牙驱动,方法自然是通过"windows设备管理器",为新的硬件设备(也就是连接的Bluetooth USB device)添加下载过来的驱动(主要是找到fbtusb.inf文件)。具体如下:
1、打开Windows设备管理器。
2、在设备管理器,找到蓝牙设备将设备用作FreeBT USB接口。
3、点击鼠标右键并选择"更新驱动程序..."在弹出的菜单。
4、选择"否,暂时不",然后点击"下一步>"。
5、选择"安装从列表或指定位置",然后点击"下一步>"。
6、选择"不要搜索。我将选择要安装的驱动",然后点击"下一步>"。
7、选择设备驱动程序,然后点击"从磁盘安装... ".
8、在查找文件对话框中,浏览到fbtusb.inf文件,然后点击"下一步>"。
9、当一个"硬件安装警告"出现后,点击"仍然继续"。
10、一旦安装完毕后,点击"完成"。
11、设备管理器现在应显示"FreeBT USB驱动"在名单的USB控制器。
1、安装FreeBT USB驱动中描述了蓝牙USB驱动安装部分。
2、安装要求如描述的安装步骤。
3、?在Remote Tools Framework plugin中运行BthEmulManager.cetool,将其和模拟器建立连接。
4、从列表中选择一个仿真图像。
5、等待模拟器连接。
6、选择"Bluetooth for Microsoft Device Emulator"节点。
7、如果你有一个蓝牙设备连接成功,那么蓝牙设备信息(地址,制造商,人机交互版本和LMP版本)将被显示。现在,蓝牙应该运行在你的模拟器上。如果有一个错误代码,则将被显示。如图9所示。
8、要xx通讯记录,点击鼠标右键,选择"xx"。
9、当前设备信息复制到剪贴板,选择"蓝牙为微软装置模拟器"节点,点击鼠标右键,选择"拷贝到剪贴板"。
10、关掉蓝牙的模拟器,点击"连接"菜单,选择"脱离…模拟器"。
11、微软远程工具结构允许你在同一时间启动两个装置模拟器。如果您已经安装了两个或两个以上FreeBT USB设备,点击"窗口"菜单并选择"切分窗口查看"。重复步骤3-6。你将得到两个同时运行的蓝牙驱动仿真器。
12、很可能要启用/禁用设备端日志记录。如果您启用"设备日志记录"复选框,然后将启用远程登录在模拟器上。关注\ \ Temp的模拟器目录。应该有建立btd_bthemul_0.txt,btd_BthEmulAgent_0.txt,btd_bthemulcom_0.txt文件。
13、很可能要启用/禁用桌面端日志记录。如果您启用"桌面日志记录"复选框,那么就将启用本地记录。查看你的安装目录,应该有建立BthEmulManager.txt文件。
14、很可能要启用/禁用通讯记录。通讯记录可以看到仿真器和蓝牙设备之间的通信活动。
连接建立以后,蓝牙设备的信息(Address, Manufacturer, HCI Version, LMP Version)就会显示在界面上。
在程序启动后,首先搜索附近的蓝牙设备,搜索过程完成以后,将其设备的名字和蓝牙地址显示在下拉列表中,然后就可以进行双向的消息发送和接收了。程序运行界面如下图10所示:
需要注意的是,在设备蓝牙的时候,如果没有将"对其他设备可见"选项打上勾,即只是将蓝牙打开,如下图所示。
可能无法正常通信,应用程序会提示无法发送消息。选中"对其他设备可见"之后,就能够正常通信了,如下图12所示:
在蓝牙配对中,当Windows Mobile 找到了蓝牙设备金瓯,金瓯设置的蓝牙连接密码是1234。如图13所示。
成功后,选择打开蓝牙时不会出错 |
正在搜索设备过程中 |
搜索完成 |
建立连接过程中要求输入连接密码 |
?
选择蓝牙服务——串口连接服务 |
创建成功COM端口 |
由于蓝牙无线协议在手机中的应用较为广泛,因此,无论是Windows Mobile SDK,还是其他第三方商业或开源软件组织,都对蓝牙程序设计有较好的支持。如xx的OpenNETCF,在其早期的版本中就提供了OpenNETCF.Bluetooth.WidComm库,而微软则有官方版本的Microsoft?Bluetooth?Stack提供,目前,比较流行的库有32feet.net。三方的地址列表如下:
在.NET Compact Framework下进行Bluetooth开发有几个可选解决方案
而这些库里,对蓝牙串口服务的支持均存在一定的缺陷,如Feet32.Net的作者甚至声称其中建立串口连接的代码是不安全的。这个问题在模拟器下更显突出,原因是模拟器下,对串口的支持仅是从COM0~COM2,而在使用SDK中列举时,得到的结果却是COM0~COM10,这样,如果随机选择了大于COM2的虚拟端口,程序便会报错。而这个问题,在手动创建蓝牙虚拟串口服务时也会不时发生。由于暂时没有真实的Mobile设备进行测试,不能xx证明,是由于模拟器的问题还是SDK使用不当的问题,留待购置了Mobile设备后再来研究。
OpenNETCF的新版本中,不知何故去除了WidComm的包,但仍有网友在其源代码上进行部分的改造利用,下文就针对上面所列的两种库的使用进行简要的说明。
32feet.net是由InTheHand公司所属的一个开源项目组织,专门针对基于.NET平台的无线数据传输(如蓝牙,红外等)提供技术支持。目前已经发布的组件有:
对蓝牙的支持需要设备兼容?Microsoft Bluetooth协议栈。CE.NET4.2或XP以上的操作系统。
表1:InTheHand类库结构
名字空间 | 描述 |
The InTheHand namespace contains classes for working with the Uri class for personal area networks. | |
The InTheHand.Net namespace contains classes for working with addressing on personal area networks. | |
The InTheHand.Net.Bluetooth namespace contains classes for working with Bluetooth functionality such as Radio hardware. | |
The InTheHand.Net.Bluetooth.AttributeIds namespace contains definitions of Service Discovery Protocol attributes. | |
The InTheHand.Net.IrDA namespace contains classes for working with Infrared functionality. | |
The InTheHand.Net.Mime namespace holds types that are used to represent Multipurpose Internet Mail Exchange (MIME) headers | |
The InTheHand.Net.Ports namespace contains classes for working with legacy virtual COM ports over Bluetooth. | |
The InTheHand.Net.Sockets namespace provides added functionality for working with IrDA and Bluetooth Sockets. | |
The InTheHand.Windows.Forms namespace contains forms related to networking functionality. |
现仅列举32feet.NET中的Buletooth及Sockets名字空间,以供参考。
表2:InTheHand.Net.Bluetooth名字空间
类 | 描述 |
Retrieves the name of the SDP Attribute ID with the given value in the specified Attribute ID class sets. Implementing -like behaviour. | |
Configures what type of element will be added by the for the attribute. | |
Represents a Bluetooth Radio device. | |
Handles security between bluetooth devices. | |
Standard Bluetooth Profile identifiers. | |
Provides Bluetooth authentication services on desktop Windows. | |
Provides data for an authentication event. | |
Describes the device and service capabilities of a device. | |
Class of Device flags as assigned in the Bluetooth specifications. | |
Represents the types that an SDP element can hold. | |
Represents the type of the element in the SDP record binary format, and is stored as the higher 5 bits of the header byte. | |
Specifies the current status of the Bluetooth hardware. | |
Represents a member of the SDP , Attribute which provides for multi-language strings in a record. | |
Flags to describe Link Policy. | |
Manufacturer codes. | |
Gets a list of enum-like classes containing SDP Service Attribute Id definitions for a particular Service Class. | |
Determine all the possible modes of operation of the Bluetooth radio. | |
Holds an attribute from an SDP service record. | |
A Service Attribute Id identifies each attribute within an SDP service record. | |
? | |
Holds an SDP data element. | |
Holds an SDP service record. | |
Provides a simple way to build a , including ServiceClassIds and ServiceNames attributes etc. | |
Creates a Service Record byte array from the given object. | |
Some useful methods for working with a SDP including creating and accessing the for an RFCOMM service. | |
Parses an array of bytes into the contained SDP . | |
Utilities method working on SDP s, for instance to produce a 'dump' of the record's contents. | |
Represents the size of the SDP element in the record binary format, and is stored as the lower 3 bits of the header byte. | |
Indicates that the field to which it is applied represents an SDP Attribute that can exist in multiple language instances and thus has a language base offset applied to its numerical ID when added to a record. |
表3: InTheHand.Net.Sockets名字空间
?
Type | Description |
Specifies additional addressing schemes that an instance of the class can use. | |
Provides client connections for Bluetooth network services. | |
Provides information about an available device obtained by the client during device discovery. | |
Listens for connections from Bluetooth network clients. | |
Specifies additional protocols that the class supports. | |
Defines additional Bluetooth socket option levels for the and methods. | |
Defines configuration option names for the class. | |
Describes the character sets supported by the device. | |
Makes connections to services on peer IrDA devices. | |
Provides information about remote devices connected by infrared communications. | |
Describes an enumeration of possible device types, such as Fax. | |
Places a socket in a listening state to monitor infrared connections from a specified service or network address. | |
Defines additional IrDA socket option levels for the and methods. | |
Socket option constants to set IrDA specific connection modes, and get/set IrDA specific features. |
Bluetooth的应用十分广泛,基于Bluetooth的通信程序开发主要有以下几个步骤:
(1)服务端
开启蓝牙
public
static
void DisplayBluetoothRadio()
{
BluetoothRadio myRadio = BluetoothRadio.PrimaryRadio;
if (myRadio == null)
{
WriteMessage("No radio hardware or unsupported software stack");
return;
}
// Enable discoverable mode
myRadio.Mode = RadioMode.Discoverable;
}
开启服务
private
static
void StartService()
{
BluetoothListener listener = new BluetoothListener(BluetoothService.SerialPort);
listener.Start();
WriteMessage("Service started!");
BluetoothClient client = listener.AcceptBluetoothClient();
WriteMessage("Got a request!");
Stream peerStream = client.GetStream();
string dataToSend = "Hello from service!";
// Convert dataToSend into a byte array
byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(dataToSend);
// Output data to stream
peerStream.Write(dataBuffer, 0, dataBuffer.Length);
byte[] buffer = new
byte[2000];
while (true)
{
if (peerStream.CanRead)
{
peerStream.Read(buffer, 0, 50);
string data = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, 50);
WriteMessage("Receiving data: " + data);
}
}
}
DisplayBluetoothRadio用来展现本端设备的信息,以及把本端bluetooth设备设置为可发现。如果使用32feet.NET 2.3,也就是当前{zx1}的release版本,wince不支持读取和设置radio mode。
服务端的侦听BluetoothListener还是使用winsock实现和Windows Embedded Source Tools for Bluetooth的实现类似。
(2)客户端
private
static
void ConnectService()
{
BluetoothClient client = new BluetoothClient();
BluetoothDeviceInfo[] devices = client.DiscoverDevices();
BluetoothDeviceInfo device = null;
foreach (BluetoothDeviceInfo d in devices)
{
if (d.DeviceName == "BLUETOOTH_DEVICE")
{
device = d;
break;
}
}
if (device != null)
{
WriteMessage(String.Format("Name:{0} Address:{1:C}", device.DeviceName, device.DeviceAddress));
client.Connect(device.DeviceAddress, BluetoothService.SerialPort);
Stream peerStream = client.GetStream();
// Create storage for receiving data
byte[] buffer = new
byte[2000];
// Read Data
peerStream.Read(buffer, 0, 50);
// Convert Data to String
string data = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, 50);
WriteMessage("Receiving data: " + data);
int i = 0;
while (true)
{
WriteMessage("Writing: " + i.ToString());
byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(i.ToString());
peerStream.Write(dataBuffer, 0, dataBuffer.Length);
++i;
if (i >= int.MaxValue)
{
i = 0;
}
System.Threading.Thread.Sleep(500);
}
// Close network stream
peerStream.Close();
}
}
?
32feet.NET 2.3提供了自发现功能,通过client.DiscoverDevices()寻找附近的bluetooth设备。在上述例子中指定连接名字为"BLUETOOTH_DEVICE"的设备。Winsock通信和Windows Embedded Source Tools for Bluetooth类似。client.Connect(device.DeviceAddress, BluetoothService.SerialPort)用于连接名字为"BLUETOOTH_DEVICE"的设备,同样指定串口服务。传输的数据为字节流(byte[]),因此可以传输任意类型的数据。客户端在接收回应信息后,不断的往服务端发送数据。
Connect() 函数的第二个参数是服务类型,十分重要,通信双方必须使用同样的服务类型。如果通信双方的程序都是由我们负责开发,可以使用通用的服务类型,例如BluetoothService.SerialPort。但是如果要与第三方设备进行通信,需要查出该设备的服务类型,在设备端的通信服务类型一般编写在Firmware(固件)里面,而且会按照规范编写,例如Bluetooth耳机会使用BluetoothService.Handsfree。
try
{
BluetoothAddress deviceAddress = deviceAddresses[listBoxDevices.SelectedItem.ToString()];
client.SetPin(deviceAddress, textBoxPin.Text.Trim());
//client.Connect(deviceAddress, BluetoothService.Handsfree); //if connect ot Hands free.
client.Connect(deviceAddress, BluetoothService.SerialPort); //if connect to cell phone and so forth.
MessageBox.Show("Pair successful.");
//transfer data..
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
SetPin()的功能是设置连接密码的,其函数原型如下:
[DllImport("btdrt.dll", SetLastError=true)]
public static extern int BthSetPIN(byte[] pba, int cPinLength, byte[] ppin);
所谓Bluetooth Virtual Serial Port,其实是从软件的角度看,把Bluetooth的通信转化成Serial Port(串口)。经过这样的转换后,使用Bluetooth的Client程序可以像使用串口一样操作Bluetooth。这个应用方式的出现是为了支持现有应用(Legacy system,遗产应用)。举个例子,在Bluetooth出现以前,大部分移动设备都是通过串口连接的GPS receiver的,基于GPS应用程序的开发也就通过的串口通信取出NMEA data。
所谓Bluetooth Virtual Serial Port,其实是从软件的角度看,把Bluetooth的通信转化成Serial Port(串口)。经过这样的转换后,使用Bluetooth的Client程序可以像使用串口一样操作Bluetooth。这个应用方式的出现是为了支持现有应用(Legacy system,遗产应用)。举个例子,在Bluetooth出现以前,大部分移动设备都是通过串口连接的GPS receiver的,基于GPS应用程序的开发也就通过的串口通信取出NMEA data。随着Bluetooth的普及,移动设备可以通过Bluetooth来连接GPS receiver了,那么原先基于GPS的应用程序需要重新开发通信部分去读取NMEA data,这为现有应用带来很多麻烦,所有的现有应用都需要重写通信部分,因此人们想出解决方法,把Bluetooth的通信转化成Serial Port(串口)。硬件上使用Bluetooth来进行通信,在软件上虚拟一个串口给应用程序,应用程序不需要任何的修改就可以支持Bluetooth的GPS Receiver了。这就像设计模式里面的Adapter模式,但是这里是为新设备提供原有的接口,使得原先的Client不需要更改。
由于Bluetooth Virtual Serial Port的出现基于对现有系统(Legacy System)支持的需求,所以对于新的系统,MS不推荐使用Bluetooth Virtual Serial Port,而是直接使用Winsock进行通信。在使用Winsock进行Bluetooth通信需要指定服务,因此可以指定使用串口服务进行通信。Bluetooth Virtual Serial Port和Winsock的Bluetooth通信都是使用RFCOMM协议,所以两者等同。使用Winsock的Bluetooth通信比Bluetooth Virtual Serial Port更简单,不需要配置。而且更强壮(robust),因为使用Winsock的Bluetooth通信可以直接监听到蓝牙设备关闭或者离开通信范围,而Bluetooth Virtual Serial Port只能通过Timeout来检查。
由于支持现有系统(Legacy System),Bluetooth Virtual Serial Port还是有存在的价值,下面讲述Bluetooth Virtual Serial Port的开发。在Windows Mobile下有两种方法可以建立Bluetooth Virtual Serial Port:调用API建立Bluetooth Virtual Serial Port和修改注册表建立Bluetooth Virtual Serial Port。
在32feet.net里面,创建虚拟串口这些API封装在InTheHand.Net.Ports.BluetoothSerialPort,可以直接使用。但是32feet.net的作者提醒这些API是不是可信赖的(unreliable),所以在使用之前请要谨慎考虑和详细测试。我在wince 5下测试过,不能成功建立Bluetooth Virtual Serial Port。
public static void CreateIncomingPort()
{
BluetoothSerialPort port = BluetoothSerialPort.CreateServer(BluetoothService.SerialPort);
Console.WriteLine(port.PortName);
}
public static void CreateIncomingPort()
{
BluetoothClient client = new BluetoothClient();
BluetoothDeviceInfo[] devices = client.DiscoverDevices();
BluetoothDeviceInfo device = null;
foreach (BluetoothDeviceInfo d in devices)
{
if (d.DeviceName == "BLUETOOTH_DEVICE")
{
device = d;
break;
}
}
BluetoothEndPoint endPoint = new BluetoothEndPoint(device.DeviceAddress, BluetoothService.SerialPort);
BluetoothSerialPort port = BluetoothSerialPort.CreateClient(endPoint);
Console.WriteLine(port.PortName);
}
(2)使用修改注册表法创建虚拟串口
if (state)
{
//write registry settings for WM5 Serial Port support
//get available ports
Microsoft.Win32.RegistryKey rkPorts = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Bluetooth\\Serial\\Ports", true);
string[] supportedPorts = (string[])rkPorts.GetValue("SupportedPorts");
System.Collections.ArrayList alPorts = new System.Collections.ArrayList(supportedPorts);
//check availability
foreach (string deviceid in rkPorts.GetSubKeyNames())
{
Microsoft.Win32.RegistryKey rkDevice = rkPorts.OpenSubKey(deviceid);
//remove port from arraylist if unavailable
string port = rkDevice.GetValue("Port").ToString();
int nullPos = port.IndexOf('\0');
if (nullPos > -1)
{
port = port.Substring(0, nullPos);
}
if (alPorts.Contains(port))
{
alPorts.Remove(port);
}
rkDevice.Close();
}
if (alPorts.Count == 0)
{
throw new InvalidOperationException("No ports available");
}
//write port details to registry
Microsoft.Win32.RegistryKey rkNewPort = rkPorts.CreateSubKey(this.DeviceAddress.ToString("8"));
rkNewPort.SetValue("KeepDCB", 0);
rkNewPort.SetValue("RemoteDCB", 0);
rkNewPort.SetValue("Encryption", 0);
rkNewPort.SetValue("Authentication", 0);
rkNewPort.SetValue("Port", alPorts[0]);
rkNewPort.SetValue("Server", 0);
rkNewPort.Close();
rkPorts.Close();
//try open port now
try
{
InTheHand.Net.Ports.BluetoothSerialPort.CreateClient(alPorts[0].ToString(), new BluetoothEndPoint(this.DeviceAddress, BluetoothService.SerialPort));
}
catch
{
}
}
else
{
//find and remove registry entries
Microsoft.Win32.RegistryKey rkPorts = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Bluetooth\\Serial\\Ports", true);
foreach (string deviceAddress in rkPorts.GetSubKeyNames())
{
if (deviceAddress == this.DeviceAddress.ToString("8"))
{
rkPorts.DeleteSubKeyTree(deviceAddress);
break;
}
}
????rkPorts.Close();
}
当state为true时为注册,当state为false时为反注册。