Thursday, April 09, 2009

.Net – Chatting with a Barcode Reader over the serial port

One of my projects requires that my application be able to read bar-codes. I had a slot badge scanner to test with, which connected to the computer via the serial port (its an old reader).

slotscanner-bg

.Net 2.0 makes it extremely easy to talk to devices connected to the serial port using the “SerialPort” class.

You create a new instance of the class using one of the constructors, the simplest of which is: SerialPort port = new SerialPort(port, baudRate, parity, dataBits, stopBits), which sets up the class to communicate over the given port using the parameters specified. (port is a string like “COM1”).

You can now send data using one of the Write methods or receive data using one of the Read methods.

But, to process data that is being read from the bar-code scanner, I wanted the application to automatically react everytime it received barcode data. Using the Read, ReadLine methods, will only allow you to retrieve data that is already stored in the SerialPort’s buffer/stream. To be able to process data from the Barcode reader as it is received – the SerialPort class provides the “DataReceived” event.

port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived)

Where port_DataReceived has the following method signature: void port_DataReceived(object sender, SerialDataReceivedEventArgs e)

The one thing that you should know is that the DataReceived event can be fired more than once while the bar-code reader is sending its data. For this reason, you need to determine a method to find out when a complete bar-code has been received before you begin processing the data. In my case the bar-code would send a carriage return at the end of every bar-code that had been read. The port_DataReceived method would buffer in coming data in a StringBuilder. The StringBuilder is converted to a string and stored in a Queue everytime the bar-code reader send a NewLine character. The queue can then be read by another method to process the data. (My implementation provides for a method which signals when a complete bar-code has been received).

public class BarcodeDataReceivedEventArgs : EventArgs
{
   public BarcodeDataReceivedEventArgs(string barcodeData)
   {
       _barcodeData = barcodeData;
   }
   string _barcodeData = null;
   public string BarcodeData { get { return _barcodeData; } }
}
public delegate void Barcode_DataReceived(object sender, BarcodeDataReceivedEventArgs bdrEA);

public class SerialPortBarcodeReader
{
   bool _sendBarcodeDataToKeyboard = true;
   public event Barcode_DataReceived Barcode_DataReceived;
   public SerialPortBarcodeReader(bool sendBarcodeDataToKeyboard)
   {
       _sendBarcodeDataToKeyboard = sendBarcodeDataToKeyboard;
   }

   private SerialPort _port = null;

   /// <summary>
   /// use this method to start polling the barcode on the specified serial port with the specified settings
   /// </summary>
   public void StartPolling(string port, int baudRate, Parity parity, int dataBits, StopBits stopBits)
   {
       if (!IsPortOpen)
       {
           string[] portNames = SerialPort.GetPortNames();
           _port = new SerialPort(port, baudRate, parity, dataBits, stopBits);
           _port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
           //Sometimes you need to enable RTS for the SerialDataReceivedEventHandler event to be fired.
           _port.RtsEnable = true;
           _port.Open();
       }
   }
   public void StopPolling()
   {
       if (IsPortOpen)
       {
           Close();
       }
   }

   /// <summary>
   /// returns true if the port is still open
   /// </summary>
   public bool IsPortOpen
   {
       get { return (_port != null && _port.IsOpen); }
   }

   /// <summary>
   /// returns data from the queue and removes it, so that the next call
   /// to this method will return the next item in the quque.
   /// </summary>
   /// <returns></returns>
   public string DequeBarCodeData()
   {
       string data = null;
       if (_barcodeQue != null && _barcodeQue.Count > 0)
           data = _barcodeQue.Dequeue();
       return data;
   }

   private StringBuilder _buffer = new StringBuilder();
   private Queue<string> _barcodeQue = new Queue<string>();
   /// <summary>
   /// this method might be called multiple times for a single piece of data (for example a bar-code)
   /// the number of times its called is based on the size of the serial ports buffer and the read threshold size
   /// which is why we need to buffer the received data, until we receive an end of transmission flag, after which we can process the data.
   /// </summary>
   /// <param name="sender"></param>
   /// <param name="e"></param>
   private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
   {
       //read line and append to buffer
       string inLine = _port.ReadExisting();
       System.Diagnostics.Debug.WriteLine("dataReceived");
       _buffer.Append(inLine);

       //check if complete information was received - which in this case is signalled by
       //a NewLine.
       if (inLine.EndsWith(Environment.NewLine))
       {
           System.Diagnostics.Debug.WriteLine("dataProcessed");
           //process the buffer and clear it out.
           string barcodeInfo = _buffer.ToString();
           _buffer = new StringBuilder();

           //add to queue - so that it can be processed else where
           _barcodeQue.Enqueue(barcodeInfo);

           if (_sendBarcodeDataToKeyboard)
           {
               SendKeys.Send(barcodeInfo);
           }

           //raise event
           if (Barcode_DataReceived != null)
           {
               Barcode_DataReceived(this, new BarcodeDataReceivedEventArgs(barcodeInfo));
           }
       }
   }

   /// <summary>
   /// closes the reader
   /// </summary>
   public void Close()
   {
       if (_port != null && _port.IsOpen)
       {
           _port.Close();
       }
   }
}

Gotchas:

When I first began using SerialPort with the bar-code reader, the DataReceived event would not fire. After many hours of testing I found out that in the case of my Bar-code reader, I had to set the RtsEnable property of the SerialPort class to true.

In the class above, you can subscribe to the “Barcode_DataReceived” event to be notified when ever a complete bar-code has been received. The event args provide you with the bar-code data that was received. You can process items from the queue using the “DequeBarCodeData” method, which will remove the bar-code data from the processing queue.

1 comment:

Dale Sean said...

I make program using vb.net where it connected to barcode scanner using rs232 connection. As generally, I use serialport communication (SerialPort1.ReadLine) to get data from barcode scanner. First I think it's work because the barcode value appears in the textbox until I get it can't show the data which has relation with barcode value.

The barcode value is same with data in database, but it can't show. So I try to copy the data from textbox then found that there are the undefined string in the last character of textbox. It's like space but not, when I copy that textbox value to the winword, the value enter automatically, so, i just want to know how my programs can read the barcode value without that undefined string.Thank you.
.NET BARCODE READER