Bluetooth Low Energy (BLE) library

jbullock

Member
Licensed User
Longtime User
When do you think you will add the capability to write to a characteristic? To read a sensor value on the TI sensor tag you have to turn on the sensor by writing to the sensor. I think your program is outstanding. I hate having to switch to eclipse to complete my test project.
 

jbullock

Member
Licensed User
Longtime User
If you like I can post the library code. You can then build an extended library (it will be easier than building the whole project with Eclipse).
Absolutely. I think you have a great app. Anytime I can use it verses something as complicated as eclipse I am all in........

Have you seen the Texas Instruments application accelerator for android. It has the classes all defined to allow you to access a BLE device. It is a quick start eclipse project. You maybe able to pull the code from that. Let me know if you want me to send you the link.
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
BLE code:
B4X:
package anywheresoftware.b4a.objects;

import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.content.pm.PackageManager;
import anywheresoftware.b4a.AbsObjectWrapper;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Permissions;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;
import anywheresoftware.b4a.keywords.Common;
import anywheresoftware.b4a.objects.collections.Map;

@Version(1f)
@ShortName("BleManager")
@Permissions(values={"android.permission.BLUETOOTH", "android.permission.BLUETOOTH_ADMIN"})
@Events(values={"DiscoveryFinished",
     "DeviceFound (Name As String, MacAddress As String)", "Connected (Services As Map)", "Disconnected",
"CharacteristicRead (Success As Boolean, Characteristic As BleCharacteristic)"})
public class BleManager {
   private String eventName;
   private BluetoothAdapter adapter;
   private ConcurrentHashMap<String, BluetoothDevice> devices = new ConcurrentHashMap<String, BluetoothDevice>();
   private BA ba;
   private BluetoothGatt gatt;
   private LeScanCallback scanCallback;
   /**
    * Initializes the object and sets the subs that will handle the events.
    */
   public void Initialize(BA ba, String EventName) {
     this.eventName = EventName.toLowerCase(BA.cul);
     adapter = BluetoothAdapter.getDefaultAdapter();
     this.ba = ba;
   }
   /**
    * Tests whether BLE is supported.
    */
   public boolean getBleSupported() {
     return (BA.applicationContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE));

   }
   /**
    * Starts scanning for devices.
    *PeriodMs - Scan period measured in milliseconds.
    *ServiceUUIDs - An array of UUIDs. Only devices supporting one of the UUIDs will be discovered. Pass Null to discover all devices.
    *
    *Returns True if the process started successfully.
    */
   public boolean Scan(final BA ba, long PeriodMs, String[] ServiceUUIDs) {
     scanCallback = new LeScanCallback() {

       @Override
       public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
         devices.put(device.getAddress(), device);
         ba.raiseEventFromDifferentThread(BleManager.this, null, 0, eventName + "_devicefound", false,
             new Object[] {device.getName(), device.getAddress()});
       }

     };
     BA.handler.postDelayed(new Runnable() {
       @Override
       public void run() {
         if (scanCallback == null)
           return;
         adapter.stopLeScan(scanCallback);
         ba.raiseEventFromDifferentThread(BleManager.this, null, 0, eventName + "_discoveryfinished", false, null);
       }
     }, PeriodMs);
     if (ServiceUUIDs == null || ServiceUUIDs.length == 0)
       return adapter.startLeScan(scanCallback);
     else {
       UUID[] u = new UUID[ServiceUUIDs.length];
       for (int i = 0;i < u.length;i++)
         u[i] = UUID.fromString(ServiceUUIDs[i]);
       return adapter.startLeScan(u, scanCallback);
     }
   }
   /**
    * Connects to the device with the given MAC address. You can only connect to a previously discovered device.
    *AutoConnect - Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote
    *  device becomes available (true).
    */
   public void Connect(String MacAddress, boolean AutoConnect) {
     if (scanCallback != null) {
       adapter.stopLeScan(scanCallback);
       scanCallback = null;
     }
     BluetoothDevice bd = devices.get(MacAddress);
     if (bd == null)
       throw new RuntimeException("MacAddress not found. Make sure to call Scan before trying to connect.");
     bd.connectGatt(BA.applicationContext, AutoConnect, new GattCallback());
   }
   /**
    * Starts an asynchronous reading of a characteristic.
    */
   public boolean ReadCharacteristic(GattCharacteristic Characteristic) {
     return gatt.readCharacteristic(Characteristic.getObject());
   }
   @ShortName("BleService")
   public static class GattServiceWrapper extends AbsObjectWrapper<BluetoothGattService> {

     /**
      * Returns the service UUID.
      */
     public String getUuid() {
       return getObject().getUuid().toString();
     }
     public Map GetCharacteristics() {
       Map m = new Map();
       m.Initialize();
       for (BluetoothGattCharacteristic bgc : getObject().getCharacteristics()) {
         m.Put(bgc.getUuid().toString(),  bgc);
       }
       return m;
     }

   }
   @ShortName("BleCharacteristic")
   public static class GattCharacteristic extends AbsObjectWrapper<BluetoothGattCharacteristic> {

     /**
      * Characteristic value format type uint8
      */
     public static final int FORMAT_UINT8 = 0x11;

     /**
      * Characteristic value format type uint16
      */
     public static final int FORMAT_UINT16 = 0x12;

     /**
      * Characteristic value format type uint32
      */
     public static final int FORMAT_UINT32 = 0x14;

     /**
      * Characteristic value format type sint8
      */
     public static final int FORMAT_SINT8 = 0x21;

     /**
      * Characteristic value format type sint16
      */
     public static final int FORMAT_SINT16 = 0x22;

     /**
      * Characteristic value format type sint32
      */
     public static final int FORMAT_SINT32 = 0x24;

     /**
      * Characteristic value format type sfloat (16-bit float)
      */
     public static final int FORMAT_SFLOAT = 0x32;

     /**
      * Characteristic value format type float (32-bit float)
      */
     public static final int FORMAT_FLOAT = 0x34;
     /**
      * Gets the characteristic Uuid.
      */
     public String getUuid() {
       return getObject().getUuid().toString();
     }
     /**
      * Returns the raw value.
      */
     public byte[] GetValue() {
       return getObject().getValue();
     }
     public int GetIntValue(int FormatType, int Offset) {
       return getObject().getIntValue(FormatType, Offset);
     }
     public float GetFloatValue(int FormatType, int Offset) {
       return getObject().getFloatValue(FormatType, Offset);
     }
     public String GetStringValue(int Offset) {
       return getObject().getStringValue(Offset);
     }
   }
   class GattCallback extends BluetoothGattCallback {
     /**
      * Callback indicating when GATT client has connected/disconnected to/from a remote
      * GATT server.
      *
      * @param gatt GATT client
      * @param status Status of the connect or disconnect operation.
      *  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      * @param newState Returns the new connection state. Can be one of
      *  {@link BluetoothProfile#STATE_DISCONNECTED} or
      *  {@link BluetoothProfile#STATE_CONNECTED}
      */
     @Override
     public void onConnectionStateChange(BluetoothGatt gatt, int status,
         int newState) {
       if (newState == BluetoothProfile.STATE_CONNECTED) {
         BleManager.this.gatt = gatt;
         Common.Log("Discovering services.");
         gatt.discoverServices();
       }
       else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
         if (gatt != null)
           gatt.close();
         ba.raiseEventFromDifferentThread(BleManager.this,null, 0, eventName + "_disconnected", false, null);
       }

     }

     /**
      * Callback invoked when the list of remote services, characteristics and descriptors
      * for the remote device have been updated, ie new services have been discovered.
      *
      * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
      * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
      *  has been explored successfully.
      */
     @Override
     public void onServicesDiscovered(BluetoothGatt gatt, int status) {
       if (status != BluetoothGatt.GATT_SUCCESS) {
         Common.Log("Service discovery failed.");
         gatt.disconnect();
       }
       else {
         Map m = new Map();
         m.Initialize();
         for (BluetoothGattService s : gatt.getServices()) {
           m.Put(s.getUuid().toString(), s);
         }
         ba.raiseEventFromDifferentThread(BleManager.this,null, 0, eventName + "_connected", false, new Object[] {m});
       }
     }

     /**
      * Callback reporting the result of a characteristic read operation.
      *
      * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
      * @param characteristic Characteristic that was read from the associated
      *  remote device.
      * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
      *  was completed successfully.
      */
     @Override
     public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
         int status) {
       ba.raiseEventFromDifferentThread(BleManager.this, null, 0, eventName + "_characteristicread", false,
           new Object[] {status == BluetoothGatt.GATT_SUCCESS,
           AbsObjectWrapper.ConvertToWrapper(new GattCharacteristic(), characteristic)});
     }

     /**
      * Callback indicating the result of a characteristic write operation.
      *
      * <p>If this callback is invoked while a reliable write transaction is
      * in progress, the value of the characteristic represents the value
      * reported by the remote device. An application should compare this
      * value to the desired value to be written. If the values don't match,
      * the application must abort the reliable write transaction.
      *
      * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
      * @param characteristic Characteristic that was written to the associated
      *  remote device.
      * @param status The result of the write operation
      *  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      */
     @Override
     public void onCharacteristicWrite(BluetoothGatt gatt,
         BluetoothGattCharacteristic characteristic, int status) {
     }

     /**
      * Callback triggered as a result of a remote characteristic notification.
      *
      * @param gatt GATT client the characteristic is associated with
      * @param characteristic Characteristic that has been updated as a result
      *  of a remote notification event.
      */
     @Override
     public void onCharacteristicChanged(BluetoothGatt gatt,
         BluetoothGattCharacteristic characteristic) {
     }

     /**
      * Callback reporting the result of a descriptor read operation.
      *
      * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor}
      * @param descriptor Descriptor that was read from the associated
      *  remote device.
      * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
      *  was completed successfully
      */
     @Override
     public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
         int status) {
     }

   }
}

Have you seen the Texas Instruments application accelerator for android.
Have you tried it? Is it running more stable? I expect the results to be similar as eventually the problem is in a lower level. I tested it with Google samples and it behaved in the same way.
 

jbullock

Member
Licensed User
Longtime User
I haven't tried this yet but I can use TI`s own app and get it to hang up. The issue is caused by the response time of the sensor and the operating system. If the response exceeds 7 ms the sensor and android have issues. With the TI sensor if I turn on more than two sensors to read it will lock up most of the time. If I turn on the humidity sensor it will lock up every time. The humidity sensor's response time exceeds the 7 ms. Android 19 came out yesterday. I am not sure if they fixed the problem or not. They need to add the capability to extend the response timer. When the timer is exceeded most of the time it takes a battery removal at the sensor to clear the stack issue left by the error.

I will read and see if I can make what you sent me work. Thanks.
 

jbullock

Member
Licensed User
Longtime User
I hate to bother you but I am getting an error when I try to create the library.

upload_2013-11-4_19-17-44.png


Do you see something I am missing or do I need to dive into the library. I am assuming it is ready to compile.

Thanks
 

jbullock

Member
Licensed User
Longtime User
Starting step: Compiling Java code.
javac 1.7.0_45
C:\BLEManager\src\anywheresoftware\b4a\objects\BLEManager.java:31: error: class BleManager is public, should be declared in a file named BleManager.java
public class BleManager {
^
1 error


Error.


The setup for the Simple Library Compiler is as follows:

Project Folder: C:\BleManager
Library Name: BLEManager
-b4aignore: org,com.android,anywheresoftware.b4a.objects.com.hoho
 

Attachments

  • BLEManager.zip
    3 KB · Views: 357

Antti Mauranen

Member
Licensed User
Longtime User
Hello Erel, do you already have a timetable for the BLE library?

Will it be possible to have multible simultaneous connections from application program, from one master/client to many slaves/servers?

Is this possible already using the current library?
 

Antti Mauranen

Member
Licensed User
Longtime User
Hello Erel, ok.

Do you mean, that I make a table of BleManager(s) (and everything else I need), then initialize them one by one, when I find new device (I have BlueGiga BLE(D)112 devices) and they all use the same BLE capable radio, I mean all the simultaneous connections use the one tablet radio.

I had an illusion, that you have to use one BleManager and then have several BluetoothGatt(s) by connectGatt.

So, will I have callbacks e.g. BLE0_Connected, BLE1_Connected, ..., BLE9_Connected.

I experiment with Nexus 7 2012, using CyanogenMod 10.2, I hope Nexus is capable to this, it should be.

I will experiment more tomorrow.
 
Last edited:

Antti Mauranen

Member
Licensed User
Longtime User
Hello Erel, sorry to bother you again but I did as you told to do:

1. scanned with a BleManager -> found devices
2. dimmed new BleManager, e.g manager0, initialized it and tried to connect with the MacAddress found, java throws exception:
java.lang.RuntimeException: MacAddress not found. Make sure to call Scan before trying to connect.


3. when I read the library source or docs, I can see that you can connect only previously discovered device
4. and that seems to mean previously discovered device in the BleManager you try to open now, e.g. manager0
5. is this what happens, is there any simple solution or will I scan again with e.g. manager0 and when I find the previous mac, connect
 

Antti Mauranen

Member
Licensed User
Longtime User
Hello Erel,

did that scan by every BleManager and yes, it works. So first scan by "mastermanager" -> for every found device dim own BleManager and initialize. Then one by one scan and connect.

Works great, I have two devices "heart rate" and "accelerometer/gyroscope". I can connect to them simultaneously (at least I get readings from both devices simultaneously, heart rate bangs from 0x00 to 0xff and x/y/z acceleration varies when in move the acc/qyro device). I can turn off one device, it disconnects, turn it on again, and I can connect it again.

I can do new scan by "mastermanager" and during the scan connected devices give readings.

And the most amazing thing is when i flip the tablet from vertical to horizontal, the connections remain on :).

Hope you get the library ready in (near) future, I am swetting when I think I have to do this thing using SDK.
 

Antti Mauranen

Member
Licensed User
Longtime User
Hello Erel, any news about the timetable for ble library?

I was able to program my prototype using your current library. I added some methods to write and notification handling into your library using SimpleLibraryCompiler (very good tool indeed) and could get everything I needed into the proto.
 
Top