Please see the version now online at our codeplex site.

 

 

 

 

32feet.NET — User’s Guide

Version 3.0 Beta, September 2010

 

32feet.NET — User’s Guide................................................................................................................................... 1

Supported hardware and software.................................................................................................................. 1

Broadcom/Widcomm................................................................................................................................... 2

Multi-stack support............................................................................................................................................ 2

Referencing the library....................................................................................................................................... 3

VisualBasic samples........................................................................................................................................... 3

OBEX — object transfer................................................................................................................................... 3

Behaviour from servers................................................................................................................................ 4

Server side....................................................................................................................................................... 4

Brecham.Obex............................................................................................................................................... 5

General Bluetooth data connections............................................................................................................... 5

Discovery........................................................................................................................................................ 5

DeviceName and discovery......................................................................................................................... 6

Server side....................................................................................................................................................... 6

Errors................................................................................................................................................................ 6

Stream.Read and the number of bytes returned..................................................................................... 7

The Connected property and connection loss.......................................................................................... 7

Connecting to Bluetooth services.................................................................................................................... 7

General IrDA connections................................................................................................................................. 8

Bluetooth settings, device information etc..................................................................................................... 8

Peer Device information............................................................................................................................... 8

Local Radio information............................................................................................................................. 8

Bluetooth Serial Ports......................................................................................................................................... 9

Getting virtual COM port names for remote Bluetooth devices........................................................... 9

Bluetooth Security............................................................................................................................................ 10

Bluetooth SDP — Service Discovery Protocol............................................................................................ 11

Creating Records......................................................................................................................................... 11

Connect by Service Name......................................................................................................................... 12

Manual record creation.............................................................................................................................. 12

 

Introduction

There are generally four ways an application might want to use Bluetooth:

1. Make a direct data connection

Where the program connects directly to a Bluetooth RFCOMM service, and sends and receives the raw data for that connection.  The server side function can also be provided.  See section “General Bluetooth data connections” below.

2. Do an OBEX transfer

Where the program is an OBEX client and connects to a server, and sends (PUTs) or GETs a file/object.  The server side function can also be provided.  See section “OBEX” below.

3. Have the Bluetooth stack and/or the OS connect to and use a remote service

Common services for this case are where the service is Headset/Handsfree/A2DP, or networking for instance.  Here we do not want the program to connect directly to those services, as we wouldn’t know what to do with the raw bytes, but instead want the OS to send audio to the headset, or form a network connection with an access-point or similar.  See section “Connecting to Bluetooth services” below.

4. Make a direct data connection using the L2CAP protocol

Where the program connects directly to a Bluetooth L2CAP service, and sends and receives the raw data for that connection.  This is not supported currently.

See http://www.alanjmcf.me.uk/comms/bluetooth/Bluetooth Profiles and 32feet.NET.html for information on what services use which method.

For device discovery see the section under “General Bluetooth data connections” below.

Supported hardware and software

The library is supported both in a version for desktop Windows, and a version for NETCF v2.0.  On both platforms various companies have provided software protocol stack software to use Bluetooth hardware.  For instance, on desktop Windows there are well known stacks from Microsoft, Widcomm (now Broadcom), BlueSoleil and Toshiba.  On CE platforms there are also stacks from Microsoft and Widcomm/Broadcom.  To visually identify which stack is installed see http://www.peterfoot.net/VisuallyIdentifyYourBluetoothStack.aspx.  On both platforms we have long standing support for the Microsoft stack, and we have new support for the Broadcom/Widcomm stack again on both platforms.  We’d like to support the BlueSoleil stack but are looking for funding to do so, contact Alan if you need support for this stack.

On a device where there is no Bluetooth hardware connected or a non-Microsoft stack is present the library will obviously not function.  Opening a socket will fail with an exception and getting the list of local radios (BluetoothRadio.AllRadios) will return a zero length array, and getting the primary radio (BluetoothRadio.PrimaryRadio) will return null/Nothing, thus code like the following will fail with a NullReferenceException.

BluetoothRadio.PrimaryRadio.Mode = RadioMode.Discoverable

On desktop Windows it is generally possible to disable the third-party stack and install the Microsoft stack.  The document Belkin F8T012 and Microsoft Stack downloadable from http://32feet.net/files/folders/1118/download.aspx describes how to install the Microsoft stack, and also includes the steps necessary to install a Bluetooth device that Windows wasn’t originally aware of.

On machines with the Toshiba software, from Add/Remove programs remove Bluetooth Stack for Windows by Toshiba, then run the C:\TOSHIBA\MS_Bluetooth\BtMon2Inst.exe installer to install the BT monitor, and finally reboot the machine, whereupon the system will detect the radio and install the necessary Microsoft-supplied drivers as above.

Finally note that each of these stacks support only one attached radio.  For instance on the Microsoft XP stack when I have two dongles attached I see an event log warning from BTHUSB with message “Only one active Bluetooth radio is supported at a time.”

Broadcom/Widcomm

This was new support in version 2.4.  In 3.0 we have done a lot of work to ensure that all functionality works from asynchronous callbacks, new features include setting BluetoothRadio.Mode on CE/WM (there’s no Widcomm API on Win32), correctly reporting the Mode when the radio is disabled, support for InquiryLength in DiscoverDevices, and closing all connections when the radio is switched off.  In 2.5, there were improvements to diagnostics on setting-up dependencies and BluetoothListener was rewritten amongst other changes.

To enable the Widcomm support, in brief the 32feetWidcomm.dll native DLL need to be present at runtime in the same folder as the main library assembly (InTheHand.Net.Personal.dll) and the application itself.  As a native DLL it cannot be referenced at compile-time like a normal managed .NET assembly.

Also, Win32 needs a particular version of the Visual C++ Runtime libraries, since 2.5.1 this is version 9.0.21022.8, and can be found at “Microsoft Visual C++ 2008 Redistributable Package (x86)” http://www.microsoft.com/downloads/en/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf. (Version 2.5 required version 8.0.50727.4053. If not already installed, this can be found at “Microsoft Visual C++ 2005 Service Pack 1 Redistributable Package ATL Security Update” http://www.microsoft.com/downloads/details.aspx?familyid=766a6af7-ec73-40ff-b072-9112bab119c2&displaylang=en)

In more detail, Since the Widcomm API is C++ and thus can’t be P/Invoke’d directly we unfortunately require a native DLL as well as the normal library assembly.  Versions of the native DLL are supplied primarily for Win32 x86 and WM2005, we also include PPC2003 and Win32 x64 but we don’t/can’t test them.  At runtime the 32feetWidcomm.dll DLL needs to be present alongside the library assembly (or somewhere else in the path presumably), there is no need to reference it at compile-time (as a native DLL is can’t be referenced like a .NET assembly).

For Widcomm, various users have reported that there are problems on desktop Windows with newer versions of the Widcomm stack, with for instance BluetoothClient.Connect failing with a SocketException with it message including the code “PortLookup_NoneRfcomm”.  We now supply two versions of 32feetWidcomm.dll for Win32 for this reason.  Unfortunately when to use them is complex— I wish Widcomm had been a bit cleverer about how they provided their Vista support. :-(  (Note that Bluecove on Java also has to supply two versions of the DLL presumably for similar reasons[1]).

·         Normal 32feetWidcomm.dll version
Works even when the Microsoft Bluetooth stack is also active, and so allows multi-stack support.  But might not work on newer version installations of the Widcomm stack.

·         “SDK6” version
May be required on newer version installations of the Widcomm stack, but will not work when the Microsoft Bluetooth stack is active.

We also now include copies of the 32feetWidcomm.dll for x64, let me know if it works for you, it is untested by us.

There are no further dependencies on Windows Mobile but there is on desktop Windows.  That’s the ‘C Runtime’ (CRT) libraries as described above.  If the correct version is not installed, then the Widcomm support in the library will report that it can’t load the 32feetWidcomm.dll — even if it’s present in the same folder as the application/library.  To check this, run the test app ‘Test32FeetWidcommWin32.exe’ which will report something like: “This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.”  Installing the CRT will fix this.  See the Widcomm document for more information.

Multi-stack support

From version 2.5 (actually version 2.4.1) we support using the Microsoft and Widcomm stacks at the same time.  This allows one program to support more than seven connections, or similar situations. 

To use the multi-stack support on Windows XP, install both Microsoft and Widcomm stacks and attach their radio dongles.  Then create an app.config file with your application and set the flag “oneStackOnly” to “false” — there is a sample app.config file in the assemblies folder, copy it, rename it to match you app e.g. “thisismyapp.exe.config”, and change the settings as required.  It is likely that we will change the default for “oneStackOnly” to be “false” in the next version.

If the two stacks both load successfully, then BluetoothRadio.AllRadios should contain two items.  Using new BluetoothClient() etc will always create an instance from the first radio/stack.  To specifically create an instance from a particular radio/stack use the StackFactory property on BluetoothRadio.  For instance:

Dim radioB As BluetoothRadio = BluetoothRadio.AllRadios(1)
Dim cli As BluetoothClient = radioB.StackFactory.CreateBluetoothClient()
Dim lsnr As BluetoothListener = _
   radioB.StackFactory.CreateBluetoothListener(BluetoothService.Wap)

If at least one of the stacks loads, but one or more fails to load then the program will continue.  More diagnostics can be enabled to allow troubleshooting.  Set app.config flag “reportAllErrors” to “true”.  This outputs any errors on the Console and on System.Diagnostics.Trace output; since we use Trace.Fail currently, an assert dialog box will pop-up and need to be closed manually —  if necessary see MSDN on “AssertUiEnabled=false” for disabling the dialog.

Referencing the library

The library is provided as an assembly with name InTheHand.Net.Personal.dll.  The installer arranges that it can be selected directly from Visual Studio’s Add Reference dialog.

Two versions of the library are provided, one for use with: desktop (Win32) CLR and one for NETCF v2.0 applications.  They are installed in directories called XP2 and CE2 respectively.  The correct one for the project type is listed in the Add Reference dialog as above.

VisualBasic samples

The samples here are provided in VisualBasic.NET.  C# users are generally always able to read VB except in the most advanced cases.  Apart from obvious like needing semicolons added, method calls and member access for example are very similar, and explicit Sub…End Sub, While … End While can simply be replaced by braces {…}.  For the samples here the only thing that might need to be explained is that “Dim” creates a variable definition, so the first two lines in the first OBEX sample below in C# are:

String addr = "112233445566";
Uri uri = new Uri("obex://" + addr + "/HelloWorld.txt");

So apart from the change to the variable definition syntax we needed to add a semicolon to end of each line, change the case of keyword “New” to be “new”, and change VB’s string append operator (&) to C#s ‘+’ equivalent.

OBEX — object transfer

If you want to transfer a file or other object using the standard service as used by Windows’ Wireless Link / Bluetooth File Transfer Wizard, Palm’s Beam, Nokia’s Send via Infrared, then use the OBEX protocol.  On the client-side one can use code like the following to send a file.

' The host part of the URI is the device address, e.g. IrDAAddress.ToString(),
' and the file part is the OBEX object name.
Dim addr As String = "112233445566"
Dim uri As New Uri("obex://" & addr & "/HelloWorld.txt")
Dim req As New ObexWebRequest(uri)
req.ReadFile("Hello World.txt")
Dim rsp As ObexWebResponse = CType(req.GetResponse(),ObexWebResponse)
Console.WriteLine("Response Code: {0} (0x{0:X})", rsp.StatusCode)

Or, to send locally generated content use something like the following.

' The host part of the URI is the device address, e.g. IrDAAddress.ToString(),
' and the file part is the OBEX object name.
Dim addr As String = "112233445566"
Dim uri As New Uri("obex://" & addr & "/HelloWorld2.txt")
Dim req As New ObexWebRequest(uri)
Using content As Stream = req.GetRequestStream()
   ' Using a StreamWriter to write text to the stream...
   Using wtr As New StreamWriter(content)
      wtr.WriteLine("Hello World GetRequestStream")
      wtr.WriteLine("Hello World GetRequestStream 2")
      wtr.Flush()
      ' Set the Length header value
      req.ContentLength = content.Length
   End Using
   ' In this case closing the StreamWriter also closed the Stream, but ...
End Using
Dim rsp As ObexWebResponse = CType(req.GetResponse(),ObexWebResponse)
Console.WriteLine("Response Code: {0} (0x{0:X})", rsp.StatusCode)

See also the ObexPushApplication and ObexPushVB sample programs.

The PUT operation is supported, and there is new support for GET, but there is no support for changing folders or getting a folder listing.  In previous version there were some issue with handling file names that include non-English characters, and making connections to some device types failed.  Both are fixed in the current version.

This class can only send one file, closing the connection after it does so.  The Brecham.Obex library that we distribute can send more than one file without reconnecting.  That depends on the peer server being allowing that too.  See below for more information in this library.

Like the framework’s HttpWebResponse class etc, the ObexWebRequest signals an error by throwing a WebException and it includes the original error as the InnerException property.  Status code BadRequest indicates an error at connect time, in the network connection, the OBEX connection or even in the format or content of the URI.  Status code InternalServerError indicates an error during the transfer.

The OBEX protocol is used to provide access to various types of service; in Bluetooth we have for instance Push (OPP), FTP, and PBAP “Phone Book Access” etc.  Push normally allows PUT only and doesn’t require authentication whereas FTP allows general GET support and folder browsing but normally requires authentication. What service to connect to can be specified in the “scheme” of the URL passed to the ObexWebRequest constructor, see the class documentation.

Finally, note that the ObexWebRequest class itself makes the connection to the OBEX server, so don’t also call BluetoothClient.Connect(addr, BluetoothService.ObexObjectPush) directly or it will be blocked from doing so.

Behaviour from servers

Most devices with an OBEX Server don’t return a different response code depending whether the user accepted the received object or rejected it.  In most cases the server actually only prompts the user after the complete object has been received.  This is the case for all the portable devices that I've seen; for instance PalmOS devices and Nokia mobile phones — they return an error code only if the object was too big to be received for instance.  One device that does prompt before the object is received is Windows XP etc: the user is prompted, which blocks the continuing transfer until the user replies.

Server side

Is also supported.  Use code like the following.

Dim lsnr As New ObexListener(ObexTransport. Bluetooth)
lsnr.Start()
' For each connection
Dim ctx As ObexListenerContext = lsnr.GetContext()
Dim req As ObexListenerRequest = ctx.Request
Dim pathSplits() As String = req.RawUrl.Split('/')
Dim filename As String = pathSplits(pathSplits.Length – 1)
req.WriteFile(filename)
'
lsnr.Stop()

See also the DesktopListener and DeviceListener sample programs.

One active server

Note that only one OBEX server can be active on a particular protocol at any time.  If another server — for instance the operation-system supplied server — is active then the ObexListener will either fail at start-up (likely for IrDA), or will simply never receive any connections (likely for Bluetooth).

On desktop Windows the IrDA OBEX server is implemented by the Infrared Monitor/irmon service, so stop/disable that service to use your own server.  On the Microsoft stack the supplied Bluetooth OBEX server is a windows application that the user has to run manually, so there should be no general conflict.

On Windows CE/Mobile the system has a running OBEX server over both IrDA and Bluetooth which can be disabled by unchecking “Receive all incoming beams.” on the Settings > Connections > Beam control panel.  For programmatic control see http://msdn2.microsoft.com/en-us/library/aa916802.aspx and http://msdn2.microsoft.com/en-us/library/aa916361.aspx  Some HTC devices also include their own OBEX server software which needs to be disabled instead/also, with “option Enable Sharing Files (tab FTP, menu Configuration - Connections - Bluetooth)”.  Some also had to resort to also editing the registry see e.g. http://inthehand.co.uk/forums/p/2842/10481.aspx#10481

On Broadcom/Widcomm again the built-in server must be disabled.  There is a Widcomm API to act as part of the OBEX server perhaps we can use it to work without having to disable the server.  Something for the future perhaps…

Brecham.Obex

This is a separate library also available from the 32feet.net website.  It provides very complete OBEX support: PUT, GET, SETPATH, and Folder Listings.  It used by connecting to the server then carrying out one or more operations, it can thus be used to send more that one file without reconnecting.  It can also be used in an asynchronous manner to allow progress monitoring etc.

There is also an open-source library using it to provide server functionality.

General Bluetooth data connections

The library includes the BluetoothClient, BluetoothAddress, BluetoothEndPoint, and BluetoothListener classes.  So, to connect to the Serial port service on a particular peer device, use code like the following.

Dim addr As BluetoothAddress _
  = BluetoothAddress.Parse("001122334455")
'
Dim ep As New BluetoothEndPoint(addr, BluetoothService.SerialPort)
Dim cli As New BluetoothClient
cli.Connect(ep)
Dim peerStream As Stream = cli.GetStream()
peerStream.Write/Read ...

The Service Class Id should be changed to suit the service you are connecting too.

A connection can also be made to a particular port number.  There are also cases where the service identifies itself not by a unique Service Class Id but by a particular Service Name, for instance when a device has multiple virtual serial port services then the correct one can be identified by its name.  In that case see the “Connect by Service Name” section in the SDP chapter below.

Discovery

To discover and select the peer device at runtime, one can either discover the devices and select one in code, or display the UI device selection dialog.  One would use code like the following, respectively.

Dim cli As New BluetoothClient
Dim peers() As BluetoothDeviceInfo = cli.DiscoverDevices()
Dim device As BluetoothDeviceInfo = ... select one of peer()...
Dim addr As BluetoothAddress = device.DeviceAddress
...

and

Dim dlg As New SelectBluetoothDeviceDialog
Dim result As DialogResult = dlg.ShowDialog(Me)
If result <> DialogResult.OK Then
  Return
End If
Dim device As BluetoothDeviceInfo = dlg.SelectedDevice
Dim addr As BluetoothAddress = device.DeviceAddress
...

Finally, the discovered devices can be added to a Windows Form’s control with Data Binding, using code like the following, which uses a drop-down list box.

Dim cli As New BluetoothClient
Dim peers() As BluetoothDeviceInfo = cli.DiscoverDevices()
Me.ListBox1.DisplayMember = "DeviceName"
Me.ListBox1.ValueMember = Nothing
Me.ListBox1.DataSource = peers

A set of flags can be passed to DiscoverDevices to select which devices will be returned: ‘remembered’, ‘authenticated’, ‘unknown’, and ‘discoverable only’.  When ‘discoverable only’ (new in v2.4) is set, only devices in range and in discoverable mode will be returned.  When ‘unknown’ alone is set the same discoverable devices are found but any devices in the remembered set will be removed from the list.

On desktop Windows with the Microsoft stack there is no API to get the discoverable devices only, the remembered and discoverable devices are returned merged into one list.  ‘discoverable only’ thus does not work on that platform, it is undefined whether it will return all or zero devices.

Finally the maxDevices parameter allows the discovery process to complete early if and when the specified number of devices in range have been found.  It does not place a strict upper limit on the number of devices returned — particularly when remembered devices are to be included.  It is not supported on all platforms.

To remove devices from the list of remembered/authenticated devices use BluetoothSecurity.RemoveDevice, see the Bluetooth Security section below.

Asynchronous Device Discovery

We have new support in version 3 for ‘live’ discovery that’s where the application can get notification of each in-range device as it is discovered, instead of just getting the list of device at completion (as was previously provided by the DiscoveryDevices and End-/BeginDiscoveryDevices methods).  The new method is provided on class BluetoothComponent with events DiscoverDevicesProgress and DiscoverDevicesComplete and method DiscoverDevicesAsync.

This feature is beta, and it is currently supported on MSFT+CE/WM and Widcomm+both, and not on MSFT+Win32 where some native code is required.  Also SelectBluetoothDeviceForm has not been updated to use this feature.

DeviceName and discovery

Note, due to the way in which Bluetooth device discovery works, the existence and address of a device is known first, but a separate query has to be carried out to find whether the device also has a name.  One can also see this in the Windows’ Bluetooth device dialogs where the device appears first with its address and the name is later updated.  Bluetooth v2.1 supports extended discovery which supports sending the name in the first phase, both devices will need to support v2.1 for that to work.

This means that if a device is discovered afresh then the BluetoothDeviceInfo.DeviceName property might return only a text version of the device’s address and not its name.  To see the name, wait for some time and access this property again having called BluetoothDeviceInfo.Refresh in the meantime.

Server side

The BluetoothListener class provides server-sides connections.

Bluetooth applications/services are identified and registered by UUID (Universally Unique Id), a 128-bit value that is represented by the System.Guid class in .NET.  If one is creating a new service then a new UUID should be created at design time and entered into the two applications’ source code, a new value can be created either by calling Guid.NewValue or using the guidgen.exe Windows SDK program — in Visual Studio access it with menu item Tools, “Create GUID”.

One would thus use code like the following.

Class MyConsts
  Shared ReadOnly MyServiceUuid As Guid _
    = New Guid("{00112233-4455-6677-8899-aabbccddeeff}")
End Class

  ...
  Dim lsnr As New BluetoothListener(MyConsts.MyServiceUuid)
  lsnr.Start()
  ' Now accept new connections, perhaps using the thread pool to handle each
  Dim conn As New BluetoothClient = lsnr.AcceptBluetoothClient()
  Dim peerStream As Stream = conn.GetStream()
  ...

One can also pass the BluetoothListener a Service Name (v2.4), or a custom Service Record (Service Discovery Protocol record), and/or set Class of Service bit(s). To create a custom Service Record use ServiceRecordBuilder, see the SDP section below.

Note that unlike with TCP/IP and IrDA, if another server is already listening on a given UUID, then no error occurs, but the first running server will likely receive all connections — though which is chosen is somewhat arbitrary depending in part how the client device uses SDP.

Errors

The list of error codes used by Bluetooth sockets on the Microsoft stack on desktop Windows is described at “Bluetooth and connect” http://msdn2.microsoft.com/en-us/library/aa362901.aspx (the generic list of errors is at http://msdn.microsoft.com/en-us/library/ms740668.aspx), and at http://msdn.microsoft.com/en-us/library/aa923167.aspx for the Microsoft stack on CE/WM (generic errors).  Note that the error can sometimes be different from expected, for instance on connecting with BluetoothClient to a Blackberry which requires bonding the error that occurs is 10058 WSAESHUTDOWN (“The L2CAP channel disconnected by remote peer”), rather than say 10013 WSAEACCES (“Connecting application requested authentication”).

On the Broadcom stack Winsock isn’t used but instead we have to talk to a custom Widcomm API, we thus create the error IOException/SocketException instances ourself.  Also there isn’t a good correspondence between the errors reported by the Microsoft stack and the Widcomm stack, for instance the Widcomm stack reports the same error for every variety of the RFCOMM connect operation failing and connection closing.  Currently we use arbitrary error codes in the SocketExceptions, see the Widcomm document for more information.

Stream.Read and the number of bytes returned

Public Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer

In all situations there is no guarantee that any call to Stream.Read will return the full number of bytes requested in the count argument.  This is particularly the case on NetworkStream and SerialPort Stream etc.  The Read method is allowed to return as soon as one byte is available.  One must thus always use the return value, looping until the required number of bytes has been received, see the example below.

Read also returns when the ‘end’ of the stream has been reached, returning zero in that case.  For NetworkStream ‘end of stream’ occurs when the connection has been closed —when the connection was closed “gracefully”; closure due to error is signalled by an exception.

For more information see e.g. http://www.yoda.arachsys.com/csharp/readbinary.html

Stream.Read returning fewer bytes is more likely when there’s a network or other communications involved.  For instance all of today’s common transport protocols TCP/IP’s TCP, Bluetooth RFCOMM, and IrDA TinyTP are allowed to split and recombine the data as they see fit.  They are allowed to split for instance to carry the data within the maximum packet size, and they are allowed to coalesce data for instance for efficiency to fill a packet with data from multiple send calls.

The Connected property and connection loss

Two things happen when a connection is lost:

1. Both BluetoothClient.Connected and IrDAClient.Connected behave the same as Socket.Connected and TcpClient.Connected.  As described by MSDN, the Connected property reports the state as of the last I/O operation.  From MSDN TcpClient.Connected:

The Connected property gets the connection state of the Client socket as of the last I/O operation. When it returns false, the Client socket was either never connected, or is no longer connected.

Because the Connected property only reflects the state of the connection as of the most recent operation, you should attempt to send or receive a message to determine the current state. After the message send fails, this property no longer returns true. Note that this behavior is by design. You cannot reliably test the state of the connection because, in the time between the test and a send/receive, the connection could have been lost. Your code should assume the socket is connected, and gracefully handle failed transmissions.

2. Connection-loss detection

In my testing with Bluetooth it seems to take about twenty seconds for one device to realise that other device has gone -- though this may be configurable.  This is called the “Link Supervision Timeout” in Bluetooth. So you will have to wait up to that long for the system to know that the connection is lost, and until your next IO operation for you to know.  If you start a read when there is no data being received for instance, it will block until the connection fails, or is closed (or some data is received).

(Note that not all protocols do this detection.  The philosophy of TCP/IP is that it should try to survive outages in the network, this is does not send anything when there is no data to be sent.  So the only way to find out if a TCP connection is alive is to send some data and see if an error occurs after sending it).

IrDA behaves like Bluetooth; if the other IrDA device isn’t responding then the link it assumed broken, and the checking happens both when data is being transmitted and when the link is idle.

Connecting to Bluetooth services

Sometimes we don’t want our application to itself send data to/from a remote service but we want instead the local operating system to do so.  This is the case for keyboard/mouse/etc with HID, networking with DUN/NAP/PAN/etc, Headset/Handsfree etc.

The short answer in this case is to use BluetoothDeviceInfo.SetServiceState.  This is the API equivalent to manually checking the respective checkbox on the “Services” tab of the Device dialog in Bluetooth Control panel.

For HID this will allow you to access the input keys/movements via the Windows’ HID API.  This is for instance used by the Wiimote project.  This should also work for Handsfree and Headset (on Windows 7).  It probably won’t work for network connections.

However this API is provided only on desktop Windows with the Microsoft stack.  It is not supported at all on Widcomm.  We manually try to reproduce this behaviour on CE/WM by editing the Registry and thus only support a few services there.

General IrDA connections

The IrDA classes are quite similar to the Bluetooth ones.  In IrDA, a service / application is identified by a textual Service Name.  One can thus use code like the following.

This sample automatically chooses the (hopefully) single device in range.

Dim cli As New IrDAClient("MyCustomServiceName") 'Or "OBEX", "IrDA:IrCOMM" etc
...

Server side.

Dim lsnr As New IrDAListener("MyCustomServiceName")
lsnr.Start()
...

The DiscoverDevices method is present on IrDAClient too, but there is no equivalent to the SelectBluetoothDeviceDialog for IrDA currently.

The two connection modes IrCOMM and IrLMP (aka IrLPT) can be set by socket option.  See the IrDAServiceClient samples or the samples on Alan’s website http://www.alanjmcf.me.uk/

Bluetooth settings, device information etc

Peer Device information

As seen above, the Bluetooth discovery operation returns an array of type BluetoothDeviceInfo; as well as providing the Bluetooth Device Address this class also provides access to the device’s name, its class-of-device bits, its SDP service records, the RSSI measurement, the time the device was last connected to, whether it was remembered from a previous discovery process, and whether it is authenticated etc.

In particular, on the Microsoft stack the RSSI property is only supported on the WM6 platform, devices known to support it include the HTC Trinity and the Symbol MC35; it is not supported on desktop Windows.  On WM/CE, the native APIs used by the Rssi and ClassOfDevice properties require an open connection to the target device.  The CoD value can be read at discovery time when the device is in range, for the Rssi property and if we didn’t learn the CoD value at discovery time, when the get property is called we internally attempt to form a (ACL) connection to allow the native API to work.  On Widcomm we don’t currently attempt to form the connection, RSSI will thus fail if there is no existing connection.

There is also a LastSeen property which reports the value provided by desktop Windows — however the value seems broken however, always just being the time of the discovery operation.  However, we set the value correctly on WM/CE with the Microsoft stack, and on both platforms with the Widcomm stack.

The SDP lookup operation GetServiceRecords can be used to find the details of the services running on a peer device —the SdpBrowserDesktop/-PPC samples programs can be used to inspect the records on devices in range, and see below for more information on using SDP records.  Finally, the InstalledServices property does not list all the services a peer device supports, but instead reports those that the local machine is configured to use — on desktop Windows, those that are ticked on the Services page of the Bluetooth Device property sheets.  This property is currently not supported on Widcomm.

Local Radio information

Local Bluetooth Radios are represented by the BluetoothRadio class, and an instance representing the primary radio can be accessed via the BluetoothRadio.PrimaryRadio static (Shared in Visual Basic) property.  The class provides various information about the radio: the Bluetooth Address, the Class of Devices bits and the manufacturer, etc. The radio mode can be configured be setting the Mode property: it can be enabled, disabled, and put in discoverable mode.  See the example below.

Public Shared Sub DisplayBluetoothRadio()
        Dim myRadio As BluetoothRadio = BluetoothRadio.PrimaryRadio
        If myRadio Is Nothing Then
            Console.WriteLine("No radio hardware or unsupported software stack")
            Return
        End If
        Dim mode As RadioMode = myRadio.Mode
        ' Warning: LocalAddress is null if the radio is powered-off.
        wtr.WriteLine("* Radio, address: {0:C}", myRadio.LocalAddress)
        Console.WriteLine("Mode: " & mode.ToString())
        Console.WriteLine("Name: " & myRadio.Name _
            & ", LmpSubversion: " & myRadio.LmpSubversion)
        Console.WriteLine("ClassOfDevice: " & myRadio.ClassOfDevice.ToString() _
            & ", device: " & myRadio.ClassOfDevice.Device.ToString() _
            & " / service: " & myRadio.ClassOfDevice.Service.ToString())
        '
        '
        ' Enable discoverable mode
        Console.WriteLine()
        myRadio.Mode = RadioMode.Discoverable
        Console.WriteLine("Radio Mode now: " & myRadio.Mode.ToString())
    End Sub

There is also a static property BluetoothRadio.AllRadios to fetch all the local radios.  However the supported Bluetooth stack software (Microsoft and Widcomm) each support one radio.

Note, if there is no supported local radio then BluetoothRadio.PrimaryRadio will return null, as shown in the sample; and BluetoothRadio.AllRadios will return an empty array.  Also BluetoothRadio.LocalAddress can be null on the instance returned when the radio is powered-off.

Bluetooth Serial Ports

By far the easiest and best way to use a serial port connection is to use BluetoothClient with service class BluetoothService.SerialPort, i.e. using code very much like shown above.  Both BluetoothClient and virtual serial ports use the RFCOMM protocol so the two are equivalent.  This method is much easier to set-up that a virtual serial port, there is no global state to configure etc; and is also more robust, for instance if the peer device is taken out of range or turned-off one learns this directly soon after, whereas with a serial port one has to use timeouts and retries to detect it.

However there are cases where a virtual serial port is required, for instance where another program needs to access the connection — a common case is where your program is configuring a connection that will be used by a printer driver utility.

On Win32, to create a Bluetooth virtual serial port one can use BluetoothDeviceInfo.SetServiceState, passing in service class SerialPort.  Unfortunately the name of the COM port created is not returned — that’s because the native API does not tell!  One way to find the name of the port created is to call the System.IO.Ports.SerialPort.GetPortNames method before and after the SetServiceState call and see which name is new.

On Windows Mobile, two methods to create a port exist in the library, the first is class BluetoothSerialPort, this creates a connection immediately but the underlying API it uses is rather unreliable, and it seems not to work at all on various device types.  The second is BluetoothDeviceInfo.SetServiceState as for Win32, this manually configures the necessary Registry settings and is reliable but requires a reboot before the port becomes available, and again the name of the new port is not returned.

On Widcomm, on both platforms we currently support neither method.

Getting virtual COM port names for remote Bluetooth devices

On Win32 to find which virtual COM port is for which remote device use WMI to query the serial ports; the remote device address is included in the PnP Id.  In the following PowerShell example see the remote address as “00803A686519”.

C:\> Get-WmiObject -query "select DeviceID,PNPDeviceID from Win32_SerialPort"
DeviceID     : COM66
PNPDeviceID  : BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}\7&1D80ECD3&0&00803A686519_C00000003
… …

In C# for instance:

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

On CE/WM there is no WMI, so one has to resort to inspecting the Registry directly.  For instance:

using (var portsK = Registry.LocalMachine.OpenSubKey(
          @"Software\Microsoft\Bluetooth\Serial\Ports", false)) {
   if (portsK == null) {
      console.WriteLine("Bluetooth COM port configuration is missing.");
      return;
   }
   foreach (var devKeyName in portsK.GetSubKeyNames()) {
      BluetoothAddress addr;
      try {
         addr = BluetoothAddress.Parse(devKeyName);
      } catch (FormatException) {
         console.WriteLine("Error: key name is not a device address: '{0}'", devKeyName);
         continue;
      }
      console.WriteLine("DeviceID:    {0} ", addr);
      using (var devKey = portsK.OpenSubKey(devKeyName)) {
         var portN0 = devKey.GetValue("Port");
         var portN1 = portN0 as string;
         var portN = TruncateAtZeros(portN1);
         console.WriteLine("PortName: '{0}'", portN);
      }
   }//for
}

Bluetooth Security

Firstly there is a facility to actively request a bond.  The method is BluetoothSecurity.PairRequest( BluetoothAddress device, string pin).  It is supported on all platforms (MSFT+Win32, MSFT+CE/WM and Widcomm+both).

The corresponding method to delete a bonding is BluetoothSecurity.RemoveDevice( BluetoothAddress device).

Then we support the facility to specify a passphrase/pin that will be used when connecting if bonding is required (i.e. passively).  This is the area where the support from the Bluetooth stacks varies the most, some stacks support many security features and some hardly any.  Thus the support we can provide varies per platform.

On BluetoothClient we support SetPin(string pin) on MSFT+CE/WM and on MSFT+Win32 (with some ‘behind the scenes’ complexity on the latter).  There is no support for this feature in the Widcomm API, we’ve tried implementing a solution but it will likely not work for general usage.  Let me know if it works for you.  There is also method SetPin(BluetoothAddress device, string pin), support for it is implemented on the two MSFT platforms only.  (Note: The MSDN CE/WM documentation is slightly ambiguous and it’s not 100% clear that specifying the remote address there restricts the use of the pin to that device).

We’d like to provide similar support on BluetoothListener.  However the lack of support from the platform APIs makes implementation even more difficult if not impossible there.  We support SetPin(BluetoothAddress device, string pin), on the two MSFT platforms currently.

We’ve had some changes over different versions in these two classes (for the MSFT support):

BluetoothClient

v2.4

Widcomm

v2.3

v2.2

Pin property

n/a

n/a

n/a

SetPin(string pin)

kind-of…

— when connected
— when not (NRE)

SetPin(BluetoothAddress device, string pin)

— CE/WM
— Win32

 

BluetoothListener

v2.4

Widcomm

v2.3

v2.2

SetPin(string pin)

(NullRefEx)

SetPin(BluetoothAddress device, string pin)

 

Finally there are some other features.  Firstly, on the MSFT+Win32 stack, the facility for passively bonding is provided by handling a global event.  Support for it is in class BluetoothWin32Authentication, it is used internally by BluetoothClient but can be used directly by user code.  It operates in two modes, firstly if an address and pin are specified it will then automatically respond if that device requires bonding, in the second mode a callback event is raised to user code when any device requires bonding.  Secondly there are five other methods on BluetoothSecurity, they are CE/WE only and apparently are for callback based bonding.

Bluetooth SDP — Service Discovery Protocol

The library contains support for creating, parsing, and dumping SDP records.  This will generally be used in advanced scenarios only.  For instance BluetoothListener by default creates a basic record based on the Service Class Id passed in.

To retrieve and dump the records containing a given UUID on a particular device one can use code like the following.

Guid uuid = ...
BluetoothDeviceInfo device = ...
ServiceRecord[] records = device.GetServiceRecords(uuid);
foreach(ServiceRecord curRecord in records) {
   ServiceRecordUtilities.Dump(curRecord, Console.Out);
}

For instance to see whether the device has an an Serial Port service search for UUID BluetoothService.SerialPort, or too find all the services that use RFCOMM use BluetoothService.RFCommProtocol, or to find all the services use BluetoothService.L2CapProtocol.

Creating Records

BluetoothListener creates a record automatically, and will include the Service Class Id List and a RFCOMM Protocol Descriptor List, and optionally (v2.4) a Service Name attribute.  If you need to advertise a custom record with more attributes then see the information below.

Creating a record is relatively simple; however the format of the records is relatively complex.  Therefore we provide a class implementing the ‘Builder’ pattern for ServiceRecord creation.  One needs to set at least the class id of the service that the server is providing.  One can however also add Service Name, Provider Name attributes etc, specify that the service uses the GOEP (OBEX) protocol stack, and other things including even adding custom attributes.

A simple example is:

ServiceRecordBuilder bldr = new ServiceRecordBuilder();
bldr.AddServiceClass(BluetoothService.SerialPort);
bldr.ServiceName = "Alan's SPP service";
//
// For numerical values need strict cast or use CreateNumericalServiceElement
bldr.AddCustomAttribute(new ServiceAttribute(0x8001,
   ServiceElement.CreateNumericalServiceElement(ElementType.UInt16, 0xFEDC)));
//
ServiceRecord record = bldr.ServiceRecord;

And an example of a standard Bluetooth service is:

ServiceRecordBuilder bldr = new ServiceRecordBuilder();
bldr.AddServiceClass(BluetoothService.Headset);
bldr.AddServiceClass(BluetoothService.GenericAudio);
bldr.AddBluetoothProfileDescriptor(BluetoothService.Headset, 1, 0);
bldr.AddCustomAttribute(new ServiceAttribute(
    HeadsetProfileAttributeId.RemoteAudioVolumeControl,
    new ServiceElement(ElementType.Boolean, true)));
//
ServiceRecord record = bldr.ServiceRecord;

As discussed in the BluetoothListener section above, the service class id should be a new UUID if you are providing a custom/non-standard service.

Connect by Service Name

There are cases where the service identifies itself not by a unique Service Class Id but by a particular Service Name.  In these cases one should query the service records on the remote device, find which contains the expected Service Name, and use the RFCOMM channel (port) number from the record in the BluetoothEndPoint passed to Connect.  For example:

Dim expectedSvcName As String = "FooBar"
Dim addrS As String = "001122334455"
Dim commonClass As Guid = BluetoothService.SerialPort ' e.g
Dim addr As BluetoothAddress = BluetoothAddress.Parse(addrS)
'
Dim bdi As New BluetoothDeviceInfo(addr)
Dim rcdList() As ServiceRecord = bdi.GetServiceRecords(commonClass)
Dim curSvcName As String
Dim
portInteger As Integer = -1
For Each record As ServiceRecord In rcdList
  portInteger = ServiceRecordHelper.GetRfcommChannelNumber(record)
  Try
    curSvcName = record.GetPrimaryMultiLanguageStringAttributeById( _
      UniversalAttributeId.ServiceName)
  Catch ex As KeyNotFoundException ' No ServiceName attribute
    Continue For
  End Try
  Debug.Assert(curSvcName IsNot Nothing, "null ServiceName!?")
  If expectedSvcName.Equals(curSvcName, StringComparison.InvariantCulture) Then
    If portInteger = -1 Then
      Throw New InvalidOperationException("Selected Service is not RFCOMM")
    End If
    Exit For ' Success!
  End If
Next
If
portInteger = -1 Then
  Throw New InvalidOperationException("No Service found with the ServiceName")
End If
Dim
port As Byte = CByte(portInteger) ' convert to byte now we know that it's valid
Dim rep As New BluetoothEndPoint(addr, BluetoothService.Empty, port)
Dim cli As New BluetoothClient()
cli.Connect(rep)
... ...

Manual record creation

There should be no need to create a record manually as follows, use ServiceRecordBuilder instead.

The format of the simplest record to advertise an RFCOMM service contains two attributes: one to identify the service by its UUID, and one to provide the RFCOMM Channel Number that the service is listening on — and it contains five elements, so we provide a helper method to create it.  So, code the like following is necessary.  When BluetoothListener is passed such a record, at Start() it will update the record and set the Channel Number byte element to the active channel number.

ServiceElement pdl = ServiceRecordHelper.CreateRfcommProtocolDescriptorList();
ServiceElement classList = new ServiceElement(ElementType.ElementSequence,
   new ServiceElement(ElementType.Uuid128, serviceClassUuid));
ServiceRecord record = new ServiceRecord(
   new ServiceAttribute(UniversalAttributeId.ServiceClassIdList, classList),
   new ServiceAttribute(UniversalAttributeId.ProtocolDescriptorList, pdl));

Adding elements of type ‘string’ to a record is even more complex, as the strings in the base specification (ServiceName, ProviderName, etc) are defined in a very baroque manner to allow multiple language versions.  Thus, code like the following is required.

ServiceElement strName = new ServiceElement(ElementType.TextString, "hello world");
ServiceElement langBaseList = CreateEnglishUtf8PrimaryLanguageServiceElement();
'
ServiceRecord record = new ServiceRecord(
   ...
   new ServiceAttribute(UniversalAttributeId.LanguageBaseAttributeIdList,
      langBaseList),
   new ServiceAttribute(ServiceRecord.CreateLanguageBasedAttributeId(
         UniversalAttributeId.ServiceName,
         LanguageBaseItem.PrimaryLanguageBaseAttributeId),
      strName)
);

... ...

private static ServiceElement CreateEnglishUtf8PrimaryLanguageServiceElement()
{
   ServiceElement englishUtf8PrimaryLanguage
      = LanguageBaseItem.CreateElementSequenceFromList(
         new LanguageBaseItem[] {
            new LanguageBaseItem("en", LanguageBaseItem.Utf8EncodingId,
               LanguageBaseItem.PrimaryLanguageBaseAttributeId)});
   return englishUtf8PrimaryLanguage;
}

See the source to ObexListener (ObexListener.cs) for a real example.



[1] “v6 use custom bluecove.dll build” at http://code.google.com/p/bluecove/wiki/stacks