BLEHandler.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. package nl.digitalthings.mebarista;
  2. import android.annotation.TargetApi;
  3. import android.bluetooth.BluetoothAdapter;
  4. import android.bluetooth.BluetoothDevice;
  5. import android.bluetooth.BluetoothGatt;
  6. import android.bluetooth.BluetoothGattCallback;
  7. import android.bluetooth.BluetoothGattCharacteristic;
  8. import android.bluetooth.BluetoothGattService;
  9. import android.bluetooth.BluetoothManager;
  10. import android.bluetooth.BluetoothProfile;
  11. import android.bluetooth.le.BluetoothLeScanner;
  12. import android.bluetooth.le.ScanCallback;
  13. import android.bluetooth.le.ScanFilter;
  14. import android.bluetooth.le.ScanResult;
  15. import android.bluetooth.le.ScanSettings;
  16. import android.content.Context;
  17. import android.os.Build;
  18. import android.os.Handler;
  19. import android.os.Message;
  20. import android.os.ParcelUuid;
  21. import android.util.Log;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.lang.reflect.InvocationTargetException;
  26. import java.lang.reflect.Method;
  27. import java.util.Arrays;
  28. import java.util.Collections;
  29. import java.util.List;
  30. import java.util.UUID;
  31. import java.util.concurrent.Semaphore;
  32. // http://stackoverflow.com/questions/825732/how-can-i-implement-an-outputstream-that-i-can-rewind
  33. // https://github.com/robokoding/STK500
  34. // https://github.com/ksksue/PhysicaloidLibrary
  35. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  36. public class BLEHandler {
  37. private static final String TAG = "BLE";
  38. public class ble_os extends OutputStream {
  39. byte[] queue = new byte[20];
  40. int queued = 0;
  41. @Override
  42. public void write( int b ) throws IOException {
  43. queue[ queued++ ] = (byte)b;
  44. if( queued >= queue.length )
  45. flush( );
  46. }
  47. @Override
  48. public void flush( ) throws IOException {
  49. // If queue is empty, abort
  50. if( queued == 0 )
  51. return;
  52. // Prepare message by copying data in
  53. Message m = new Message();
  54. m.obj = Arrays.copyOfRange( queue, 0, queued );
  55. // Clear queue once copied
  56. queued = 0;
  57. for( int i = 0; i < queue.length; i++ )
  58. queue[ i ] = 0;
  59. // Test or wait for the output channel to be free
  60. try {
  61. output_wait.acquire();
  62. } catch (InterruptedException e) {
  63. throw new IOException( e.getMessage( ) );
  64. }
  65. // Hand the actual sending over to the BLE owning thread
  66. write_handler.sendMessage( m );
  67. }
  68. }
  69. public class ble_is extends InputStream {
  70. @Override
  71. public int read () throws IOException {
  72. // Test or wait for data being available
  73. try {
  74. ble_available.acquire( );
  75. } catch (InterruptedException e) {
  76. throw new IOException( e.getMessage() );
  77. }
  78. // Fetch first byte from queue and remove it
  79. int res = input_sb.charAt( 0 );
  80. input_sb.delete( 0, 1 );
  81. return res;
  82. }
  83. @Override
  84. public int available( ) {
  85. return input_sb.length( );
  86. }
  87. }
  88. // service
  89. BaristaService service;
  90. // BLE
  91. BluetoothGattService mBluetoothGattService;
  92. BluetoothGatt mBluetoothGatt;
  93. BluetoothGattCharacteristic characteristic;
  94. BluetoothDevice bledevice;
  95. StringBuffer input_sb;
  96. BluetoothManager btmanager;
  97. public ble_os os;
  98. public ble_is is;
  99. Logx logx;
  100. byte[] fw;
  101. private final Semaphore ble_available = new Semaphore( 40960, false ), output_wait = new Semaphore( 1, false );
  102. boolean connecting = false;
  103. boolean start_firmware = false;
  104. Handler write_handler = new Handler() {
  105. @Override
  106. public void handleMessage( Message msg ) {
  107. if( characteristic == null )
  108. return;
  109. characteristic.setValue( ( byte[] ) msg.obj );
  110. if( !mBluetoothGatt.writeCharacteristic( characteristic ) )
  111. Log.e( TAG, "write failed" );
  112. }
  113. };
  114. public Handler flush_handler = new Handler() {
  115. @Override
  116. public void handleMessage( Message msg ) {
  117. try {
  118. os.flush();
  119. } catch (IOException e) {
  120. e.printStackTrace();
  121. }
  122. }
  123. };
  124. // final BluetoothManager mBluetoothManager;
  125. public BLEHandler( BaristaService serv, BluetoothManager ble_manager) {
  126. // mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
  127. service = serv;
  128. btmanager = ble_manager;
  129. input_sb = new StringBuffer();
  130. is = new ble_is();
  131. os = new ble_os();
  132. ble_available.drainPermits( );
  133. }
  134. // https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
  135. // http://stackoverflow.com/questions/24780714/hm-10-bluetooth-module-ble-4-0-keep-losing-connection
  136. // http://stackoverflow.com/questions/28018722/android-could-not-connect-to-bluetooth-device-on-lollipop
  137. final BluetoothGattCallback bgc = new BluetoothGattCallback() {
  138. @Override
  139. public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
  140. if (newState == BluetoothProfile.STATE_CONNECTED) {
  141. // BLE_stop();
  142. Log.i(TAG, "STATE_CONNECTED");
  143. // mBluetoothGatt.discoverServices();
  144. mBluetoothAdapter.stopLeScan( mLeScanCallback );
  145. bledevice = gatt.getDevice();
  146. gatt.discoverServices();
  147. }
  148. if( newState == BluetoothProfile.STATE_DISCONNECTED ) {
  149. Log.i(TAG, "STATE_DISCONNECTED");
  150. // gatt.close();
  151. if( bledevice == null ) {
  152. Log.i( TAG, "STATE_DISCONNECTED: was already disconnected, skipping" );
  153. return;
  154. }
  155. if( characteristic != null ) {
  156. gatt.setCharacteristicNotification( characteristic, false );
  157. characteristic = null; }
  158. //gatt.close();
  159. mBluetoothGatt.close();
  160. mBluetoothGatt = null;
  161. bledevice = null;
  162. // gatt.setCharacteristicNotification( characteristic, false ); // Prevents double notifications if reconnected
  163. // connecting = false;
  164. // bledevice = null;
  165. service.connected( null, null );
  166. // TODO: moet dit wel?
  167. //BluetoothAdapter mBluetoothAdapter = btmanager.getAdapter(); // BluetoothAdapter.getDefaultAdapter();
  168. if( !mBluetoothAdapter.isDiscovering() ) {
  169. mBluetoothAdapter.stopLeScan(mLeScanCallback);
  170. // mBluetoothAdapter.startLeScan(new UUID[]{UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb")}, mLeScanCallback); // TODO, when to stop
  171. setup();
  172. }
  173. }
  174. Log.i( TAG, "connectionstatechange");
  175. }
  176. @Override
  177. // Characteristic notification
  178. public void onCharacteristicChanged(BluetoothGatt gatt,
  179. BluetoothGattCharacteristic characteristic) {
  180. String out, v = characteristic.getStringValue( 0 );
  181. /*
  182. out = "";
  183. for(int i =0; i< v.length(); i++ )
  184. out += Integer.toHexString( (int)v.charAt( i ) );
  185. System.out.println("char:" + out);
  186. System.out.println(v); */
  187. String char_string = characteristic.getStringValue(0);
  188. input_sb.append(char_string);
  189. ble_available.release(char_string.length());
  190. }
  191. @Override
  192. public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic char2, int status) {
  193. if (status != BluetoothGatt.GATT_SUCCESS)
  194. Log.e( TAG, "write failed");
  195. output_wait.release();
  196. }
  197. @Override
  198. public void onServicesDiscovered(BluetoothGatt gatt, int status) {
  199. if (status == BluetoothGatt.GATT_SUCCESS) {
  200. List<BluetoothGattService> gattServices = gatt.getServices();
  201. for (BluetoothGattService gattService : gattServices) {
  202. if ( gattService.getUuid().toString().startsWith( "0000ffe0" ) ) {
  203. // if ("0000ffe0-0000-1000-8000-00805f9b34fb".equals(gattService.getUuid().toString())) {
  204. //mBluetoothGattService = gattService;
  205. Log.i( TAG, "Service found");
  206. if( characteristic != null ) {
  207. Log.i( TAG, "Chararacterics already set" );
  208. gatt.setCharacteristicNotification(characteristic, false);
  209. characteristic = null;
  210. }
  211. // if( characteristic != null )
  212. // gatt.setCharacteristicNotification(characteristic, false);
  213. characteristic = gattService.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
  214. if( characteristic == null ) {
  215. Log.e( TAG, "Characteristic null : not found" );
  216. continue;
  217. }
  218. //if( characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE ) {
  219. // return ;
  220. //}
  221. gatt.setCharacteristicNotification(characteristic, true);
  222. characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
  223. if (!gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH))
  224. Log.e( TAG, "no req conn prio");
  225. // now we have working input- & output-stream's
  226. // let's run the firmware update if required
  227. Log.i( TAG, "Connected?" );
  228. //
  229. // tijdelijk uit totdat FW flashing werkt. service.connected( is, os );
  230. service.connected(is, os);
  231. } else
  232. Log.i( TAG, "more service: " + gattService.getUuid().toString());
  233. }
  234. } else {
  235. Log.i(TAG, "onServicesDiscovered received: " + status);
  236. }
  237. }
  238. };
  239. final BluetoothAdapter.LeScanCallback mLeScanCallback =
  240. new BluetoothAdapter.LeScanCallback() {
  241. @Override
  242. public void onLeScan(final BluetoothDevice device, int rssi,
  243. byte[] scanRecord) {
  244. if( connecting ) {
  245. Log.i( TAG, "meLEScanCallback : backed out connecting" );
  246. return;
  247. }
  248. if( device.getName() == null ) {
  249. Log.i( TAG, "meLEScanCallback : backed out device name null" );
  250. return;
  251. }
  252. if( !device.getName().startsWith("meCoffee") ) {
  253. Log.i( TAG, "meLEScanCallback : backed out device name not meCoffee* but " + device.getName( ) );
  254. return;
  255. }
  256. if( device == bledevice ) {
  257. Log.i(TAG, "mLeScanCallBack: backed out, already connected to this device");
  258. return ;
  259. }
  260. BluetoothAdapter mBluetoothAdapter = btmanager.getAdapter(); // repeated everywhere because unsure if this is the right call in the right context
  261. mBluetoothAdapter.cancelDiscovery();
  262. Method connectGattMethod = null;
  263. try {
  264. Log.i( TAG, "connectGatt -> " + device.getName() );
  265. // connecting = true;
  266. bledevice = device;
  267. connectGattMethod = device.getClass().getMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
  268. } catch (NoSuchMethodException e) {
  269. e.printStackTrace();
  270. }
  271. try {
  272. // https://stackoverflow.com/questions/22214254/android-ble-connect-slowly : about second boolean, was true, changed to false
  273. mBluetoothGatt = (BluetoothGatt) connectGattMethod.invoke(device, service.getApplicationContext(), false, bgc, /* TRANSPORT_LE */ 2);
  274. // mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
  275. } catch (IllegalAccessException e) {
  276. e.printStackTrace();
  277. } catch (IllegalArgumentException e) {
  278. e.printStackTrace();
  279. } catch (InvocationTargetException e) {
  280. e.printStackTrace();
  281. }
  282. }
  283. };
  284. class MyScanCallback extends ScanCallback {
  285. //Fields and constructor omitted
  286. @Override
  287. public void onScanResult(int callbackType, ScanResult result) {
  288. //Will execute on the main thread!
  289. //Do the work below on a worker thread instead!
  290. //if (showldConnect(result)) {
  291. // BluetoothDevice device = result.getDevice();
  292. // scaner.stopScan(this);
  293. // device.connectGatt(context, false, gattCallback);
  294. // }
  295. BluetoothDevice device = result.getDevice();
  296. Log.i( TAG, "myScanCallback::onScanResult: " + device.getName() );
  297. if( device != null ) {
  298. // https://stackoverflow.com/questions/33274009/how-to-prevent-bluetoothgattcallback-from-being-executed-multiple-times-at-a-tim
  299. bluetoothLeScanner.stopScan( this );
  300. //bluetoothLeScanner.
  301. // if( mBluetoothGatt == null /* || mBluetoothGatt.getDevice() != device */ )
  302. mBluetoothGatt = device.connectGatt( service.getApplicationContext(), false, bgc );
  303. // else
  304. // mBluetoothGatt.connect();
  305. }
  306. }
  307. }
  308. BluetoothAdapter mBluetoothAdapter;
  309. BluetoothLeScanner bluetoothLeScanner;
  310. //@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  311. public void setup( ) {
  312. Log.i( TAG, "BLE Started");
  313. // BLE
  314. if( mBluetoothAdapter == null )
  315. mBluetoothAdapter = btmanager.getAdapter(); // BluetoothAdapter.getDefaultAdapter();
  316. if( false ) {
  317. /* BluetoothAdapter */
  318. // mBluetoothAdapter.startDiscovery();
  319. mBluetoothAdapter.startLeScan(new UUID[]{UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb")}, mLeScanCallback); // TODO, when to stop ?
  320. }
  321. // Static device picking
  322. //byte[] address = new byte[] { (byte)0x00, (byte)0x17, (byte)0xEA, (byte)0x93, (byte)0x9F, (byte)0x75}; // V7
  323. //byte[] address = new byte[] { (byte)0x00, (byte)0x17, (byte)0xEA, (byte)0x93, (byte)0xA3, (byte)0x8B};
  324. //BluetoothDevice device = mBluetoothAdapter.getRemoteDevice( address );
  325. // https://intersog.com/blog/tech-tips/how-to-work-properly-with-bt-le-on-android/
  326. ScanSettings scanSettings = new ScanSettings.Builder()
  327. .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
  328. .build();
  329. //If name or address of peripheral is known
  330. ScanFilter scanFilter = new ScanFilter.Builder()
  331. .setServiceUuid( new ParcelUuid( UUID.fromString( "0000ffe0-0000-1000-8000-00805f9b34fb") ) )
  332. .build();
  333. // BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
  334. /* BluetoothLeScanner */
  335. if( bluetoothLeScanner == null )
  336. bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
  337. bluetoothLeScanner.startScan( Collections.singletonList(scanFilter), scanSettings, new MyScanCallback() );
  338. Log.i(TAG, "startScan()");
  339. // API ?? how did this work?
  340. // mBluetoothGatt = device.connectGatt(service, true, bgc, BluetoothDevice.TRANSPORT_LE );
  341. /*
  342. Method connectGattMethod = null;
  343. try {
  344. //connectGattMethod = device.getClass().getMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
  345. } catch (NoSuchMethodException e) {
  346. e.printStackTrace();
  347. }
  348. try {
  349. //mBluetoothGatt = (BluetoothGatt) connectGattMethod.invoke(device, service.getApplicationContext(), true, bgc, TRANSPORT_LE 2);
  350. // mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
  351. } catch (IllegalAccessException e) {
  352. e.printStackTrace();
  353. } catch (IllegalArgumentException e) {
  354. e.printStackTrace();
  355. } catch (InvocationTargetException e) {
  356. e.printStackTrace();
  357. }
  358. }
  359. */
  360. }
  361. public void discover( ) {
  362. System.out.println( "BLE discover" );
  363. setup( ); // TOD: is dit wel nodig?
  364. }
  365. public void close() {
  366. Log.i(TAG, "Close");
  367. BluetoothAdapter mBluetoothAdapter = btmanager.getAdapter(); // repeated everywhere because unsure if this is the right call in the right context
  368. mBluetoothAdapter.cancelDiscovery();
  369. if( mBluetoothGatt != null) {
  370. if( characteristic != null )
  371. mBluetoothGatt.setCharacteristicNotification(characteristic, false);
  372. mBluetoothGatt.disconnect();
  373. // mBluetoothGatt.close();
  374. }
  375. service.connected(null, null);
  376. mBluetoothAdapter.startDiscovery();
  377. }
  378. }