Delphi Android USB Interface with the G2

I first tried to use Libusb to connect my Android tablet with the G2. I managed to compile the libusb sources for Android and call it from the application, only to run into the “No permissions” error on trying to open the device.

There is no easy solution for this at the moment, other than havng to root the device and do other complex things, which very much narrows down the public who could eventually use the application.

So I tried the other method, using the “Java native interface”, which turned out to be very easy. Here is an example application which uses this method to interface over USB.

It involves 3 steps:

  1. Writting the JNI API header unit
  2. Writing the application
  3. Defining a device filter in the AndroidManifest.template.xml file

There is one important requirement: the Android device must support “Host mode” and you should use a USB OTG cable to connect to the G2.

Host mode means that the Android device can play the role of host to the usb slave device (the G2) and the OTG cable, because of special wiring, triggers the device into host mode.

First the API header unit for the USB functions has to be created. I used the example over here: http://www.pclviewer.com/android/, which  deals with the Bluetooth interface as an example.

I’ve not exposed all classes or methods at the moment, just the ones I needed for my application.

unit Androidapi.JNI.USB;

// (c) B.J.H. Verhue 2014
// JNI USB API converted from api-versions.xml

interface
uses
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes,
  Classes;

type
// =============================================================================
//
//                                 UsbEndPoint
//
// =============================================================================

  JUsbEndPoint = interface;
  JUsbEndPointClass = interface(JObjectClass)
  ['{4B9757DB-9DF8-4E8A-B981-E318D099B0A1}']
  end;

  [JavaSignature('android/hardware/usb/UsbEndpoint')]
  JUsbEndPoint = interface(JObject)
  ['{2D1BCC63-C184-41D0-A23A-78E256F8E1D4}']
    function init:JUsbEndPoint; cdecl;
    function getAddress:integer; cdecl;
    function getAttributes:integer; cdecl;
    function getDirection:integer; cdecl;
    function getEndpointNumber:integer; cdecl;
    function getInterval:integer; cdecl;
    function getMaxPacketSize:integer; cdecl;
    function getType:integer; cdecl;
  end;

  TJUsbEndPoint = class(TJavaGenericImport<JUsbEndPointClass, JUsbEndPoint>) end;

// =============================================================================
//
//                                 UsbInterface
//
// =============================================================================

  JUsbInterface = interface;
  JUsbInterfaceClass = interface(JObjectClass)
  ['{245DC801-9BF6-4014-B84A-62F44A8C3DB9}']
  end;

  [JavaSignature('android/hardware/usb/UsbInterface')]
  JUsbInterface = interface(JObject)
  ['{82D91C14-A4AA-47D7-BA6A-5B05B86F6856}']
    function init:JUsbInterface; cdecl;
    function getEndpoint(i : integer): JUsbEndPoint; cdecl;
    function getEndpointCount: integer; cdecl;
    function getId: integer; cdecl;
    function getInterfaceClass: integer; cdecl;
    function getInterfaceProtocol: integer; cdecl;
    function getInterfaceSubclass: integer; cdecl;
  end;

  TJUsbInterface = class(TJavaGenericImport<JUsbInterfaceClass, JUsbInterface>) end;

// =============================================================================
//
//                               UsbDeviceConnection
//
// =============================================================================

  JUsbDeviceConnection = interface;
  JUsbDeviceConnectionClass = interface(JObjectClass)
  ['{447D85BC-BA61-4BBA-A803-563071D90D85}']
  end;

  [JavaSignature('android/hardware/usb/UsbDeviceConnection')]
  JUsbDeviceConnection = interface(JObject)
  ['{D613CA69-DD0E-404A-A064-828E09429145}']
    function init:JUsbDeviceConnection; cdecl;
    function bulkTransfer(UsbEndpoint: JUsbEndpoint; data: TJavaArray<Byte>; length : integer; timeout : integer): integer; cdecl;
    function claimInterface(UsbInterface: JUsbInterface; ForceClaim: boolean): boolean; cdecl;
    procedure close; cdecl;
    function releaseInterface(UsbInterface: JUsbInterface): boolean; cdecl;
  end;

  TJUsbDeviceConnection = class(TJavaGenericImport<JUsbDeviceConnectionClass, JUsbDeviceConnection>) end;

// =============================================================================
//
//                                   UsbDevice
//
// =============================================================================

  JUsbDevice = interface;
  JUsbDeviceClass = interface(JObjectClass)
  ['{38F968EC-5B0B-4018-A302-4DC469509254}']
  end;

  [JavaSignature('android/hardware/usb/UsbDevice')]
  JUsbDevice = interface(JObject)
  ['{35B16245-52F3-409B-86BF-259F3A8F4845}']
    function getProductId:integer; cdecl;
    function getVendorId:integer; cdecl;
    function getInterface(i: integer):JUSBInterface; cdecl;
    function getInterfaceCount: integer; cdecl;
  end;

  TJUsbDevice = class(TJavaGenericImport<JUsbDeviceClass, JUsbDevice>) end;

// =============================================================================
//
//                                 UsbManager
//
// =============================================================================

  JUsbManager = interface;
  JUsbManagerClass = interface(JObjectClass)
  ['{D4A4DDAC-EE30-4123-A0BE-76F8E95FAC55}']
  end;

  [JavaSignature('android/hardware/usb/UsbManager')]
  JUsbManager = interface(JObject)
  ['{5E8A5FA6-64DA-4C90-9D52-988D66E6728E}']
    function getDeviceList:JHashMap; cdecl;
    function hasPermission(UsbDevice:JUsbDevice):boolean; cdecl;
    function openDevice(UsbDevice:JUsbDevice):JUsbDeviceConnection; cdecl;
  end;

  TJUsbManager = class(TJavaGenericImport<JUsbManagerClass, JUsbManager>) end;


implementation

end.

Second writing the application.  I’ve made a simple Android application in XE5 bases on a blank mobile form.  The application has four buttons:

  1. List devices, this lists all connected USB devices
  2. Open, this opens the G2 devivce, if it is found
  3. Message, this sends an “Init” message to the G2
  4. Close, this closes the device

The messages are shown in a Memo control.

Here is the code

 

unit UnitUSBTestJNI;

// (c) B.J.H. Verhue 2014
// G2 USB communication using JNI API DEMO

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
  FMX.Memo, FMX.StdCtrls,
  Androidapi.JNIBridge,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.USB;

type
  // Thread for receiving messages from G2
  TUSBThread = class(TThread)
  private
    [Weak] FUsbDeviceConnection : JUSBDeviceConnection;
    [Weak] FUsbEPII : JUSBEndPoint;
    [Weak] FUsbEPBI : JUSBEndPoint;
    FLogMessage : string;
    FBytesRead : integer;
    FIBuffer,
    FBBuffer : TJavaArray<Byte>;
  protected
    procedure Execute; override;
  public
    constructor Create( CreateSuspended: Boolean);
    destructor Destroy; override;

    procedure ProcessMessage;
    procedure WriteLog;
    procedure DumpIMessage;
    procedure DumpBMessage;

    property UsbDeviceConnection : JUSBDeviceConnection read FUsbDeviceConnection write FUsbDeviceConnection;
    property UsbEPII : JUSBEndPoint read FUsbEPII write FUsbEPII;
    property UsbEPBI : JUSBEndPoint read FUsbEPBI write FUsbEPBI;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    bListDevices: TButton;
    Memo1: TMemo;
    bOpen: TButton;
    bMessage: TButton;
    bClose: TButton;
    procedure bListDevicesClick(Sender: TObject);
    procedure bOpenClick(Sender: TObject);
    procedure bCloseClick(Sender: TObject);
    procedure bMessageClick(Sender: TObject);
  private
    FUsbActive : boolean;
    FUsbThread : TUSBThread;
    FUsbManager : JUSBManager;
    FUsbDevice : JUSBDevice;
    FUsbInterface : JUSBInterface;
    FUsbEPII, FUsbEPBI, FUsbEPBO : JUSBEndPoint;
    FUsbDeviceConnection : JUSBDeviceConnection;
    function GetUsbActive: boolean;
    procedure SetUsbActive(const Value: boolean);
    procedure DumpMessage( data : TJavaArray<Byte>; size : integer);
  public
    property UsbActive : boolean read GetUsbActive write SetUsbActive;
  end;

var
  Form1: TForm1;

implementation
uses
  FMX.Helpers.Android;

{$R *.fmx}

function CrcClavia( Seed: Integer; aVal: Integer): Word;
var
   i    : Integer;
   aCrc : Integer;
   k    : Integer;
begin
  // Calculates the G2 checksum for messages

   k    := ((( Seed shr 8) xor aVal) and 255) shl 8;
   aCrc := 0;
   for i := 1 to 8
   do begin
     if ( aCrc xor k) and $8000 <> 0
     then aCrc := ( aCrc shl 1) xor $1021
     else aCrc := aCrc shl 1;
     k := k shl 1;
   end;
   Result := (( Seed shl 8) xor aCrc) and $ffff;
end;

procedure TForm1.bListDevicesClick(Sender: TObject);
var JavaObject : JObject;
    DeviceList : JHashMap;
    Device : JUSBDevice;
    i : Jiterator;
    s : JString;
begin
  // Device discovery...

  // Get pointer to UsbManager
  JavaObject := SharedActivityContext.getSystemService(TJContext.JavaClass.USB_SERVICE);
  FUsbManager := TJUSBManager.Wrap((JavaObject as ILocalObject).GetObjectID);

  // Get a list of connected slave devices
  DeviceList := FUsbManager.getDeviceList;
  s := DeviceList.toString;
  Memo1.Lines.Add(jstringtostring(s));

  // Get pointer to G2 Device
  FUsbDevice := nil;
  i := DeviceList.values.iterator;
  while i.hasNext do begin
    Device := TJUSBDevice.Wrap((i.next as ILocalObject).GetObjectID);
    if (Device.getVendorId = 4092) and (Device.getProductId = 2) then
      FUsbDevice := Device;
    Memo1.Lines.Add('VendorID ' + IntToStr(Device.getVendorId) + ', ProductID ' + IntToStr(Device.getProductId));
  end;

  if assigned(FUsbDevice) then
    bOpen.Enabled := assigned(FUsbDevice);
end;

procedure TForm1.bCloseClick(Sender: TObject);
begin
  if UsbActive then
    UsbActive := False;
end;

procedure TForm1.bMessageClick(Sender: TObject);
var bytes_written, size, i : integer;
    crc : Word;
    Buffer : TJavaArray<Byte>;
begin
  // Send "Init" message to G2

  if assigned(FUsbDeviceConnection) then begin

    Buffer := TJavaArray<Byte>.Create(5);
    try
      Size := 1 + 2 + 2;

      Buffer.Items[0] := size div 256;
      Buffer.Items[1] := size mod 256;
      Buffer.Items[2] := $80;

      // Calc CRC
      Crc := 0;
      i := 2;
      while i < (Size-2) do begin
        Crc := CrcClavia(Crc, Buffer.Items[i]);
        inc(i);
      end;

      Buffer.Items[3] := crc div 256;
      Buffer.Items[4] := crc mod 256;

      bytes_written := FUsbDeviceConnection.bulkTransfer(FUsbEPBO, Buffer, Size, 100);
    finally
      Buffer.Free;
    end;
  end;
end;

procedure TForm1.bOpenClick(Sender: TObject);
begin
  UsbActive := True;
end;

procedure TForm1.DumpMessage(data: TJavaArray<Byte>; size : integer);
var p, i, c : integer;
    char_line, line : string;
begin
  // Dump message in HEX and Char values

  Memo1.BeginUpdate;
  try
    c := 0;
    i := 0;
    p := 0;
    line := '';
    char_line := '';;
    while (i<size) do begin
      if c < 16 then begin
        line := line + IntToHex(data.Items[i], 2) + ' ';
        if data.Items[i] >= 32 then
          char_line := char_line + chr(data.Items[i])
        else
          char_line := char_line + '.';
        inc(c);
        inc(i);
      end else begin
        Memo1.Lines.Add(IntToHex(p, 6) + ' - ' + line + ' ' + char_line);
        p := i;
        c := 0;
        line := '';
        char_line := '';
      end;
    end;
    if c <> 0 then
      Memo1.Lines.Add(IntToHex(p, 6) + ' - ' + line + stringofchar(' ', 16*3 - Length(line) + 1) + char_line);
  finally
    Memo1.EndUpdate;
  end;
end;

function TForm1.GetUsbActive: boolean;
begin
  result := FUsbActive;
end;

procedure TForm1.SetUsbActive(const Value: boolean);
begin
  if Value then begin

    // Activate the USB inteface with G2

    // Get interface
    Memo1.Lines.Add('# Interfaces ' + IntToStr(FUsbDevice.getInterfaceCount));

    FUsbInterface := FUsbDevice.getInterface(0);
    Memo1.Lines.Add('# Endpoints ' + IntToStr(FUsbInterface.getEndpointCount));

    // Get 3 endpoints
    FUsbEPII := FUsbInterface.getEndpoint(0);
    Memo1.Lines.Add('Endpoint ' + IntToStr(FUsbEPII.getEndpointNumber));

    FUsbEPBI := FUsbInterface.getEndpoint(1);
    Memo1.Lines.Add('Endpoint ' + IntToStr(FUsbEPBI.getEndpointNumber));

    FUsbEPBO := FUsbInterface.getEndpoint(2);
    Memo1.Lines.Add('Endpoint ' + IntToStr(FUsbEPBO.getEndpointNumber));

    // Check permisions
    if FUsbManager.hasPermission(FUsbDevice) then begin
      Memo1.Lines.Add('Permissions o.k.');
    end else begin
      raise Exception.Create('No permission...');
    end;

    // Open device
    FUsbDeviceConnection := FUsbManager.openDevice(FUsbDevice);
    if not assigned( FUsbDeviceConnection) then
      raise Exception.Create('Failed to open device.');

    if not FUsbDeviceConnection.claimInterface(FUsbInterface, True) then
      raise Exception.Create('Failed to claim interface.');

    FUsbActive := Value;

    // Start listening thread
    FUsbThread := TUsbThread.Create(True);
    FUsbThread.FUsbDeviceConnection := FUsbDeviceConnection;
    FUsbThread.FUsbEPII := FUsbEPII;
    FUsbThread.FUsbEPBI := FUsbEPBI;
    FUsbThread.Start;
  end else begin

    // Deactivate the USB connection

    FUsbActive := Value;

    if assigned(FUsbDeviceConnection) then begin

      // Release interface
      if not FUsbDeviceConnection.releaseInterface(FUsbInterface) then
        Memo1.Lines.Add('Failed to release the interface');

      // Close device and free the thread
      if assigned(FUsbThread) then begin
        FUsbThread.Terminate;
        FUsbDeviceConnection.close;
        FUsbThread.DisposeOf;
      end else
        FUsbDeviceConnection.close;

      Memo1.Lines.Add('Device is closed');
    end;
  end;
end;

{ TUSBThread }

constructor TUsbThread.Create( CreateSuspended: Boolean);
begin
  FreeOnTerminate := False;
  inherited;

  FIBuffer := TJavaArray<Byte>.Create(16);
  FBBuffer := TJavaArray<Byte>.Create(8192);
end;

destructor TUsbThread.Destroy;
begin

  FBBuffer.Free;
  FIBuffer.Free;
end;

procedure TUsbThread.Execute;
var size : integer;
begin
  FLogMessage := 'Thread started.';
  synchronize(WriteLog);

  // The G2 sends first a short message over the interrupt endpoint of max 16 bytes
  // This may be followed by a longer message (extended message)

  while assigned(FUsbDeviceConnection) and (not Terminated) do begin
    FBytesRead := FUsbDeviceConnection.bulkTransfer(FUsbEPII, FIBuffer, 16, 0);

    if FBytesRead > 0 then begin

      synchronize(DumpIMessage);

      if FIBuffer.Items[0] and $f = 1 then begin
        // Extended message
        size := (FIBuffer.Items[1] shl 8) or FIBuffer.Items[2];

        FLogMessage := 'Extended message, size ' + IntToStr(size);
        synchronize(WriteLog);

        FBytesRead := FUsbDeviceConnection.bulkTransfer(FUsbEPBI, FBBuffer, size, 0);

        FLogMessage := 'Extended message, bytes read ' + IntToStr(FBytesRead);
        synchronize(WriteLog);

        synchronize(DumpBMessage);
      end else begin
        // Embedded message
      end;
    end;
    sleep(10)
  end;
  FLogMessage := 'Thread terminated.';
  synchronize(WriteLog);
end;

procedure TUsbThread.ProcessMessage;
begin
  // Do something with the message...
end;

procedure TUsbThread.WriteLog;
begin
  Form1.Memo1.Lines.Add(FLogMessage);
end;

procedure TUsbThread.DumpIMessage;
begin
  Form1.DumpMessage(FIBuffer, FBytesRead);
end;

procedure TUsbThread.DumpBMessage;
begin
  Form1.DumpMessage(FBBuffer, FBytesRead);
end;

end.

 

Finally we have to define a device filter in the “AndroidManifest.template.xml” file. This ensures that you get the necessary permissions for opening the device. Also, this enables automatic launch of the application when you plug in the device in your Android tablet!

First you have to make a little xml file, containing the vendor id and product id of the device. In case of the G2 synth, this file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <usb-device vendor-id="4092" product-id="2" class="0" subclass="0" protocol="0" />
</resources>

Save this file as “device_filter.xml” and add it to the project.

You also have to deploy this file to the Android device, so put it into the Deployment manager, with remote path “res\xml”.

device_filter_xml_deploy

Next edit the file “AndroidManifest.template.xml” which should be in your project folder after compiling the application.

Put in

<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />

As a child in the “activity” element, and put

<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />

As a child to the “intent-filter” element. So the dile should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="%package%"
        android:versionCode="%versionCode%"
        android:versionName="%versionName%">

    <!-- This is the platform API where NativeActivity was introduced. -->
    <uses-sdk android:minSdkVersion="%minSdkVersion%" />
<%uses-permission%>
    <application android:persistent="%persistent%" 
        android:restoreAnyVersion="%restoreAnyVersion%" 
        android:label="%label%" 
        android:installLocation="%installLocation%" 
        android:debuggable="%debuggable%" 
        android:largeHeap="%largeHeap%"
        android:icon="%icon%"
        android:theme="%theme%">
        <!-- Our activity is a subclass of the built-in NativeActivity framework class.
             This will take care of integrating with our NDK code. -->
        <activity android:name="com.embarcadero.firemonkey.FMXNativeActivity"
                android:label="%activityLabel%"
                android:configChanges="orientation|keyboardHidden">
            <!-- Tell NativeActivity the name of our .so -->
            <meta-data android:name="android.app.lib_name"
                android:value="%libNameValue%" />
            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter> 
        </activity>
        <receiver android:name="com.embarcadero.firemonkey.notifications.FMXNotificationAlarm" />
    </application>
</manifest>   
<!-- END_INCLUDE(manifest) -->

Ok. So now you should be able to deploy the application to the Android device and connect to the G2.

9 thoughts on “Delphi Android USB Interface with the G2

  1. Pingback: Androidapi.JNI Interface und function : TJavaArray<>; cdecl; - Delphi-PRAXiS

  2. Pingback: XE6 + Android - Delphi-PRAXiS

  3. manuel

    Good afternoon
    This code works for Fire monkey delphi 10.2. or you have to make some change.
    I tried the code and it marks me some errors
    JavaObject = SharedActivityContext.getSystemService (TJContext.JvaClass.USB_SERVICE);
    I appreciate your support

    Reply
    1. bverhue Post author

      Hi,

      I have moved on to other projects, I just keep the site up for people looking for an example.

      Bruno

      Reply
  4. Rulin

    I have written code

    Memo1.Lines.Append(‘Endpoint count’+IntToStr(LocalUsbInterface.getEndpointCount));
    for I := 0 to LocalUsbInterface.getEndpointCount-1 do //Их всего два
    begin

    FUsbEP := LocalUsbInterface.getEndpoint(i);
    if FUsbEP.getType=3 then
    begin
    if FUsbEP.getDirection = TJUsbConstantsUSB_DIR_OUT then
    begin
    if FEpOut=nil then FEpOut := FUsbEP;
    Memo1.Lines.Append(‘Endpoint’+IntToStr(i));
    end
    else if FUsbEP.getDirection = TJUsbConstantsUSB_DIR_IN then
    begin
    if FEpIn=nil then FEpIn := FUsbEP;
    end;
    end;
    end;

    There are only 2 Enpoints

    How you take 3 Enpoints?

    Reply
  5. necoArc

    i managed to use this example to connect into a usb printer and send comands by esc/pos

    i added methods to these interfaces on the JNI

    JUsbDeviceConnection = interface;
    JUsbDeviceConnectionClass = interface(JObjectClass)
    [‘{447D85BC-BA61-4BBA-A803-563071D90D85}’]
    end;

    [JavaSignature(‘android/hardware/usb/UsbDeviceConnection’)]
    JUsbDeviceConnection = interface(JObject)
    [‘{D613CA69-DD0E-404A-A064-828E09429145}’]
    function init:JUsbDeviceConnection; cdecl;
    function bulkTransfer(UsbEndpoint: JUsbEndpoint; data: TJavaArray; length : integer; timeout : integer): integer; cdecl;
    function claimInterface(UsbInterface: JUsbInterface; ForceClaim: boolean): boolean; cdecl;
    procedure close; cdecl;
    function releaseInterface(UsbInterface: JUsbInterface): boolean; cdecl;

    function controlTransfer(requestType, request, value, index: Integer;
    buffer: TJavaArray; length, timeout: Integer): Integer; cdecl;

    end;

    TJUsbDeviceConnection = class(TJavaGenericImport) end;

    JUsbManager = interface;
    JUsbManagerClass = interface(JObjectClass)
    [‘{D4A4DDAC-EE30-4123-A0BE-76F8E95FAC55}’]
    end;

    [JavaSignature(‘android/hardware/usb/UsbManager’)]
    JUsbManager = interface(JObject)
    [‘{5E8A5FA6-64DA-4C90-9D52-988D66E6728E}’]
    function getDeviceList:JHashMap; cdecl;
    function hasPermission(UsbDevice:JUsbDevice):boolean; cdecl;
    function openDevice(UsbDevice:JUsbDevice):JUsbDeviceConnection; cdecl;
    procedure requestPermission(device: JUsbDevice; pi: JPendingIntent); cdecl; overload;
    // procedure requestPermission(accessory: JUsbAccessory; pi: JPendingIntent); cdecl; overload;

    end;

    TJUsbManager = class(TJavaGenericImport) end;

    and on my form i had to add a button request permission and i do not use the same interface as on the exemple G2 device uses interface 0
    FUsbInterface := FUsbDevice.getInterface(0);

    i’m using interface 10 CDC Data

    Reply
    1. necoArc

      unit UFormPrinc;

      interface

      uses
      System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
      FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
      FMX.Memo, FMX.StdCtrls,
      Androidapi.JNIBridge,
      Androidapi.JNI.GraphicsContentViewText,
      Androidapi.JNI.JavaTypes,
      Androidapi.JNI.USB,
      Androidapi.JNI.App,
      Androidapi.Helpers,
      Androidapi.JNI.Os,
      FMX.Platform.Android, FMX.Memo.Types, FMX.Controls.Presentation, FMX.ScrollBox;

      type
      TForm1 = class(TForm)
      Memo1: TMemo;
      Panel1: TPanel;
      bListDevices: TButton;
      bOpen: TButton;
      bClose: TButton;
      bMessage: TButton;
      bRequestPermission: TButton;
      procedure bListDevicesClick(Sender: TObject);
      procedure bRequestPermissionClick(Sender: TObject);
      procedure bOpenClick(Sender: TObject);
      procedure bCloseClick(Sender: TObject);
      procedure bMessageClick(Sender: TObject);
      private
      FUsbActive: boolean;
      FUsbManager: JUSBManager;
      FUsbDevice: JUSBDevice;
      FUsbInterface: JUSBInterface;
      FUsbEPBO: JUSBEndPoint;
      FUsbDeviceConnection: JUSBDeviceConnection;
      procedure OpenDevice;
      procedure InitCDCSerial;
      public
      end;

      const
      USB_DIR_OUT = 0;
      USB_DIR_IN = 128;

      USB_ENDPOINT_XFER_CONTROL = 0;
      USB_ENDPOINT_XFER_ISOC = 1;
      USB_ENDPOINT_XFER_BULK = 2;
      USB_ENDPOINT_XFER_INT = 3;

      var
      Form1: TForm1;

      implementation

      uses
      FMX.Helpers.Android;

      {$R *.fmx}

      procedure TForm1.bListDevicesClick(Sender: TObject);
      var
      JavaObject: JObject;
      DeviceList: JHashMap;
      Device: JUSBDevice;
      i: JIterator;
      s: JString;
      begin
      Memo1.Lines.Add(‘=== Procurando dispositivos USB ===’);

      try
      // Get pointer to UsbManager
      JavaObject := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.USB_SERVICE);
      FUsbManager := TJUSBManager.Wrap((JavaObject as ILocalObject).GetObjectID);

      // Get a list of connected slave devices
      DeviceList := FUsbManager.getDeviceList;
      s := DeviceList.toString;
      Memo1.Lines.Add(‘Lista de dispositivos: ‘ + JStringToString(s));

      // Procura dispositivos
      FUsbDevice := nil;
      i := DeviceList.values.iterator;

      while i.hasNext do
      begin
      Device := TJUSBDevice.Wrap((i.next as ILocalObject).GetObjectID);

      Memo1.Lines.Add(Format(‘Dispositivo encontrado – VendorID: %d, ProductID: %d’,
      [Device.getVendorId, Device.getProductId]));

      // Verifica se é sua impressora (vendor-id 2843, product-id 3)
      if (Device.getVendorId = 2843) and (Device.getProductId = 3) then
      begin
      FUsbDevice := Device;
      Memo1.Lines.Add(‘>>> Impressora encontrada!’);
      end;
      end;

      if Assigned(FUsbDevice) then
      begin
      bRequestPermission.Enabled := True;
      Memo1.Lines.Add(‘Clique em “Solicitar Permissão” para continuar’);
      end
      else
      begin
      Memo1.Lines.Add(‘Nenhuma impressora conectada!’);
      bRequestPermission.Enabled := False;
      end;
      except
      on E: Exception do
      Memo1.Lines.Add(‘ERRO ao listar: ‘ + E.Message);
      end;
      end;

      procedure TForm1.bRequestPermissionClick(Sender: TObject);
      var
      LIntent: JIntent;
      LPendingIntent: JPendingIntent;
      begin
      if not Assigned(FUsbDevice) then
      begin
      Memo1.Lines.Add(‘Nenhum dispositivo selecionado!’);
      Exit;
      end;

      Memo1.Lines.Add(‘=== Verificando permissões ===’);

      try
      if FUsbManager.hasPermission(FUsbDevice) then
      begin
      Memo1.Lines.Add(‘Permissão já concedida!’);
      bOpen.Enabled := True;
      Memo1.Lines.Add(‘Clique em “Abrir” para conectar’);
      end
      else
      begin
      Memo1.Lines.Add(‘Solicitando permissão USB…’);

      // Solicita permissão
      LIntent := TJIntent.JavaClass.init(
      StringToJString(‘com.embarcadero.firemonkey.USB_PERMISSION’));

      LPendingIntent := TJPendingIntent.JavaClass.getBroadcast(
      TAndroidHelper.Context,
      0,
      LIntent,
      TJPendingIntent.JavaClass.FLAG_MUTABLE);

      FUsbManager.requestPermission(FUsbDevice, LPendingIntent);

      Memo1.Lines.Add(‘Diálogo de permissão deve aparecer…’);
      Memo1.Lines.Add(‘Após conceder, clique em “Solicitar Permissão” novamente’);
      end;
      except
      on E: Exception do
      Memo1.Lines.Add(‘ERRO ao solicitar permissão: ‘ + E.Message);
      end;
      end;

      procedure TForm1.bOpenClick(Sender: TObject);
      begin
      OpenDevice;
      end;

      procedure TForm1.OpenDevice;
      var
      I, J: Integer;
      Intf: JUsbInterface;
      EP: JUsbEndPoint;
      begin
      Memo1.Lines.Add(‘===> Abrindo dispositivo ===’);

      if not FUsbManager.hasPermission(FUsbDevice) then
      begin
      Memo1.Lines.Add(‘Sem permissão USB’);
      Exit;
      end;

      FUsbDeviceConnection := FUsbManager.openDevice(FUsbDevice);
      if not Assigned(FUsbDeviceConnection) then
      begin
      Memo1.Lines.Add(‘Falha ao abrir dispositivo’);
      Exit;
      end;

      Memo1.Lines.Add(‘Dispositivo aberto’);

      FUsbInterface := nil;
      FUsbEPBO := nil;

      // 🔎 PROCURA INTERFACE CDC DATA (Classe 10)
      for I := 0 to FUsbDevice.getInterfaceCount – 1 do
      begin
      Intf := FUsbDevice.getInterface(I);

      Memo1.Lines.Add(Format(
      ‘Interface %d – Classe=%d SubClasse=%d Protocolo=%d’,
      [I,
      Intf.getInterfaceClass,
      Intf.getInterfaceSubclass,
      Intf.getInterfaceProtocol]
      ));

      if Intf.getInterfaceClass = 10 then // CDC Data
      begin
      Memo1.Lines.Add(‘>>> Interface CDC DATA encontrada’);

      if not FUsbDeviceConnection.claimInterface(Intf, True) then
      begin
      Memo1.Lines.Add(‘Falha ao reivindicar interface’);
      Exit;
      end;

      for J := 0 to Intf.getEndpointCount – 1 do
      begin
      EP := Intf.getEndpoint(J);

      Memo1.Lines.Add(Format(
      ‘Endpoint %d -> Direção=%d Tipo=%d’,
      [J, EP.getDirection, EP.getType]
      ));

      // 🔴 BULK OUT = envio ESC/POS
      if (EP.getDirection = USB_DIR_OUT) and
      (EP.getType = USB_ENDPOINT_XFER_BULK) then
      begin
      FUsbInterface := Intf;
      FUsbEPBO := EP;
      Memo1.Lines.Add(‘>>> Endpoint BULK OUT encontrado!’);
      InitCDCSerial;
      Sleep(200);

      Break;
      end;
      end;
      end;

      if Assigned(FUsbEPBO) then
      Break;
      end;

      if not Assigned(FUsbEPBO) then
      begin
      Memo1.Lines.Add(‘ERRO: Endpoint BULK OUT não encontrado’);
      Exit;
      end;

      FUsbActive := True;
      Memo1.Lines.Add(‘=== IMPRESSORA CONECTADA (CDC) ===’);
      end;

      procedure TForm1.bCloseClick(Sender: TObject);
      begin
      Memo1.Lines.Add(‘=== Fechando dispositivo ===’);

      try
      if Assigned(FUsbDeviceConnection) then
      begin
      if Assigned(FUsbInterface) then
      FUsbDeviceConnection.releaseInterface(FUsbInterface);

      FUsbDeviceConnection.close;
      FUsbDeviceConnection := nil;

      Memo1.Lines.Add(‘Dispositivo fechado’);
      end;

      FUsbActive := False;
      bMessage.Enabled := False;
      bClose.Enabled := False;
      bOpen.Enabled := True;
      except
      on E: Exception do
      Memo1.Lines.Add(‘ERRO ao fechar: ‘ + E.Message);
      end;
      end;

      procedure TForm1.bMessageClick(Sender: TObject);
      var
      Buffer: TJavaArray;
      BytesWritten: Integer;
      TestText: string;
      begin
      if not Assigned(FUsbDeviceConnection) then
      begin
      Memo1.Lines.Add(‘Dispositivo não conectado!’);
      Exit;
      end;

      Memo1.Lines.Add(‘=== Enviando comando de teste ===’);

      try
      // Teste simples: ESC @ (inicializa impressora)
      Buffer := TJavaArray.Create(2);
      try
      Buffer.Items[0] := $1B; // ESC
      Buffer.Items[1] := $40; // @

      BytesWritten := FUsbDeviceConnection.bulkTransfer(
      FUsbEPBO,
      Buffer,
      2,
      1000);

      Memo1.Lines.Add(Format(‘Bytes enviados: %d’, [BytesWritten]));

      if BytesWritten > 0 then
      Memo1.Lines.Add(‘Comando enviado com sucesso!’)
      else
      Memo1.Lines.Add(‘ERRO: Nenhum byte enviado’);
      finally
      Buffer.Free;
      end;

      // Agora envia texto de teste
      TestText := ‘TESTE DE IMPRESSAO’#10#10;
      Buffer := TJavaArray.Create(Length(TestText));
      try
      for var I := 0 to Length(TestText) – 1 do
      Buffer.Items[I] := Ord(TestText[I + 1]);

      BytesWritten := FUsbDeviceConnection.bulkTransfer(
      FUsbEPBO,
      Buffer,
      Length(TestText),
      1000);

      Memo1.Lines.Add(Format(‘Texto enviado: %d bytes’, [BytesWritten]));
      finally
      Buffer.Free;
      end;

      // Avança papel (ESC d 3)
      Buffer := TJavaArray.Create(3);
      try
      Buffer.Items[0] := $1B; // ESC
      Buffer.Items[1] := $64; // d
      Buffer.Items[2] := 3; // 3 linhas

      BytesWritten := FUsbDeviceConnection.bulkTransfer(
      FUsbEPBO,
      Buffer,
      3,
      1000);

      Memo1.Lines.Add(‘Papel avançado’);
      finally
      Buffer.Free;
      end;

      Memo1.Lines.Add(‘=== Impressão concluída! ===’);
      except
      on E: Exception do
      Memo1.Lines.Add(‘ERRO ao imprimir: ‘ + E.Message);
      end;
      end;

      procedure TForm1.InitCDCSerial;
      var
      LineCoding: TJavaArray;
      Ret: Integer;
      begin
      // Line Coding (7 bytes)
      // 115200 baud, 8N1
      LineCoding := TJavaArray.Create(7);

      // Baudrate 115200 = 0x0001C200 (little endian)
      LineCoding[0] := $00;
      LineCoding[1] := $C2;
      LineCoding[2] := $01;
      LineCoding[3] := $00;

      LineCoding[4] := 0; // Stop bits: 1
      LineCoding[5] := 0; // Parity: None
      LineCoding[6] := 8; // Data bits

      // SET_LINE_CODING (0x20)
      Ret := FUsbDeviceConnection.controlTransfer(
      $21, // REQUEST_TYPE (Host to Device | Class | Interface)
      $20, // SET_LINE_CODING
      0,
      FUsbInterface.getId,
      LineCoding,
      7,
      1000
      );

      Memo1.Lines.Add(‘SET_LINE_CODING retorno: ‘ + Ret.ToString);

      // SET_CONTROL_LINE_STATE (0x22)
      Ret := FUsbDeviceConnection.controlTransfer(
      $21,
      $22,
      3, // DTR + RTS
      FUsbInterface.getId,
      nil,
      0,
      1000
      );

      Memo1.Lines.Add(‘SET_CONTROL_LINE_STATE retorno: ‘ + Ret.ToString);
      end;

      end.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *