From 2d887e4226d60347a97518c8efafa507d7ab7bed Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Wed, 3 Dec 2014 23:39:15 +0100 Subject: o Rewritten how the BT callbacks are handled. Now with a small queue of actions. o Can successfully read bytes from a BT device. --- .../topi/fiken/display/DefaultDisplayService.java | 354 +++++++++++++++------ .../topi/fiken/display/DisplayControlActivity.java | 3 + .../java/no/topi/fiken/display/DisplayService.java | 5 + .../main/res/layout/activity_display_control.xml | 8 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 268 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java b/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java index 3bd2dd0..53b00fc 100644 --- a/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java +++ b/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java @@ -16,8 +16,10 @@ import android.os.IBinder; import android.util.Log; import android.widget.Toast; -import java.lang.reflect.Array; -import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; public class DefaultDisplayService extends Service implements DisplayService { private final Context context = DefaultDisplayService.this; @@ -27,10 +29,15 @@ public class DefaultDisplayService extends Service implements DisplayService { private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; - private BluetoothGatt gatt; - private BluetoothGattService displayService; - private BluetoothGattCharacteristic gaugeCtrl; - private BluetoothGattCharacteristic gaugeData; + + private State state; + + private static class State { + BluetoothGatt gatt; + BluetoothGattService displayService; + BluetoothGattCharacteristic gaugeCtrl; + BluetoothGattCharacteristic gaugeData; + } private Handler handler; private int UPDATE_RSSI_DELAY = 100 * 1000; @@ -38,8 +45,8 @@ public class DefaultDisplayService extends Service implements DisplayService { private Runnable updateRssi = new Runnable() { @Override public void run() { - if (gatt != null) { - gatt.readRemoteRssi(); + if (state != null && state.gatt != null) { + state.gatt.readRemoteRssi(); } handler.postDelayed(this, UPDATE_RSSI_DELAY); @@ -94,7 +101,7 @@ public class DefaultDisplayService extends Service implements DisplayService { @Override public boolean connect(final String address) { if (serviceState != ServiceState.IDLE) { - if (!(serviceState == ServiceState.CONNECTED && gatt.getDevice().getAddress().equals(address))) { + if (!(serviceState == ServiceState.CONNECTED && state.gatt.getDevice().getAddress().equals(address))) { Log.e(TAG, "connect(): Not idle: " + serviceState); return false; } @@ -103,129 +110,100 @@ public class DefaultDisplayService extends Service implements DisplayService { return true; } + state = new State(); BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); - gatt = device.connectGatt(this, false, new BluetoothGattCallback() { + BtActionExecutor executor = new BtActionExecutor(); + + executor.onSuccess(new BtCallback() { @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { -// Toast.makeText(context, "Connected", Toast.LENGTH_SHORT).show(); - if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { - boolean ok = gatt.discoverServices(); + public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { + boolean ok = gatt.discoverServices(); - if (!ok) { - disconnect(); - } else { - Intent intent = IntentAction.DEVICE_UPDATE.intent(); - intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); - intent.putExtra(IntentExtra.CONNECTED.name(), true); - sendBroadcast(intent); - } - } else { - Log.w(TAG, "Could not connect to device"); -// Toast.makeText(context, "Could not connect to device", Toast.LENGTH_SHORT).show(); + if (!ok) { + return false; } - } - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - Log.i(TAG, "onServicesDiscovered"); - - Log.i(TAG, "Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID = " + Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); + intent.putExtra(IntentExtra.CONNECTED.name(), true); + sendBroadcast(intent); - for (BluetoothGattService bluetoothGattService : gatt.getServices()) { - Log.i(TAG, "bluetoothGattService.getUuid() = " + bluetoothGattService.getUuid()); - } + return true; + } + }).onSuccess(new BtCallback() { + @Override + public boolean onServicesDiscovered(BluetoothGatt gatt) { + Log.i(TAG, "Services discovered"); - displayService = gatt.getService(Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); - gaugeCtrl = displayService.getCharacteristic(Constants.TRYGVIS_IO_GAUGE_CTRL_UUID); - gaugeData = displayService.getCharacteristic(Constants.TRYGVIS_IO_GAUGE_DATA_UUID); + state.displayService = gatt.getService(Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); + state.gaugeCtrl = state.displayService.getCharacteristic(Constants.TRYGVIS_IO_GAUGE_CTRL_UUID); + state.gaugeData = state.displayService.getCharacteristic(Constants.TRYGVIS_IO_GAUGE_DATA_UUID); - Log.i(TAG, "service=" + displayService + ", gaugeCtrl=" + gaugeCtrl + ", gaugeData=" + gaugeData); + Log.i(TAG, "service=" + state.displayService + ", gaugeCtrl=" + state.gaugeCtrl + ", gaugeData=" + state.gaugeData); Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); - intent.putExtra(IntentExtra.DEVICE_IS_DISPLAY.name(), displayService != null); + intent.putExtra(IntentExtra.DEVICE_IS_DISPLAY.name(), state.displayService != null); sendBroadcast(intent); - BluetoothGattDescriptor ccg = gaugeCtrl.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG); + BluetoothGattDescriptor ccg = state.gaugeCtrl.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG); ccg.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(ccg); - Log.i(TAG, "ccg=" + ccg); - - // Send a request for gauge count. - -// gaugeCtrl.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); -// gaugeCtrl.setValue(new byte[]{0x01}); -// gaugeCtrl.getDe -// gatt.setCharacteristicNotification(gaugeCtrl, true); -// gatt.writeCharacteristic(gaugeCtrl); -// gatt.readCharacteristic(gaugeCtrl); + gatt.setCharacteristicNotification(state.gaugeCtrl, true); + return gatt.writeDescriptor(ccg); } - + }).onSuccess(new BtCallback() { @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - Log.i(TAG, "onCharacteristicRead"); + public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + state.gaugeCtrl.setValue(new byte[]{0x01}); + gatt.writeCharacteristic(state.gaugeCtrl); - String s = ""; - for (byte b : characteristic.getValue()) { - s += Integer.toHexString(b); - } - - Log.i(TAG, "uuid=" + characteristic.getUuid() + ", value=" + s); + return true; } - + }).onSuccess(new BtCallback() { @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - Log.i(TAG, "onCharacteristicWrite"); - - String s = ""; - for (byte b : characteristic.getValue()) { - s += Integer.toHexString(b); - } - - Log.i(TAG, "uuid=" + characteristic.getUuid() + ", value=0x" + s + ", status=" + status); + public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + Log.w(TAG, "wait for it...!"); + return true; } - + }).onSuccess(new BtCallback() { @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - Log.i(TAG, "onCharacteristicChanged"); + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + byte[] value = state.gaugeCtrl.getValue(); + + Log.w(TAG, "gauge bytes: " + toHexString(value)); - String s = ""; - for (byte b : characteristic.getValue()) { - s += Integer.toHexString(b); + if (value.length != 2) { + Log.w(TAG, "Bad response, expected value.length to be 2, was " + value.length); + return false; } - Log.i(TAG, "uuid=" + characteristic.getUuid() + ", value=0x" + s); - } + int gaugeCount = (int) value[1]; - @Override - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - Log.i(TAG, "onDescriptorRead"); - } + Log.d(TAG, "Gauge count=" + gaugeCount); - @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - Log.i(TAG, "onDescriptorWrite, status=" + status + ", descriptor=" + descriptor.getUuid()); - } + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); + intent.putExtra(IntentExtra.GAUGE_COUNT.name(), gaugeCount); + sendBroadcast(intent); - @Override - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - Log.i(TAG, "onReliableWriteCompleted, status=" + status); + return true; } + }); + executor.onRemoteRssi(new BluetoothGattCallback() { @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { -// Log.i(TAG, "onReadRemoteRssi, status=" + status + ", rssi=" + rssi); - if (status == BluetoothGatt.GATT_SUCCESS) { - Intent intent = IntentAction.DEVICE_UPDATE.intent(); - intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), gatt.getDevice().getAddress()); - intent.putExtra(IntentExtra.RSSI.name(), rssi); - sendBroadcast(intent); - } + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), gatt.getDevice().getAddress()); + intent.putExtra(IntentExtra.RSSI.name(), rssi); + sendBroadcast(intent); } }); - if (gatt != null) { + state.gatt = device.connectGatt(this, false, executor.asCallback()); + + if (state.gatt != null) { serviceState = ServiceState.CONNECTED; return true; } else { @@ -233,6 +211,164 @@ public class DefaultDisplayService extends Service implements DisplayService { } } + private static class BtCallback { + public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { + throw new NotOverriddenException(); + } + + public boolean onServicesDiscovered(BluetoothGatt gatt) { + throw new NotOverriddenException(); + } + + public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + throw new NotOverriddenException(); + } + + public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + throw new NotOverriddenException(); + } + + public boolean onReliableWriteCompleted(BluetoothGatt gatt) { + throw new NotOverriddenException(); + } + + public boolean onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + throw new NotOverriddenException(); + } + } + + private static class NotOverriddenException extends RuntimeException { + } + + private class BtActionExecutor { + Queue actions = new ArrayDeque(); + List remoteRssi = new ArrayList(); + + + public BtActionExecutor onSuccess(BtCallback btCallback) { + actions.add(btCallback); + return this; + } + + public BtActionExecutor onRemoteRssi(BluetoothGattCallback callback) { + remoteRssi.add(callback); + return this; + } + + public void onEvent(String key, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState) { + if (status != BluetoothGatt.GATT_SUCCESS) { + Log.w(TAG, "Operation failed: " + key); + } + + Log.i(TAG, "Bt action completed successfully: callback=" + key); + + if (!actions.isEmpty()) { + BtCallback btCallback = actions.remove(); + Log.i(TAG, "Executing bt action"); + + boolean ok; + + try { + if (key.equals("onConnectionStateChange")) { + ok = btCallback.onConnectionStateChange(gatt, newState); + } else if (key.equals("onServicesDiscovered")) { + ok = btCallback.onServicesDiscovered(gatt); + } else if (key.equals("onCharacteristicRead")) { + ok = btCallback.onCharacteristicRead(gatt, characteristic); + } else if (key.equals("onCharacteristicWrite")) { + ok = btCallback.onCharacteristicWrite(gatt, characteristic); + } else if (key.equals("onCharacteristicChanged")) { + ok = btCallback.onCharacteristicChanged(gatt, characteristic); + } else if (key.equals("onDescriptorRead")) { + ok = btCallback.onDescriptorRead(gatt, descriptor); + } else if (key.equals("onDescriptorWrite")) { + ok = btCallback.onDescriptorWrite(gatt, descriptor); + } else if (key.equals("onReliableWriteCompleted")) { + ok = btCallback.onReliableWriteCompleted(gatt); + } else { + Log.w(TAG, "Unknown callback: " + key); + return; + } + } catch (NotOverriddenException e) { + Log.w(TAG, "Unexpected callback by listener: " + key); + disconnect(); + return; + } + + if (!ok) { + Log.w(TAG, "Error performing Bluetooth action."); + disconnect(); + } + } else { + Log.d(TAG, "All Bluetooth actions are done"); + } + } + + public BluetoothGattCallback asCallback() { + return new BluetoothGattCallback() { + + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + onEvent("onConnectionStateChange", gatt, null, null, status, newState); + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + onEvent("onServicesDiscovered", gatt, null, null, status, 9); + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + onEvent("onCharacteristicRead", gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + onEvent("onCharacteristicWrite", gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + onEvent("onCharacteristicChanged", gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); + } + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + onEvent("onDescriptorRead", gatt, null, descriptor, status, 0); + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + onEvent("onDescriptorWrite", gatt, null, descriptor, status, 0); + } + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + onEvent("onReliableWriteCompleted", gatt, null, null, status, 0); + } + + @Override + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + for (BluetoothGattCallback callback : remoteRssi) { + callback.onReadRemoteRssi(gatt, rssi, status); + } + } + }; + } + } + @Override public void disconnect() { if (serviceState != ServiceState.CONNECTED) { @@ -242,21 +378,23 @@ public class DefaultDisplayService extends Service implements DisplayService { serviceState = ServiceState.IDLE; - if (gatt != null) { + if (state.gatt != null) { try { - gatt.disconnect(); + state.gatt.disconnect(); } catch (Exception e) { Log.w(TAG, "gatt.disconnect()", e); } try { - gatt.close(); + state.gatt.close(); } catch (Exception e) { Log.w(TAG, "gatt.close()", e); } - gatt = null; + state.gatt = null; } - displayService = null; + state.displayService = null; + + state = new State(); } @Override @@ -294,4 +432,16 @@ public class DefaultDisplayService extends Service implements DisplayService { sendBroadcast(intent); } }; + + public static String toHexString(byte[] bytes) { + StringBuilder s = new StringBuilder(); + for (byte b : bytes) { + if (b < 10) { + s.append('0'); + } + s.append(Integer.toHexString(b)); + } + + return s.toString(); + } } diff --git a/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java b/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java index 1147c0c..1a537d9 100644 --- a/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java +++ b/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java @@ -28,6 +28,7 @@ public class DisplayControlActivity extends Activity { private TextView deviceNameView; private TextView deviceRssiView; + private TextView gaugeCountView; private LinearLayout gaugesLayout; @Override @@ -45,6 +46,7 @@ public class DisplayControlActivity extends Activity { deviceNameView = (TextView) findViewById(R.id.device_name); deviceRssiView = (TextView) findViewById(R.id.device_rssi); + gaugeCountView = (TextView) findViewById(R.id.gauge_count); gaugesLayout = (LinearLayout) findViewById(R.id.gauges); Button disconnectButton = (Button) findViewById(R.id.button_disconnect); @@ -61,6 +63,7 @@ public class DisplayControlActivity extends Activity { private void updateValues() { deviceNameView.setText(deviceInfo.name != null ? deviceInfo.name : getText(R.string.name_unknown)); deviceRssiView.setText(getText(R.string.rssi) + ": " + (deviceInfo.rssi != 0 ? valueOf(deviceInfo.rssi) : "")); + gaugeCountView.setText(getText(R.string.gauge_count) + ": " + (deviceInfo.gaugeCount)); } @Override diff --git a/app/src/main/java/no/topi/fiken/display/DisplayService.java b/app/src/main/java/no/topi/fiken/display/DisplayService.java index c907138..666a750 100644 --- a/app/src/main/java/no/topi/fiken/display/DisplayService.java +++ b/app/src/main/java/no/topi/fiken/display/DisplayService.java @@ -13,6 +13,7 @@ public interface DisplayService { RSSI, SCANNING, CONNECTED, + GAUGE_COUNT, } public static enum IntentAction { @@ -64,6 +65,7 @@ public interface DisplayService { class DeviceInfo { final String address; int rssi = 0; + int gaugeCount = 0; Boolean isDisplay = null; String name; @@ -97,6 +99,9 @@ public interface DisplayService { if (intent.hasExtra(IntentExtra.DEVICE_NAME.name())) { name = intent.getStringExtra(IntentExtra.DEVICE_NAME.name()); } + if (intent.hasExtra(IntentExtra.GAUGE_COUNT.name())) { + gaugeCount = intent.getIntExtra(IntentExtra.GAUGE_COUNT.name(), 0); + } } } } diff --git a/app/src/main/res/layout/activity_display_control.xml b/app/src/main/res/layout/activity_display_control.xml index 4151184..7b19429 100644 --- a/app/src/main/res/layout/activity_display_control.xml +++ b/app/src/main/res/layout/activity_display_control.xml @@ -22,12 +22,18 @@ android:textSize="12sp" android:layout_below="@+id/device_name"/> + +