A WP8 app I’m working on needs Bluetooth
image transfer capabilities built in, so I started looking at the Microsoft
examples and samples as a starting point. For starters I used the following MSDN
article:
The article suggests that you can connect to
a device with an empty service name, since the selected device returns an empty
string:
// Make sure ID_CAP_NETWORKING is enabled in your WMAppManifest.xml, or
the next
// line will throw an Access Denied exception.
await
socket.ConnectAsync(selectedDevice.HostName, selectedDevice.ServiceName);
This is not the case as a GUID format service
address must be supplied which matches one of the listed device Bluetooth capabilities:
- Advanced Audio Distribution Profile (A2DP 1.2)
- Audio/Video Remote Control Profile (AVRCP 1.4
- Hands Free Profile (HFP 1.5)
- Phone Book Access Profile (PBAP 1.1)
- Object Push Profile (OPP 1.1)
- Out of Band (OOB) and Near Field Communications (NFC)
There are a couple of other examples but they aren't particularly helpful either.
I’ve never had any dealing with Bluetooth
before, but done lots of TCP and serial comms and got a few pointers from Mike
Hole to get me started. At first I looked at the various Bluetooth specs from here (I always like to see protocol specs when working on this kind of thing):
Some of these make good background reading,
in particular the following:
General Object Exchange Profile (GOEP)
Object Push Profile:
However these don’t provide the level of
detail required as we need byte-level description of the protocol. For this you need IrDA
Object Exchange Protocol (OBEX) spec which is not available without being a member of IrDA but I
found a copy after a bit of googling. There are two other profiles which looked applicable to transferring an image, FTP and BIP (Basic Imaging Profile) but the only applicable profile the phone supports is OPP, which can be used to transfer objects such as files.
Anyway, down to the code. These are a few
methods which connect to a peer device, then send bytes from a file (for me
this was an image) in packets and then disconnect.
Peer Search
This bit is the same as the examples:
private async void Search()
{
this.IsLoading = true;
this.IsShareExecutable = false;
this.Message = Resources.StringTable.BTSearching;
// Note:
You can only browse and connect to paired devices!
//
Configure PeerFinder to search for all paired devices.
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
this.pairedDevices = await PeerFinder.FindAllPeersAsync();
if (pairedDevices.Count == 0)
{
this.Message = Resources.StringTable.BTNoDevices;
}
else
{
await this.Share();
}
}
Socket Connect
In my test, once a device was found, I went
straight in to share a file from the first device:
StreamSocket _stream = null;
DataWriter _dataWriter;
DataReader _dataReader;
private async Task Share()
{
this.CreatePackets();
//
Select a paired device. In this example, just pick the first one.
PeerInformation selectedDevice = pairedDevices[0];
int count = pairedDevices.Count;
//
Attempt a connection
_stream = new StreamSocket();
try
{
// Make sure ID_CAP_NETWORKING is enabled in your
WMAppManifest.xml, or the next
// line will throw an Access Denied exception.
string oopUUID = "{00001105-0000-1000-8000-00805f9b34fb}";
await _stream.ConnectAsync(selectedDevice.HostName, oopUUID);
_dataWriter = new DataWriter(_stream.OutputStream);
_dataReader = new DataReader(_stream.InputStream);
// Send
data
int maxServerPacket = await this.ObexConnect();
if (maxServerPacket > 0)
{
if (await ObexPushRequest())
{
//
Success
}
else
{
// Failed
}
}
this.ObexDisconnect();
}
catch (Exception ex)
{
this.IsLoading = false;
this.Message = Resources.StringTable.BTFailed;
}
}
This method opens a connection using the OOP
UUID GUID, creates data packets, then performs an OBEX connect, followed by an
object push and finally a disconnect.
Obex Connect
private async Task<int> ObexConnect()
{
//send
client request
byte[] ConnectPacket = new byte[7];
ConnectPacket[0] = 0x80; //
Connect
ConnectPacket[1] = (7 & 0xFF00) >>
8; //
Packetlength Hi Byte
ConnectPacket[2] = (7 & 0xFF); //
Packetlength Lo Byte
ConnectPacket[3] = 0x10; // Obex
v1
ConnectPacket[4] = 0x00; // No
flags
ConnectPacket[5] = (2048 & 0xFF00)
>> 8; // 2048 byte client max packet size Hi Byte
ConnectPacket[6] = (2048 & 0xFF); // 2048 byte max packet size Lo Byte
_dataWriter.WriteBytes(ConnectPacket);
await _dataWriter.StoreAsync();
// Get
response code
await _dataReader.LoadAsync(1);
byte[] buffer = new byte[1];
_dataReader.ReadBytes(buffer);
if (buffer[0] == 0xA0) //
Sucess
{
// Get
length
await _dataReader.LoadAsync(2);
buffer = new byte[2];
_dataReader.ReadBytes(buffer);
int length = buffer[0] << 8;
length += buffer[1];
// Get
rest of packet
await _dataReader.LoadAsync((uint)length - 3);
buffer = new byte[length - 3];
_dataReader.ReadBytes(buffer);
int obexVersion = buffer[0];
int flags = buffer[1];
int maxServerPacket = buffer[2] << 8 + buffer[3];
return maxServerPacket;
}
else
{
return -1;
}
}
This establishes a connection and lets the
server know the clients buffering capabilities. The client buffer can be something
like 256 bytes to 64k - 1. The server then responds with it’s
details, but on the laptop seemed to send back the client buffer size.
Create Packets
This method chops the file into 1k chunks and
adds the necessary headers. The first packet has extra information about the
file like it’s name and size and the last packet has a different op-code to
indicate that the transmission has finished. The data to be transferred is in the _shareData variable.
private void CreatePackets()
{
int bodyLength = 1024;
this._packets = new List<byte[]>();
// Chop
data into packets
int blocks = (int)Math.Ceiling((decimal)this._shareData.Length /
bodyLength);
System.Text.UnicodeEncoding encoding = new System.Text.UnicodeEncoding(true, false);
byte[] encodedName = encoding.GetBytes(FILE_NAME + new char());
for (int i = 0; i < blocks; i++)
{
int headerLength = i == 0 ? 14 + encodedName.Length : 6;
// Chop
data into body
byte[] body = null;
if (i < blocks - 1)
body = new byte[bodyLength];
else
body = new byte[this._shareData.Length - (i * bodyLength)];
System.Buffer.BlockCopy(this._shareData, i * bodyLength, body, 0, body.Length);
//
Create packet
byte[] packet = new byte[headerLength + body.Length];
this._packets.Add(packet);
// Build
packet
int offset = 0;
packet[offset++] = i != blocks - 1 ? (byte)0x02 : (byte)0x82; // 0x02 for first blocks, 0x82 for last
packet[offset++] = (byte)((packet.Length &
0xFF00) >> 8);
packet[offset++] = (byte)(packet.Length & 0xFF);
//
Payload details on first packet
if (i == 0)
{
packet[offset++] = 0x01; // Name header
packet[offset++] = (byte)(((encodedName.Length + 3)
& 0xFF00) >> 8);
packet[offset++] = (byte)((encodedName.Length + 3)
& 0xFF);
System.Buffer.BlockCopy(encodedName, 0, packet, offset,
encodedName.Length);
offset += encodedName.Length;
packet[offset++] = 0xC3; // Length header
packet[offset++] = (byte)((this._shareData.Length &
0xFF000000) >> 24);
packet[offset++] = (byte)((this._shareData.Length &
0xFF0000) >> 16);
packet[offset++] = (byte)((this._shareData.Length &
0xFF00) >> 8);
packet[offset++] = (byte)(this._shareData.Length &
0xFF);
}
packet[offset++] = 0x48; // Object body chunk header
packet[offset++] = (byte)(((body.Length + 3) &
0xFF00) >> 8);
packet[offset++] = (byte)((body.Length + 3) &
0xFF);
System.Buffer.BlockCopy(body, 0, packet, offset, body.Length);
}
}
Push Request
Once the packets have been built sending them
is very easy to do:
private async Task<bool> ObexPushRequest()
{
foreach (var packet in this._packets)
{
_dataWriter.WriteBytes(packet);
await _dataWriter.StoreAsync();
// Get
response code
await _dataReader.LoadAsync(3);
byte[] buffer = new byte[3];
_dataReader.ReadBytes(buffer);
// If
not success and not continue it's an error
if (buffer[0] != 0xA0 && buffer[0] != 0x90)
return false;
else if (buffer[0] == 0xA0) //
Success
return true;
}
return false;
}
Once the server has received a packet, it
issues an 0x90 to say continue and once complete, 0xA0 for success.
Disconnect
Once finished a disconnect message can be
sent, but it is not necessary and most devices and clients don’t implement it,
so just disposing the IO objects will probably do:
private async void ObexDisconnect()
{
byte[] bytes = new byte[3];
bytes[0] = 0x81;
bytes[1] = 0;
bytes[2] = 3;
_dataWriter.WriteBytes(bytes);
await _dataWriter.StoreAsync();
await _dataReader.LoadAsync(3);
byte[] response = new byte[3];
_dataReader.ReadBytes(response);
_stream.Dispose();
_stream = null;
_dataReader.Dispose();
_dataWriter.Dispose();
}
Testing
As long as a device is paired and waiting to
accept a file, this should work nicely. In windows to wait for a file,
right-click the Bluetooth icon in the system tray and select ‘Receive a File’.
If everything works, you’ll see the file name appear and a progress bar as the
file transfers.
Please could you send me the full source code to my email (ali_f_haider@yahoo.com) I would be very thankfull
ReplyDeleteJust mailed you a page and view model ;)
ReplyDeleteIt is not in my Email, Are u sure u spelled the email correctly:
DeleteAli_f_haider@yahoo.com
you can use this one also:
Ali_f_haider@hotmail.com
or this one:
Ali_f_haider@excite.com
or this one:
ali.f.haider@gmail.com
Try your Hotmail
ReplyDeleteThank you so much I found it in my Hotmail ,I sounds more complicated than it does look
ReplyDeleteI deleted some of the procedure to try it but I noticed It is a bit different than what is on the website.
ReplyDeleteAfter trying it my Pc detects the call but did not receive the file as intended ,actually it hangs on this line
Await _dataReader.LoadAsync(3)
in this function
Private Async Function ObexPushRequest() As Task(Of Boolean)
Dim ss As Integer = 100 / Me._packets.Count
For Each packet In Me._packets
Me.Progress += ss
_dataWriter.WriteBytes(packet)
Await _dataWriter.StoreAsync()
' Get response code (Hangs on this line...)
Await _dataReader.LoadAsync(3)
Dim buffer As Byte() = New Byte(2) {}
_dataReader.ReadBytes(buffer)
' If not success and not continue it's an error
If buffer(0) <> &HA0 AndAlso buffer(0) <> &H90 Then
Return False
ElseIf buffer(0) = &HA0 Then
' Success
Return True
End If
Next
Return False
End Function
I took a snapshot of the window
http://i40.tinypic.com/34zh7vd.jpg
you can see that the file name is absent !
I wonder If I edited to much or something else
by the way I tried file size 50 kb and 1.5 Mb as I was thinking that the problem could be in filesize
If it's hanging there, it's not getting a response back. Did you translate the create packets method into vb ok? You can send me your code and I'll have a look over next few days if you like...
ReplyDeletehey I found this helpfull. Can you mail me a copy too. abhinav.sayan@gmail.com
ReplyDeleteCould you please mention the class variable declaration?
ReplyDeleteJust mailed you...
ReplyDeleteWorks like a charm. Thank you!
ReplyDeleteIn ObexPushRequest, LoadAsync throws exception saying aurgument is out of range( _dataReader trying to read outside range). if i comment reading part, then after 1 package is sent, i get "An established connection was aborted by the software in your host machine. (Exception from HRESULT: 0x80072745)". I m trying to send a .xlsx file
ReplyDeleteSounds like there is nothing in the input stream, does it connect ok?
ReplyDeleteOk now its working partially. It connects to other device, sends 1st packet correctly gets 0x90 code, but on sending 2nd packet, response is of lenght 5 and is 0x00.
DeleteHi Geoff, great post. Could you please email me a copy of the code to sarel@iworksocially.com. Would you know if its possible to transfer a video stream from the device's camera? Some pointers be great.
ReplyDeletePublished here: http://geoffwebbercross.blogspot.co.uk/2013/11/sharing-file-from-wp8-using-bluetooth.html
DeleteYou couldn't send a stream as it's for files, you could send a video file though. If you wanted to stream you would need to use a different protocol or make your own.
Hi Geoff. Could you please email me a copy of the code to hoainam.bkhnvn@gmail.com .Thank you!
ReplyDeleteCheck the comment above ;-)
ReplyDeletehi Geoff. could you publish a sample please i try to copy your code in the that link(http://geoffwebbercross.blogspot.co.uk/2013/11/sharing-file-from-wp8-using-bluetooth.html) to my app but i got errors which i don't have the experience to handle it so i need a sample . Thanks For help
ReplyDeleteCheck the comment above ;-)
Delete