Connect a barcode reader to a Xamarin Forms app via Bluetooth

Recently, in Xamarin Forums, have appeared some posts about “how to read barcodes with Xamarin”.

I have already covered the topic in this post some days ago, and I would like to add some thoughts.

First of all, how can I read a barcode? I think there are three solutions.

  • using a device with an integrated barcode scanner. These are often “rugged” (and expansive) devices used for enterprise apps. For example, I am developing an app for a great supermarket chain and they use this kind of device. Performance are higher, speed is higher. The best choice
  • using a device with a camera. This is the worst choice, but is the only choice for smartphone. Every smartphone has a camera, so, every smartphone can read a barcode. The biggest problem is performance. The reading speed is low, the camera has to focus the barcode and the battery runs out quickly. Can be good only for some commercials app downloaded from stores

In addition to these two choices, can we find the third? Yes of course. Maybe not so good like an integrated scanner, but absolutely better than camera. This choice can be a device connected to a bluetooth scanner. Are you laughing? Why?

A bluetooth scanner is born to read barcodes (a camera is born to take photo…) so performance are very high, has it’s integrated battery and there are a lot of models:

  • pistol grip
  • ring scanner
  • pocket scanner

I have created a little repo on GitHub. It’a a good starting point to connect a scanner to an Android device via bluetooth. It use Xamarin Forms to update UI, but the big work is in Android Project.

Which are interesting part of this app?

  1. Return a list of paired devices
  2. Start and Stop receiving data from the scanner
  3. Visualize data received in a list

For point 1 and 2 it uses Dependency services. First of all, in Xamarin Forms project, you should create an Interface like this:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace TestBth
{
	public interface IBth
	{
		void Start(string name, int sleepTime, bool readAsCharArray);
		void Cancel();
		ObservableCollection<string> PairedDevices();
	}
}

You found three methods:

  • Start: it starts the reading service from Bluetooth Socket
  • Cancel: it stops the reading service
  • PairedDevices: it returns a list of paired devices

Then in your Android project you should have Interface implementation

Android implementation

using System;
using Android.Bluetooth;
using Java.Util;
using System.Threading.Tasks;
using Java.IO;
using TestBth.Droid;
using System.Threading;
using System.Collections.Generic;
using System.Collections.ObjectModel;

[assembly: Xamarin.Forms.Dependency (typeof (Bth))]
namespace TestBth.Droid
{
	public class Bth : IBth
	{

		private CancellationTokenSource _ct { get; set; }

		const int RequestResolveError = 1000;

		public Bth ()
		{
		}

		#region IBth implementation

		/// <summary>
		/// Start the "reading" loop 
		/// </summary>
		/// <param name="name">Name of the paired bluetooth device (also a part of the name)</param>
		public void Start(string name, int sleepTime = 200, bool readAsCharArray = false){
			
			Task.Run (async()=>loop(name, sleepTime, readAsCharArray));
		}



		private async Task loop(string name, int sleepTime, bool readAsCharArray){
			BluetoothDevice device = null;
			BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
			BluetoothSocket BthSocket = null;

			_ct = new CancellationTokenSource ();
			while (_ct.IsCancellationRequested == false) {
			
				try {
					Thread.Sleep (sleepTime);

					adapter = BluetoothAdapter.DefaultAdapter;

					if(adapter == null)
						System.Diagnostics.Debug.WriteLine("No Bluetooth adapter found.");
					else
						System.Diagnostics.Debug.WriteLine ("Adapter found!!");

					if(!adapter.IsEnabled)
						System.Diagnostics.Debug.WriteLine("Bluetooth adapter is not enabled."); 
					else
						System.Diagnostics.Debug.WriteLine ("Adapter enabled!");

					System.Diagnostics.Debug.WriteLine("Try to connect to " + name);

					foreach (var bd in adapter.BondedDevices) {
						System.Diagnostics.Debug.WriteLine ("Paired devices found: " + bd.Name.ToUpper ());
						if (bd.Name.ToUpper().IndexOf (name.ToUpper ()) >= 0) {

							System.Diagnostics.Debug.WriteLine("Found " + bd.Name + ". Try to connect with it!");
							device = bd;
							break;
						}
					}

					if (device == null)
						System.Diagnostics.Debug.WriteLine ("Named device not found.");
					else {
						UUID uuid = UUID.FromString ("00001101-0000-1000-8000-00805f9b34fb");
						if((int)Android.OS.Build.VERSION.SdkInt >= 10) // Gingerbread 2.3.3 2.3.4
							BthSocket = device.CreateInsecureRfcommSocketToServiceRecord (uuid);
						else
							BthSocket = device.CreateRfcommSocketToServiceRecord (uuid);
					
						if (BthSocket != null) {


							//Task.Run ((Func<Task>)loop); /*) => {
							await BthSocket.ConnectAsync ();


							if(BthSocket.IsConnected){
								System.Diagnostics.Debug.WriteLine("Connected!");
								var mReader = new InputStreamReader(BthSocket.InputStream);
								var buffer = new BufferedReader(mReader);
								//buffer.re
								while (_ct.IsCancellationRequested == false){
									if(buffer.Ready ()){
										//										string barcode =  buffer
										//string barcode = buffer.

										//string barcode = await buffer.ReadLineAsync();
										char[] chr = new char[100];
										//await buffer.ReadAsync(chr);
										string barcode = "";
										if (readAsCharArray) { 
										
											await buffer.ReadAsync(chr);
											foreach (char c in chr) {

												if (c == '\0')
													break;
												barcode += c;
											}

										}else
											barcode = await buffer.ReadLineAsync();
										
										if(barcode.Length > 0){
											System.Diagnostics.Debug.WriteLine("Letto: " + barcode);
											Xamarin.Forms.MessagingCenter.Send<App, string> ((App)Xamarin.Forms.Application.Current, "Barcode", barcode);
										}
										else
											System.Diagnostics.Debug.WriteLine ("No data");

									}
									else
										System.Diagnostics.Debug.WriteLine ("No data to read");

									// A little stop to the uneverending thread...
									System.Threading.Thread.Sleep (sleepTime);
									if(!BthSocket.IsConnected){
										System.Diagnostics.Debug.WriteLine ("BthSocket.IsConnected = false, Throw exception");
										throw new Exception();
									}
								}

								System.Diagnostics.Debug.WriteLine ("Exit the inner loop");

							}
						}
						else
							System.Diagnostics.Debug.WriteLine ("BthSocket = null");

					}


				}
				catch{
				}

				finally{
					if (BthSocket != null)
						BthSocket.Close ();
					device = null;
					adapter = null;
				}			
			}

			System.Diagnostics.Debug.WriteLine ("Exit the external loop");
		}

		/// <summary>
		/// Cancel the Reading loop
		/// </summary>
		/// <returns><c>true</c> if this instance cancel ; otherwise, <c>false</c>.</returns>
		public void Cancel(){
			if (_ct != null) {
				System.Diagnostics.Debug.WriteLine ("Send a cancel to task!");
				_ct.Cancel ();
			}
		}

		public ObservableCollection<string> PairedDevices()
		{
			BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
			ObservableCollection<string> devices = new ObservableCollection<string>();

			foreach (var bd in adapter.BondedDevices)
				devices.Add(bd.Name);
				
			return devices;
		}


		#endregion
	}
}

Read method

The “Read” method is simply a loop that continue to:

Search for a paired device

foreach (var bd in adapter.BondedDevices) {
	System.Diagnostics.Debug.WriteLine ("Paired devices found: " + bd.Name.ToUpper ());
	if (bd.Name.ToUpper().IndexOf (name.ToUpper ()) >= 0) {

		System.Diagnostics.Debug.WriteLine("Found " + bd.Name + ". Try to connect with it!");
		device = bd;
		break;
	}
}

adapter.BondedDevises returns all paired devices. I have a “name” parameter that is the name of the bluetooth scanner I want to use (or a part of the name. For example, if the scanner has a name  like “QuickScan XXX YYY”, I can use “scan” to connect to it).

In “device” I have my bluetooth device!

Create a Bluetooth Socket and use it like a RS232

UUID uuid = UUID.FromString ("00001101-0000-1000-8000-00805f9b34fb");
	
if((int)Android.OS.Build.VERSION.SdkInt >= 10) // Gingerbread 2.3.3 2.3.4
	BthSocket = device.CreateInsecureRfcommSocketToServiceRecord (uuid);
else
	BthSocket = device.CreateRfcommSocketToServiceRecord (uuid);

Here I try to create a socket. The strange number (“0001101…”) is the SPP UUID.

Verify if there are some data in the socket

await BthSocket.ConnectAsync ();

if(BthSocket.IsConnected){
	System.Diagnostics.Debug.WriteLine("Connected!");
	var mReader = new InputStreamReader(BthSocket.InputStream);
	var buffer = new BufferedReader(mReader);

	while (_ct.IsCancellationRequested == false){
		if(buffer.Ready ()){

Created the socket, I connect to it and open a read buffer, so I can receive data from the scanner. “IsCancellationRequested” is used to break the loop (calling “Cancel” method defined in the interface)

Read data and send it to Xamarin Forms app

char[] chr = new char[100];
string barcode = "";
if (readAsCharArray) { 
										
	await buffer.ReadAsync(chr);
	foreach (char c in chr) {

		if (c == '\0')
			break;
		barcode += c;
	}

}else
	barcode = await buffer.ReadLineAsync();
										
if(barcode.Length > 0){
	System.Diagnostics.Debug.WriteLine("Letto: " + barcode);
	Xamarin.Forms.MessagingCenter.Send<App, string> ((App)Xamarin.Forms.Application.Current, "Barcode", barcode);
}
else
	System.Diagnostics.Debug.WriteLine ("No data");

Now I can test if data is present and read it. I use two way to read data, ReadLine and ReadChar. ReadChar should works always…

Now I can “send” a message to “App” with my barcode!

Xamarin Forms implementation

Xamarin Forms part is very simple. You should:

  • Start receiving data
  • Stop receiving data

To start and stop receiving data, you can use this code in your App.cs file.

protected override void OnSleep ()
{
	MessagingCenter.Send<App>(this, "Sleep"); // When app sleep, send a message so I can "Cancel" the connection
}

protected override void OnResume ()
{
	MessagingCenter.Send<App>(this, "Resume"); // When app resume, send a message so I can "Resume" the connection
}

When app “Resume”, I re-activate read. When app “sleep”, I close read. I do nothing when app “Start” because in this sample the “start read” is not automatic.

When I start to read data?

In the MyPageViewModel.cs. In the constructor you can find:

public MyPageViewModel()
{

	MessagingCenter.Subscribe<App>(this, "Sleep",(obj) => 
	{
		// When the app "sleep", I close the connection with bluetooth
		if(_isConnected)
			DependencyService.Get<IBth>().Cancel();

	});

	MessagingCenter.Subscribe<App>(this, "Resume", (obj) =>
	{

		// When the app "resume" I try to restart the connection with bluetooth
		if(_isConnected)
			DependencyService.Get<IBth>().Start(SelectedBthDevice, _sleepTime, true);

	});


	this.ConnectCommand = new Command(() => {
			
		// Try to connect to a bth device
		DependencyService.Get<IBth>().Start(SelectedBthDevice, _sleepTime, true);
		_isConnected = true;

		// Receive data from bth device
		MessagingCenter.Subscribe<App, string> (this, "Barcode", (sender, arg) => {

			// Add the barcode to a list (first position)
			ListOfBarcodes.Insert(0, arg);
		});
	});

	this.DisconnectCommand = new Command(() => { 

		// Disconnect from bth device
		DependencyService.Get<IBth>().Cancel();
		MessagingCenter.Unsubscribe<App, string>(this, "Barcode");
		_isConnected = false;
	});


	try
	{
		// At startup, I load all paired devices
		ListOfDevices = DependencyService.Get<IBth>().PairedDevices();
	}
	catch (Exception ex)
	{
		Application.Current.MainPage.DisplayAlert("Attention", ex.Message, "Ok");
	}
}

I try to explain:

  • When I receive a “sleep” message from App.cs, I “Cancel” the read loop.
  • When I receive a “resume” message from App.cs, I “Start” again the read loop.
  • When I press “Connect” button, I “Start” the read loop
  • I fill the Picker with the list of Paired devices so the user can easily select the device to connect. I use FreshEssential Bindable Picker for this.

That’s all folks. I have tested this approach with 5/6 different scanners and it works fine. There are better methods? Maybe… leave a comment!

Annunci

9 pensieri su “Connect a barcode reader to a Xamarin Forms app via Bluetooth

  1. I just wanted to thank you for your work and your post.
    Very good explanation and good example, that I’ll use as starting point for my own project.
    Best regards, Yurii.sio2

    Mi piace

  2. Thank you very much for your post! It has been very useful for me.
    I would like to know if you could recommend me an implementation like this for Xamarin iOS ? As you can see, I have to do the same in the other platform haha.

    Thank you again!!!

    Seba

    Mi piace

  3. I have started to take a look to iOS connection… but seems to be some problems.

    If I have understand correctly, iOS accept BLE devices. Otherwise device should join MFi program, but I am not sure about this.

    Can someone explain better these things?

    If I open “bluetooth” page on my iPhone, I don’t see my Bluetooth Scanner, so I think I can’t pair it as SPP.

    If I connect as HID, I can pair and connect (and it works….)

    Mi piace

      • Excellent Seba but I think there are some problems with iOS. Here a response from Apple

        It appears that the Biocontrol Hhr3000 scanner is not an MFI Bluetooth accessory. As such, this means that there is no supported means for an iOS application to connect with the scanner and communicate with an iOS application. You can submit an API enhancement request using the Apple Developer Bug Report web page that iOS provide API’s similar to Android to facilitate the connection and communication with Classic Bluetooth accessories – actually, this has been a popular ER ever since the release of iOS 3. The other means to make this happen would be for the developer of the scanner to become an MFI licensee and implement the iPod Accessory Protocol (iAP2) into the scanner for compatability with iOS. If this happens, then the accessory can use the iOS External Accessory Framework methods to connect and communicate with the scanner.

        Let me know… thanks

        Mi piace

  4. Thanks Alessandro! Those comments are very helpful for me. In my case, I am connecting different bluetooth devices and yesterday I awared there are some devices registered on the MFI Program and what is more there is an application that connect with one of them. The next week I will be working on that and I hope I could create a post regarding this BLE connection on Xamarin iOS.

    Once I could create a connection, you will be receiving a message!

    Thank you!

    Mi piace

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...