package com.arduinoandroid.androidarduinosensserv; import android.app.Activity; 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.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class MainScreen extends Activity implements SensorEventListener { // UUIDs for UAT service and associated characteristics. public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); // UUID for the BTLE client characteristic which is necessary for notifications. public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); //Getting the name for Log Tags private final String LOG_TAG = MainScreen.class.getSimpleName(); /** * Indicates which angle we are currently pointing the phone (and hence servo) in: * -2: 0-45 degrees * -1: 45-90 degrees * 0: 90 degrees * 1: 90-135 degrees * 2: 135-180 degrees * <p/> * Default is the neutral position, i.e. 0. */ int currentPosition = 0; long lastSensorChangedEventTimestamp = 0; //Declaring UI Elements private TextView gyroTextView; private TextView bluetoothTv; //Declaring SensorManager variables private SensorManager sensorManager; //Sensor Delay Methods int PERIOD = 1000000000; // read sensor data each second Handler handler; boolean canTransmitSensorData = false; boolean isHandlerLive = false; // Mac Address of Bluetooth LE Module //public final String MAC_BLE_MODULE = "DD:08:72:3A:DF:E4"; private boolean areServicesAccessible = false; // BTLE state private BluetoothAdapter bluetoothAdaper; private BluetoothGatt gatt; private BluetoothGattCharacteristic tx; private BluetoothGattCharacteristic rx; // Main BTLE device callback where much of the logic occurs. private BluetoothGattCallback callback = new BluetoothGattCallback() { // Called whenever the device connection state changes, i.e. from disconnected to connected. @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (newState == BluetoothGatt.STATE_CONNECTED) { writeSensorData("Connected!"); // Discover services. if (!gatt.discoverServices()) { writeSensorData("Failed to start discovering services!"); } } else if (newState == BluetoothGatt.STATE_DISCONNECTED) { writeSensorData("Disconnected!"); } else { writeSensorData("Connection state changed. New state: " + newState); } } // Called when services have been discovered on the remote device. // It seems to be necessary to wait for this discovery to occur before // manipulating any services or characteristics. public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (status == BluetoothGatt.GATT_SUCCESS) { writeSensorData("Service discovery completed!"); } else { writeSensorData("Service discovery failed with status: " + status); } // Save reference to each characteristic. tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID); rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID); // Setup notifications on RX characteristic changes (i.e. data received). // First call setCharacteristicNotification to enable notification. if (!gatt.setCharacteristicNotification(rx, true)) { writeSensorData("Couldn't set notifications for RX characteristic!"); } // Next update the RX characteristic's client descriptor to enable notifications. if (rx.getDescriptor(CLIENT_UUID) != null) { BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID); desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); if (!gatt.writeDescriptor(desc)) { writeSensorData("Couldn't write RX client descriptor value!"); } } else { writeSensorData("Couldn't get RX client descriptor!"); } areServicesAccessible = true; } // Called when a remote characteristic changes (like the RX characteristic). @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); writeSensorData(characteristic.getStringValue(0)); } }; //Bluetooth Data Output private String output; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_screen); handler = new Handler(); // Setup the refresh button final Button refreshButton = (Button) findViewById(R.id.refreshButton); refreshButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { restartScan(); } }); //get the TextView from the layout file gyroTextView = (TextView) findViewById(R.id.tv); bluetoothTv = (TextView) findViewById(R.id.btView); //get a hook to the sensor service sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); } // BTLE device scanning callback. //when this Activity starts @Override protected void onStart() { super.onResume(); /*register the sensor listener to listen to the gyroscope sensor, use the callbacks defined in this class, and gather the sensor information as quick as possible*/ sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL ); //handler.post(processSensors); // Scan for all BTLE devices. // The first one with the UART service will be chosen--see the code in the scanCallback. bluetoothAdaper = BluetoothAdapter.getDefaultAdapter(); startScan(); } //When this Activity isn't visible anymore @Override protected void onStop() { //unregister the sensor listener sensorManager.unregisterListener(this); //disconnect and close Bluetooth Connection for better reliability if (gatt != null) { gatt.disconnect(); gatt.close(); gatt = null; tx = null; rx = null; } super.onStop(); areServicesAccessible = false; } //BLUETOOTH METHODS private void startScan() { if (!bluetoothAdaper.isEnabled()) { bluetoothAdaper.enable(); } if (!bluetoothAdaper.isDiscovering()) { bluetoothAdaper.startDiscovery(); } writeSensorData("Scanning for devices..."); bluetoothAdaper.startLeScan(scanCallback); } private void stopScan() { if (bluetoothAdaper.isDiscovering()) { bluetoothAdaper.cancelDiscovery(); } writeSensorData("Stopping scan"); bluetoothAdaper.stopLeScan(scanCallback); } private void restartScan() { stopScan(); startScan(); } private BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() { // Called when a device is found. @Override public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) { Log.d(LOG_TAG, bluetoothDevice.getAddress()); writeSensorData("Found device: " + bluetoothDevice.getAddress()); // Check if the device has the UART service. if (parseUUIDs(bytes).contains(UART_UUID)) { // Found a device, stop the scan. bluetoothAdaper.stopLeScan(scanCallback); writeSensorData("Found UART service!"); // Connect to the device. // Control flow will now go to the callback functions when BTLE events occur. gatt = bluetoothDevice.connectGatt(getApplicationContext(), false, callback); } } }; // Filtering by custom UUID is broken in Android 4.3 and 4.4, see: // http://stackoverflow.com/questions/18019161/startlescan-with-128-bit-uuids-doesnt-work-on-native-android-ble-implementation?noredirect=1#comment27879874_18019161 // This is a workaround function from the SO thread to manually parse advertisement data. private List<UUID> parseUUIDs(final byte[] advertisedData) { List<UUID> uuids = new ArrayList<UUID>(); int offset = 0; while (offset < (advertisedData.length - 2)) { int len = advertisedData[offset++]; if (len == 0) { break; } int type = advertisedData[offset++]; switch (type) { case 0x02: // Partial list of 16-bit UUIDs case 0x03: // Complete list of 16-bit UUIDs while (len > 1) { int uuid16 = advertisedData[offset++]; uuid16 += (advertisedData[offset++] << 8); len -= 2; uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16))); } break; case 0x06:// Partial list of 128-bit UUIDs case 0x07:// Complete list of 128-bit UUIDs // Loop through the advertised 128-bit UUID's. while (len >= 16) { try { // Wrap the advertised bits and order them. ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN); long mostSignificantBit = buffer.getLong(); long leastSignificantBit = buffer.getLong(); uuids.add(new UUID(leastSignificantBit, mostSignificantBit)); } catch (IndexOutOfBoundsException e) { // Defensive programming. //Log.e(LOG_TAG, e.toString()); continue; } finally { // Move the offset to read the next uuid. offset += 15; len -= 16; } } break; default: offset += (len - 1); break; } } return uuids; } //SENSOR METHODS private final Runnable processSensors = new Runnable() { @Override public void run() { // Do work with the sensor values. canTransmitSensorData = !canTransmitSensorData; // The Runnable is posted to run again here: handler.postDelayed(this, PERIOD); } }; @Override public void onAccuracyChanged(Sensor arg0, int arg1) { //Do nothing. } @Override public void onSensorChanged(SensorEvent event) { if ((event.accuracy != SensorManager.SENSOR_STATUS_UNRELIABLE) && (event.timestamp - lastSensorChangedEventTimestamp > PERIOD)) { System.out.println(event.timestamp - lastSensorChangedEventTimestamp); lastSensorChangedEventTimestamp = event.timestamp; // Truncate to an integer, since precision loss is really not a serious // matter here, and it will make it much easier (and cheaper) to compare. // We will also log the integer values of [2] int xTilt = (int) event.values[2]; int yTilt = (int) event.values[1]; int zTilt = (int) event.values[0]; gyroTextView.setText("Orientation X (Roll) :" + xTilt + "\n" + "Orientation Y (Pitch) :" + yTilt + "\n" + "Orientation Z (Yaw) :" + zTilt); //Log.i(LOG_TAG, "The XTilt is:" + String.valueOf(xTilt)); if (areServicesAccessible) { turnServoFinegrained(xTilt); } } } private void turnServoCoarseGrained(int xTilt) { if ((xTilt <= 90 && xTilt > 45) && currentPosition != -2) { String setServoMessage = "/servo?params=0 /"; tx.setValue(setServoMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setServoMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } currentPosition = -2; } else if ((xTilt <= 45 && xTilt > 0) && currentPosition != -1) { // send 45 to servo String setServoMessage = "/servo?params=45 /"; tx.setValue(setServoMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setServoMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } currentPosition = -1; } else if ((xTilt == 0) && currentPosition != 0) { // send 90 to servo String setTempMessage = "/servo?params=90 /"; tx.setValue(setTempMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setTempMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } currentPosition = 0; } else if ((xTilt <= 0 && xTilt > -45) && currentPosition != 1) { // send 135 to servo String setServoMessage = "/servo?params=135 /"; tx.setValue(setServoMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setServoMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } currentPosition = 1; } else if ((xTilt <= -45 && xTilt > -90) && currentPosition != 2) { // send 180 to servo String setServoMessage = "/servo?params=180 /"; tx.setValue(setServoMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setServoMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } currentPosition = 2; } } private void turnServoFinegrained(int xTilt) { // Default to vertical position int rotationAngle = 90; // Turn left if (xTilt > 0) { rotationAngle = 90 - xTilt; } // Turn right else { rotationAngle = 90 + Math.abs(xTilt); } String setServoMessage = "/servo?params=" + rotationAngle + " /"; tx.setValue(setServoMessage.getBytes(Charset.forName("UTF-8"))); if (gatt.writeCharacteristic(tx)) { writeSensorData("Sent: " + setServoMessage); } else { writeSensorData("Couldn't write TX characteristic!"); } } private void writeSensorData(final CharSequence text) { runOnUiThread(new Runnable() { @Override public void run() { Log.e(LOG_TAG, text.toString()); //bluetoothTv = (TextView) findViewById(R.id.btView); output = text.toString(); bluetoothTv.setText(output); } }); } }