Monday 21 April 2014

Fun with Delphi XE6 App tethering and barcodes

Updated: Removed broken GDrive links, now linking to old GitHub repo.

One of the new key features in XE6 is what Embarcadero has chosen to call App Tethering components - the only problem I initially had with the name was that it implied that mobile had to be involved in some way - which is not the case. You can make a windows service that is a "receiver" from a desktop application running on the same sub-net.

An example could be POS systems, running on various devices talking to the same receiver - without bothering about implementing multiple communications protocols - these components all wraps this up nicely.

I wanted to give these new component a try, by making a very simple barcode scanner using my phone as the barcode scanner and then have the receiver send the barcode to whatever application was active.

A word of warning: this is a prof of concept - not ready for production or deployment - you need to do some work yourself to make it fit your needs. I have tried to use as little code as possible - leaving out all exception handling :-).


Part 1 - The "receiver" desktop app.


Start with a new Desktop application either VCL or FireMonkey will do, add a TTetheringManager, a TTetheringAppProfile, a TLabel and a TCheckBox - which all should end up looking a bit like this:


Change the (Manager)Text property on the TTetheringManager to 'BarcodeReceiverManager', on the TTetheringAppProfile change the (Profile)Text property to 'BarcodeReceiver' and set the Manager property to point to the TTetheringManager.

Since we want to send the received barcode to the active application, we just use a send key function - in this case I have just used the WinAPI keybd_event() method - it would be more correct to use SendInput() - but the barcodes consists of non-unicode characters. For the letters we also need to send along the shift key.

  procedure SendKeys(const S: String);
  var
    I: Integer;
  begin
    for I := 1 to Length(S) do
    begin
      if CharInSet(S[I], ['A'..'Z']) then
        keybd_event(VK_SHIFT, 0, 0, 0);
      keybd_event(Ord(S[I]), MapVirtualKey(Ord(S[I]), 0),0, 0);
      keybd_event(Ord(S[I]), MapVirtualKey(Ord(S[I]), 0), KEYEVENTF_KEYUP, 0);
      if CharInSet(S[I], ['A'..'Z']) then
        keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
    end;
  end;

On the TTetheringAppProfile.OnResourceReceived event add the following code to send the string received from the mobile app - and maybe a carriage return - if our TCheckBox is checked.

 SendKeys(AResource.Value.AsString);
 if cbAddReturn.IsChecked then
   SendKeys(char(13));


Part 2 - The "sender" mobile app.

Create a new FireMonkey Mobile Application, add a TTetheringManager, a TTetheringAppProfile, a TEdit, a TLabel and two TSpeedButtons - which all should end up looking a bit like this:

This design will never get accepted on any App Store :-D

On the TTetheringAppProfile set the Manager property to point to the TTetheringManager.

Now on the FormShow event we want to unpair any Managers already paired with this TTetheringManager, and after that trigger the Discover event.

 for I := TetheringManager1.PairedManagers.Count - 1 downto 0 do
   TetheringManager1.UnPairManager(TetheringManager1.PairedManagers[I]);
 TetheringManager1.DiscoverManagers;

At the end of the discovery, the TTetheringManager.OnEndManagersDiscovery event is called:

 for I := 0 to RemoteManagers.Count-1 do
   if (RemoteManagers[I].ManagerText = 'BarcodeReceiverManager')  then
   begin
     TetheringManager1.PairManager(RemoteManagers[I]);
     Break; // Break since we only want the first...
   end;

 followed by the TTetheringManager.OnEndProfilesDiscovery event:

 Label1.Text := 'No receiver found';
 for i := 0 to TetheringManager1.RemoteProfiles.Count-1 do
   if (TetheringManager1.RemoteProfiles[i].ProfileText = 'BarcodeReceiver') then
   begin
     if TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[i]) then
       Label1.Text := 'Receiver ready.'
   end;

Notice the only two references to the "Text" properties we set in our "receiver" applications Manager and Profile components - the rest is done by magic (or a lot of Indy code I didn't need to write :-D)

And the last thing we need to add is on the OnClick event of the "Send barcode" button:

 TetheringAppProfile1.SendString(TetheringManager1.RemoteProfiles[0], 
                                 'Barcode from mobile', Edit1.Text);

Now we can start our "receiver" application, and run our "sender" mobile app on iOS or Android, and in theory it should work.

I did put the tethering example to a minimum, for a more correct examples see the samples include with XE6 - it seems that there is a lot of extra stuff hidden in these 2 components - maybe for another post.

Part 3 - Barcode scanning.

There are some examples out there for doing this both on Android and iOS - I guess you could also invest in some commercial components like those from TMS or WinSoft.

I previously been using the ZBar library on iOS/ObjectiveC and xzing library Android/Java - because ZBar wasn't that good on Android years back.

For this example I am only going to do Android using xzing as what is called an intent. I guess that using intents on Android is best practice, but I guess most Delphi developers are not that keen on being depending on any framework or other thing we do not control - like Windows :-D

When doing Android Apps in Java, I did not always follow the intent way, but included the xzing core.jar file - that would also be possible in Delphi - but getting the classes wrapped into Delphi interfaces would take some work, or someone should sponsor me by buying either WinSoft JavaImport or CHUA Chee Wee's Android2DelphiImport - even better if Embarcadero bundles one of them in XE6 update 1 :-)

Update: Software Union also provides Java2Pas which might do the trick.

I did have a peek at the code from Thomas K's TKRBarcodeScanner component (beware terrible link :-) ) which for iOS is depending on the TMS component.

Since we are going to use the clipboard to get the barcode from the xzing intent, and still behave as good citizens preserving what is already there - we declare some variables:

 FPreservedClipboardValue: TValue;
 FMonitorClipboard: Boolean;
 ClipService: IFMXClipboardService;

Notice the TValue from the System.Rtti unit - it is kind of a Variant and then not.
On the FormCreate event we start testing if the platform supports what we think of as a clipboard, and we also wants it to support application events - so that we can check when the intent is done and our app becomes active again.

 var
   aFMXApplicationEventService: IFMXApplicationEventService;
 begin
   FMonitorClipboard := False;
   if not TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService,  IInterface(ClipService)) then
     ClipService := nil;
   if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService,  IInterface(aFMXApplicationEventService)) then
   begin
     aFMXApplicationEventService.SetApplicationEventHandler(HandleAppEvent);
   end
   else
   begin
     Log.d('Application Event Service is not supported.');
   end;
 end;

Now we want to call the xzing intent to scan a barcode, put the result on the clipboard and return when done. So on the OnClick event of the "Scan barcode" we put the following code:

 if Assigned(ClipService) then
 begin
   FPreservedClipboardValue := ClipService.GetClipboard;
   FMonitorClipboard := True;
   ClipService.SetClipboard('nil');
   intent := TJIntent.Create;
   intent.setAction(StringToJString('com.google.zxing.client.android.SCAN'));
 //    intent.putExtras(TJIntent.JavaClass.EXTRA_TEXT, StringToJString('"SCAN_MODE", "CODE_39"'));
   SharedActivity.startActivityForResult(intent, 0);
 end;

We create an instance of TJIntent, set its action to the zxing barcode scanner intent, which you might need to install on your mobile before running this app, if you do not already have the intent via some other app. You need to convert the name of the intent to a Java string. You can among other things also specify which barcode types that can be read - doing that increases the speed and precision of the scanning - so if you know you only need Code 39 or EAN13 you should specify that.

When the application event fires, we check if it was because our app became active and we were monitoring the clipboard.

 Result := False;
 if FMonitorClipboard and (AAppEvent = TApplicationEvent.BecameActive) then
 begin
   Result := GetBarcodeValue;
 end;

The GetBarcodeValue function gets the barcode from the clipboard, and restores the old value from before we initiated the scan:

 Result := False;
 FMonitorClipboard := False;
 if (ClipService.GetClipboard.ToString <> 'nil') then
 begin
   Edit1.Text := ClipService.GetClipboard.ToString;
   ClipService.SetClipboard(FPreservedClipboardValue);
   Result := True;
 end;

Done - you should now have a working app - which could need a lot of extra error checking and care. But like baking your own bread I hope this has been fun. I have added some extra links below to people or code that might also be helpful - including the full code for download. If you just have interest in the barcode part there are better examples in the links below - also be aware that in XE6 the StringToJString has move to the Androidapi.Helpers unit.

Links:

The source code: Receiver application and Scanner app on GitHub
Jim McKeeth is always helpfull: http://delphi.org/2014/02/scanning-barcodes-with-rad-studio-xe5/
FMX Express is very active collecting relevnat stuff: http://www.fmxexpress.com/qr-code-scanner-source-code-for-delphi-xe5-firemonkey-on-android-and-ios/ - there is also a better link to Thomas K. code there.
John Whitham should also be mentioned: http://john.whitham.me.uk/xe5/

29 comments:

  1. Awesome blog post. Clean, clear example using XE6's App Tethering.

    ReplyDelete
  2. Can you edit your styles a little on here? There's no visual distinction between normal text and links, which makes it a bit confusing...

    ReplyDelete
    Replies
    1. Yes you're right - added font color for the links - thanks

      Delete
  3. Another convertor is Java2Pas - http://www.softwareunion.lu/downloads/

    ReplyDelete
    Replies
    1. Thanks - I did update the post - the description on the download link states "kostenlos" - but there is a "Purchase" option - and I can see from the code it generates that you do not get the implementation - unless you register. Seems like a nice inexpensive offering.

      Delete
    2. I have converted zxing https://oss.sonatype.org/content/repositories/snapshots/com/google/zxing/android-integration/3.0.2-SNAPSHOT/android-integration-3.0.2-20140418.140645-1.jar using Java2pas. You can download the pas file zip here: http://www.softwareunion.lu/files/zxing

      Delete
    3. Thanks Oliver, I will hopefully update the post later today - showing the benefits of the interface wrappers you did.

      Delete
    4. I haven't had time to look into the wrappers yet - just did a minor "adjustment" of the uses clause - new version of the IntentIntegrator.pas here: https://drive.google.com/file/d/0BwE78FIICSJvUEZ2RENHUF84dU0/edit?usp=sharing. Will probably first get some spare time in the coming weekend :-(

      Delete
    5. If you have any suggestions to enhance Java2pas, please feel free to use our mantis bugtracker.

      Delete
  4. Small bug?

    if TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[0]) then

    should be

    if TetheringAppProfile1.Connect(TetheringManager1.RemoteProfiles[i]) then

    :)

    ReplyDelete
    Replies
    1. :) Yes, I think I share the same issue as the PhotoWall Sample - which I looked at when I tried out the tethering - if you look at the RefreshList in the sample, change it to only add VCLMediaReceiverApp in the LbWall - you still might end up connecting to a MediaReceiverApp - because TakePhotoManager.RemoteProfiles holds all the profiles of the pair managers - i guess. Well I will at least fix my example - and simplify them - one Manager, one Profile - no loops - Thanks. BTW. it seems that the 32-bit VCL is a false-positive at AVAST - that is a different kind of bug-checking.

      Delete
  5. So, in a nutshell, app tethering is IPC for Delphi objects? Why couldn't anyone say so from the beginning?

    ReplyDelete
    Replies
    1. In this "mobile" world, I guess more people can in some way relate to "tethering" - marketing-wise :-D

      Delete
  6. Hi Steve, do you know if ti has limitation on using with google glass? I am just using your example with it and it doesn`t work.. :-(

    ReplyDelete
    Replies
    1. Hi, Did you try and install the xzing barcode reader from the Google App-store? I do not happen to have any glasses myself - I do not think that they are made available outside US yet. If the xzing barcode app works, so should the sample I did - and in the Project Options User permission I only think the Camera is relevant (which is default on) - I do not see anything for the "clipboard". A bit more detail (than doesn't work) would be helpful - if there is any :-)

      Delete
    2. Hi, thanks for your answer. The problem was solved. I was compiling on a virtual machine and it has different subnet when comparing with my desktop and google glass. It means 3 network subnets, when it most be the same... Sorry!! In my case I just transfer images and text (without barcode). Thanks a lot!

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi, thanks.
    To keep things simple, I would just keep sending a string with the barcode and the quantity, separated by a # or ¤ (what ever separator char you prefer). And then change the desktop "receiver" to parse the string into 2 and send a tab or return - what ever needed in between.
    But since you are not limited to string - you could send a stream with whatever structure you like and parse and act upon that on the receiver end.
    Everything is possible - we might just not yet have figured out how :-D

    ReplyDelete
  9. Thanks for sharing your knowledge, but mark me an error, telling me that the ip and port and are being used, this is due to? thank you very much

    ReplyDelete
  10. You're welcome :-) The idea with the Tethering is that they "discover" and "pair" - so no need to specify ip-addr or port, but you discover by Manager and Profile name. I haven't looked at the source (yet) - but I guess it is like a DIAL protocol - using UDP to discover and TCP to communicate. But you shouldn't bother with ip-addr and ports - just be on the same subnet. I hope that answers your question. Steffen

    ReplyDelete
    Replies
    1. thank you very much for your prompt response, the error was caused by another app installed on my android that used the same port, but now I have another problem, when closing the android app bookmark me this error: exception class segmentation fault (11) but only android in windows hahaha no, really driving me crazy I wonder if rad error or error mio certainly puts him in this procedure: procedure TIdSocketHandle.Disconnect;
      begin
         GStack.Disconnect (Handle);
      end;
      at the end I just want to know if I will have to wait to resolve this error from jetty, you owe the antencion, and I apologize if my questions are silly means am a newbie in this and in my English.

      Delete
  11. I would add the ReportMemoryLeaks := True; in the project and run the client on Windows to see if there might be any memory leaks. It is an "AV" error, take a look at this post in the embt forums: https://forums.embarcadero.com/message.jspa?messageID=645831.

    Creating a small example that reproduces the error and then sharing that - with me or Embarcadero's forums will probably be more helpful in solving the issue.

    No silly questions, just annoying problems :-D

    ReplyDelete
  12. I'm developing an application that will be using a barcode scanner. Now the problem I'm having is that when I do scan the barcodes it only displays the first number of my barcode in the TEdit.

    When I test the barcode itself by scanning it say to MS Word or Notepad it scans the whole barcode and displays the whole number sequence correct.

    So is there any code that I need to write to make the scanner read more then 1 number from my barcode when it is being scanned with my delphi application?
    java barcode scanner

    ReplyDelete
    Replies
    1. It is not quite clear to me if you are also writing the barcode scanner yourself, or we are talking about a "physical" USB connected barcode scanner. But what version of Delphi are you using? If it is just a barcode scanner that acts as keyboard input, then whatever window control that has focus and can receive keyboard input - like Word, Notepad or TEdit should work - unless your barcode scanner adds some value by not using the keyboard buffer, but sending maybe unicode strings - and your TEdit is a per-Delphi 2009 version (that being non-unicode). I would need a bit more detail - the reference to the "java barcode scanner" did confuse me a bit. Have you tried zxing and my example described above? Well my bet is on type differences between the receiver and the sender.

      Delete
  13. What if I change TTetheringManager AllowedAdapters property to Bluetooth. Do i have to change any part of code?

    ReplyDelete
  14. hi, I downloaded the code, but I get this error

    [Copy Error] Unable to copy file "\ library \ lib \ armeabi-v7a \ libMobileBarcodeKey.so" to "\ debug \ libMobileBarcodeKey.so". Could not find a part of the path '\ library \ lib \ armeabi-v7a \ libMobileBarcodeKey.so'.

    Does anyone know what I can do to correct it and to generate the .apk?

    ReplyDelete