package no.topi.fiken.display; import android.app.Service; 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.BluetoothManager; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.util.Log; import android.widget.Toast; import java.util.concurrent.atomic.AtomicInteger; public class DefaultDisplayService extends Service implements DisplayService { private final static String TAG = DefaultDisplayService.class.getSimpleName(); private final Context context = DefaultDisplayService.this; private final IBinder binder = new LocalBinder(this); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private State state; private static class State { BluetoothGatt gatt; BluetoothGattService displayService; BluetoothGattCharacteristic gaugeCtrl; BluetoothGattCharacteristic gaugeData; FikenStatusPanel fikenStatusPanel = new FikenStatusPanel(); } private Handler handler; private int UPDATE_RSSI_DELAY = 100 * 1000; private Runnable updateRssi = new Runnable() { @Override public void run() { if (state != null && state.gatt != null) { state.gatt.readRemoteRssi(); } handler.postDelayed(this, UPDATE_RSSI_DELAY); } }; public static enum ServiceState { BROKEN, IDLE, SCANNING, CONNECTED, } private ServiceState serviceState = ServiceState.BROKEN; @Override public IBinder onBind(Intent intent) { return binder; } @Override public void onCreate() { handler = new Handler(); handler.postDelayed(updateRssi, UPDATE_RSSI_DELAY); } public boolean initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } Log.e(TAG, "Bluetooth initialized"); serviceState = ServiceState.IDLE; return true; } @Override public boolean connect(final String address) { if (serviceState != ServiceState.IDLE) { if (!(serviceState == ServiceState.CONNECTED && state.gatt.getDevice().getAddress().equals(address))) { Log.e(TAG, "connect(): Not idle: " + serviceState); return false; } Log.i(TAG, "connect(): already connected: " + serviceState); return true; } state = new State(); BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); final BtActionExecutor executor = new BtActionExecutor(new Runnable() { @Override public void run() { disconnect(); } }); executor.onSuccess(new BtCallback("Connect") { @Override public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { boolean ok = gatt.discoverServices(); if (!ok) { return false; } Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); intent.putExtra(IntentExtra.CONNECTED.name(), true); sendBroadcast(intent); return true; } }).onSuccess(new BtCallback("Service discovered") { @Override public boolean onServicesDiscovered(BluetoothGatt gatt) { Log.i(TAG, "Services discovered"); state.displayService = gatt.getService(Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); if (state.displayService == null) { Log.e(TAG, "Could not find display service on device."); Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); intent.putExtra(IntentExtra.DEVICE_IS_DISPLAY.name(), state.displayService != null); sendBroadcast(intent); return false; } 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=" + 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(), state.displayService != null); sendBroadcast(intent); BluetoothGattDescriptor ccg = state.gaugeCtrl.getDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG); ccg.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.setCharacteristicNotification(state.gaugeCtrl, true); return gatt.writeDescriptor(ccg); } }).onSuccess(new BtCallback("Get gauge count") { @Override public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { state.gaugeCtrl.setValue(FikenStatusPanel.getGaugeCountReq()); return gatt.writeCharacteristic(state.gaugeCtrl); } }).onSuccess(new BtCallback("data written") { @Override public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.w(TAG, "wait for it...!"); return true; } }).onSuccess(new BtCallback("Got gauge count") { @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { byte[] value = state.gaugeCtrl.getValue(); state.fikenStatusPanel.update(value); int gaugeCount = state.fikenStatusPanel.gaugeCount(); Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); intent.putExtra(IntentExtra.GAUGE_COUNT.name(), gaugeCount); sendBroadcast(intent); final AtomicInteger i = new AtomicInteger(0); executor.addFallback(updateGaugeValue); executor.onSuccess(BtActionExecutor.waitForIt); executor.onSuccess(new BtCallback("Get gauge value") { @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { updateGaugeValue.onCharacteristicChanged(gatt, characteristic); int next = i.incrementAndGet(); if (next == state.fikenStatusPanel.gaugeCount()) { return true; } executor.onSuccess(BtActionExecutor.waitForIt); executor.onSuccess(this); state.gaugeCtrl.setValue(FikenStatusPanel.getGaugeValueReq((byte) next)); return gatt.writeCharacteristic(state.gaugeCtrl); } }); state.gaugeCtrl.setValue(FikenStatusPanel.getGaugeValueReq((byte) 0)); return gatt.writeCharacteristic(state.gaugeCtrl); } }); executor.onRemoteRssi(new BluetoothGattCallback() { @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), gatt.getDevice().getAddress()); intent.putExtra(IntentExtra.RSSI.name(), rssi); sendBroadcast(intent); } }); state.gatt = device.connectGatt(this, false, executor.asCallback()); if (state.gatt != null) { serviceState = ServiceState.CONNECTED; return true; } else { return false; } } @Override public void disconnect() { if (serviceState != ServiceState.CONNECTED) { Log.d(TAG, "disconnect(): Not connected: " + serviceState); return; } serviceState = ServiceState.IDLE; if (state.gatt != null) { try { state.gatt.disconnect(); } catch (Exception e) { Log.w(TAG, "gatt.disconnect()", e); } try { state.gatt.close(); } catch (Exception e) { Log.w(TAG, "gatt.close()", e); } state.gatt = null; } state.displayService = null; state = new State(); } @Override public void startScan() { if (serviceState != ServiceState.IDLE) { Toast.makeText(context, "startScan(): Not idle", Toast.LENGTH_SHORT).show(); return; } serviceState = ServiceState.SCANNING; mBluetoothAdapter.startLeScan(leScanCallback); } @Override public void stopScan() { Log.d(TAG, "stopScan(): stopping scanning"); if (serviceState != ServiceState.SCANNING) { Log.d(TAG, "stopScan(): not scanning"); return; } mBluetoothAdapter.stopLeScan(leScanCallback); serviceState = ServiceState.IDLE; } @Override public FikenStatusPanel getStatusPanel() { return state != null ? state.fikenStatusPanel : null; } @Override public void setGauge(int gauge, int value) { state.gaugeCtrl.setValue(FikenStatusPanel.setGaugeValueReq((byte) gauge, (byte) value)); state.gatt.writeCharacteristic(state.gaugeCtrl); } private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { Log.i(TAG, "onLeScan()"); Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), device.getAddress()); intent.putExtra(IntentExtra.DEVICE_NAME.name(), device.getName()); intent.putExtra(IntentExtra.RSSI.name(), rssi); 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(); } private final BtCallback updateGaugeValue = new BtCallback("Update gauge value") { @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (state != null) { state.fikenStatusPanel.update(state.gaugeCtrl.getValue()); Intent intent = IntentAction.DEVICE_UPDATE.intent(); intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), gatt.getDevice().getAddress()); intent.putExtra(IntentExtra.GAUGE_VALUE_CHANGED.name(), 0); sendBroadcast(intent); } return true; } }; }