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) {
}
}
}