/* * Copyright (C) 2013 The Android Open Source Project * This software is based on Apache-licensed code from the above. * * Copyright (C) 2013 APUS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package pro.apus.bleconnect; import android.app.Activity; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Paint.Align; import android.net.Uri; import android.os.Bundle; import com.dropbox.client2.DropboxAPI.Entry; import android.os.Environment; import android.os.IBinder; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowManager.LayoutParams; import android.widget.ImageButton; import android.widget.LinearLayout; //import android.widget.ExpandableListView; //import android.widget.SimpleExpandableListAdapter; import android.widget.TextView; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.UUID; import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.chart.PointStyle; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; import com.dropbox.client2.DropboxAPI; import com.dropbox.client2.android.AndroidAuthSession; import com.dropbox.client2.exception.DropboxException; import com.dropbox.client2.session.AccessTokenPair; import com.dropbox.client2.session.AppKeyPair; import com.dropbox.client2.session.Session.AccessType; import com.dropbox.client2.session.TokenPair; import com.hmkcode.android.R; import com.hmkcode.android.vo.Device; import com.hmkcode.android.vo.REST; /** * For a given BLE device, this Activity provides the user interface to connect, * display data, and display GATT services and characteristics supported by the * device. The Activity communicates with {@code BluetoothLeService}, which in * turn interacts with the Bluetooth LE API. */ public class DeviceControlActivity extends Activity { private final static String TAG = DeviceControlActivity.class .getSimpleName(); // BLE stuff public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; private BluetoothLeService mBluetoothLeService; private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); private boolean mConnected = false; private BluetoothGattCharacteristic mNotifyCharacteristic; private final String LIST_NAME = "NAME"; private final String LIST_UUID = "UUID"; REST rest = new REST(); Device device = new Device(); // Database //private EventsDataSource datasource; // Dropbox private DropboxAPI<AndroidAuthSession> mDBApi; private AccessTokenPair dropboxTokens = null; final static private String APP_KEY = "tjyi4o6psg0dm0r"; final static private String APP_SECRET = "jp6054yixb9t9e0"; final static private AccessType ACCESS_TYPE = AccessType.APP_FOLDER; final static private String ACCOUNT_PREFS_NAME = "prefs"; final static private String ACCESS_KEY_NAME = "ACCESS_KEY"; final static private String ACCESS_SECRET_NAME = "ACCESS_SECRET"; private boolean uploadFileRequested = false; // Various UI stuff public static boolean currentlyVisible; private boolean logging = false; private TextView mDataField; private String mDeviceName; private String mDeviceAddress; private ImageButton mButtonStart; private ImageButton mButtonStop; private ImageButton mButtonSend; // Chart stuff private GraphicalView mChart; private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset(); private XYMultipleSeriesRenderer mRenderer = new XYMultipleSeriesRenderer(); private XYSeries mCurrentSeries; private XYSeriesRenderer mCurrentRenderer; private void initChart() { Log.i(TAG, "initChart"); if (mCurrentSeries == null) { mCurrentSeries = new XYSeries("Frequ�ncia card�aca"); mDataset.addSeries(mCurrentSeries); Log.i(TAG, "initChart mCurrentSeries == null"); } if (mCurrentRenderer == null) { mCurrentRenderer = new XYSeriesRenderer(); mCurrentRenderer.setLineWidth(4); mCurrentRenderer.setPointStyle(PointStyle.CIRCLE); mCurrentRenderer.setFillPoints(true); mCurrentRenderer.setColor(Color.GREEN); Log.i(TAG, "initChart mCurrentRenderer == null"); mRenderer.setAxisTitleTextSize(70); mRenderer.setPointSize(5); mRenderer.setYTitle("Tempo"); mRenderer.setYTitle("Frequ�ncia card�aca"); mRenderer.setPanEnabled(true); mRenderer.setLabelsTextSize(50); mRenderer.setLegendTextSize(50); mRenderer.setYAxisMin(0); mRenderer.setYAxisMax(120); mRenderer.setXAxisMin(0); mRenderer.setXAxisMax(100); mRenderer.setShowLegend(false); mRenderer.setApplyBackgroundColor(true); mRenderer.setBackgroundColor(Color.BLACK); mRenderer.setMarginsColor(Color.BLACK); mRenderer.setShowGridY(true); mRenderer.setShowGridX(true); mRenderer.setGridColor(Color.WHITE); // mRenderer.setShowCustomTextGrid(true); mRenderer.setAntialiasing(true); mRenderer.setPanEnabled(true, false); mRenderer.setZoomEnabled(true, false); mRenderer.setZoomButtonsVisible(false); mRenderer.setXLabelsColor(Color.WHITE); mRenderer.setYLabelsColor(0, Color.WHITE); mRenderer.setXLabelsAlign(Align.CENTER); mRenderer.setXLabelsPadding(10); mRenderer.setXLabelsAngle(-30.0f); mRenderer.setYLabelsAlign(Align.RIGHT); mRenderer.setPointSize(3); mRenderer.setInScroll(true); // mRenderer.setShowLegend(false); mRenderer.setMargins(new int[] { 50, 150, 10, 50 }); mRenderer.addSeriesRenderer(mCurrentRenderer); } } // Code to manage Service lifecycle. private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service) .getService(); if (!mBluetoothLeService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // Automatically connects to the device upon successful start-up // initialization. mBluetoothLeService.connect(mDeviceAddress); } @Override public void onServiceDisconnected(ComponentName componentName) { mBluetoothLeService = null; } }; // Handles various events fired by the Service. // ACTION_GATT_CONNECTED: connected to a GATT server. // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. // ACTION_DATA_AVAILABLE: received data from the device. This can be a // result of read // or notification operations. private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { mConnected = true; updateConnectionState(true); invalidateOptionsMenu(); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED .equals(action)) { mConnected = false; updateConnectionState(false); invalidateOptionsMenu(); clearUI(); } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED .equals(action)) { // Show all the supported services and characteristics on the // user interface. displayGattServices(mBluetoothLeService .getSupportedGattServices()); // mButtonStop.setVisibility(View.VISIBLE); } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { displayData(intent .getStringExtra(BluetoothLeService.EXTRA_DATA)); } } }; private void clearUI() { // mGattServicesList.setAdapter((SimpleExpandableListAdapter) null); mDataField.setText(R.string.no_data); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate"); setContentView(R.layout.heartrate); // getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); final Intent intent = getIntent(); mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME); mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS); // Set up database connection // datasource = new EventsDataSource(this); // datasource.open(); // We create a new AuthSession so that we can use the Dropbox API. AndroidAuthSession session = buildSession(); mDBApi = new DropboxAPI<AndroidAuthSession>(session); mDataField = (TextView) findViewById(R.id.data_value); mButtonSend = (ImageButton) findViewById(R.id.btnSend); mButtonSend.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // emailLog(); if (dropboxTokens == null) { mDBApi.getSession().startAuthentication( DeviceControlActivity.this); uploadFileRequested = true; } else { File file = new File(Environment .getExternalStorageDirectory().getPath() + "/hrmlog.csv"); UploadFile upload = new UploadFile( DeviceControlActivity.this, mDBApi, "/", file); upload.execute(); } } }); mButtonStart = (ImageButton) findViewById(R.id.btnStart); mButtonStart.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startLogging(); } }); mButtonStop = (ImageButton) findViewById(R.id.btnStop); mButtonStop.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { stopLogging(); } }); getActionBar().setTitle(mDeviceName); getActionBar().setDisplayHomeAsUpEnabled(true); Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); // TODO: Lars added this this.startService(gattServiceIntent); bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); LinearLayout layout = (LinearLayout) findViewById(R.id.chart); if (mChart == null) { initChart(); mChart = ChartFactory.getTimeChartView(this, mDataset, mRenderer, "hh:mm"); layout.addView(mChart); } else { mChart.repaint(); } } @Override protected void onResume() { super.onResume(); currentlyVisible = true; registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); if (mBluetoothLeService != null) { final boolean result = mBluetoothLeService.connect(mDeviceAddress); Log.d(TAG, "Connect request result=" + result); } if (mDBApi.getSession().authenticationSuccessful()) { try { // Mandatory call to complete the auth mDBApi.getSession().finishAuthentication(); // Store it locally in our app for later use dropboxTokens = mDBApi.getSession().getAccessTokenPair(); storeKeys(dropboxTokens.key, dropboxTokens.secret); if (uploadFileRequested) { dropboxUpload(); } } catch (IllegalStateException e) { // showToast("Couldn't authenticate with Dropbox:" + // e.getLocalizedMessage()); Log.i(TAG, "Error authenticating", e); } } } // this is called when the screen rotates. // (onCreate is no longer called when screen rotates due to manifest, see: // android:configChanges) @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // setContentView(R.layout.heartrate); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { Log.i(TAG, "ORIENTATION_LANDSCAPE"); } else { Log.i(TAG, "ORIENTATION_PORTRAIT"); } } @Override protected void onPause() { super.onPause(); currentlyVisible = false; // unregisterReceiver(mGattUpdateReceiver); } @Override protected void onDestroy() { super.onDestroy(); currentlyVisible = false; unbindService(mServiceConnection); // mBluetoothLeService = null; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.gatt_services, menu); if (mConnected) { menu.findItem(R.id.menu_connect).setVisible(false); menu.findItem(R.id.menu_disconnect).setVisible(true); if (logging) { menu.findItem(R.id.menu_start_logging).setVisible(false); menu.findItem(R.id.menu_stop_logging).setVisible(true); menu.findItem(R.id.menu_dropbox).setVisible(true); mButtonStart.setVisibility(View.GONE); mButtonStop.setVisibility(View.VISIBLE); } else { menu.findItem(R.id.menu_start_logging).setVisible(true); menu.findItem(R.id.menu_stop_logging).setVisible(false); mButtonStart.setVisibility(View.VISIBLE); mButtonStop.setVisibility(View.GONE); } } else { menu.findItem(R.id.menu_connect).setVisible(true); menu.findItem(R.id.menu_disconnect).setVisible(false); menu.findItem(R.id.menu_start_logging).setVisible(false); menu.findItem(R.id.menu_stop_logging).setVisible(false); menu.findItem(R.id.menu_dropbox).setVisible(false); mButtonStart.setVisibility(View.GONE); mButtonStop.setVisibility(View.GONE); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_connect: mBluetoothLeService.connect(mDeviceAddress); return true; case R.id.menu_disconnect: mBluetoothLeService.disconnect(); return true; case R.id.menu_dropbox: dropboxUpload(); return true; case R.id.menu_start_logging: startLogging(); return true; case R.id.menu_stop_logging: stopLogging(); return true; case android.R.id.home: onBackPressed(); return true; } return super.onOptionsItemSelected(item); } private void updateConnectionState(final boolean connected) { runOnUiThread(new Runnable() { @Override public void run() { if (connected) { mButtonStart.setVisibility(View.VISIBLE); mButtonStop.setVisibility(View.GONE); } else { mButtonStart.setVisibility(View.GONE); mButtonStop.setVisibility(View.GONE); } } }); } int x = 0; private void displayData(String data) { try { if (data != null) { long time = (new Date()).getTime(); int dataElement = Integer.parseInt(data); mCurrentSeries.add(time, dataElement); appendLog((new Date()).toString() + "," + data); //datasource.createEvent(1, time, dataElement); // Storing last 600 only - should average... while (mCurrentSeries.getItemCount() > 60*10) { mCurrentSeries.remove(0); } if (currentlyVisible) { mDataField.setText("Pulso: " + data); device.setDevice("Spovan"); device.setValor(String.valueOf(data)); device.setUnidade("BPS"); rest.PostService(device); mRenderer.setYAxisMin(0); mRenderer.setYAxisMax(mCurrentSeries.getMaxY() + 20); double minx = mCurrentSeries.getMinX(); double maxx = mCurrentSeries.getMaxX(); if ((maxx - minx) < 5 * 60 * 1000) { mRenderer.setXAxisMin(minx); mRenderer.setXAxisMax(minx + (5 * 60 * 1000)); } else { mRenderer.setXAxisMin(maxx - (5 * 60 * 1000)); mRenderer.setXAxisMax(maxx); } mChart.repaint(); mChart.zoomReset(); } } } catch (Exception e) { Log.e(TAG, "Exception while parsing: " + data); } } // Demonstrates how to iterate through the supported GATT // Services/Characteristics. // In this sample, we populate the data structure that is bound to the // ExpandableListView // on the UI. private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources().getString( R.string.unknown_service); String unknownCharaString = getResources().getString( R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); // Loops through available GATT Services. for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put(LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService .getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { if (UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT) .equals(gattCharacteristic.getUuid())) { Log.d(TAG, "Freq��ncia card�aca encontrada"); mNotifyCharacteristic = gattCharacteristic; } charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put(LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } } private static IntentFilter makeGattUpdateIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); intentFilter .addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); return intentFilter; } private String[] getKeys() { SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0); String key = prefs.getString(ACCESS_KEY_NAME, null); String secret = prefs.getString(ACCESS_SECRET_NAME, null); if (key != null && secret != null) { String[] ret = new String[2]; ret[0] = key; ret[1] = secret; return ret; } else { return null; } } private void storeKeys(String key, String secret) { // Save the access key for later SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0); Editor edit = prefs.edit(); edit.putString(ACCESS_KEY_NAME, key); edit.putString(ACCESS_SECRET_NAME, secret); edit.commit(); } private AndroidAuthSession buildSession() { AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET); AndroidAuthSession session; String[] stored = getKeys(); if (stored != null) { AccessTokenPair accessToken = new AccessTokenPair(stored[0], stored[1]); session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE, accessToken); } else { session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE); } return session; } private void dropboxUpload() { File file = new File(Environment.getExternalStorageDirectory() .getPath() + "/hrmlog.csv"); UploadFile upload = new UploadFile(DeviceControlActivity.this, mDBApi, "/", file); upload.execute(); uploadFileRequested = false; } public void appendLog(String text) { File logFile = new File(Environment.getExternalStorageDirectory() .getPath() + "/hrmlog.csv"); if (!logFile.exists()) { try { logFile.createNewFile(); } catch (IOException e) { Log.e(TAG, "Error while creating file. ", e); e.printStackTrace(); } } try { // BufferedWriter for performance, true to set append to file flag BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); buf.append(text); buf.newLine(); buf.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void startLogging() { mButtonSend.setVisibility(View.VISIBLE); mButtonStop.setVisibility(View.VISIBLE); mButtonStart.setVisibility(View.GONE); mBluetoothLeService.setCharacteristicNotification( mNotifyCharacteristic, true); invalidateOptionsMenu(); logging = true; } private void stopLogging() { mButtonStop.setVisibility(View.GONE); mButtonStart.setVisibility(View.VISIBLE); mBluetoothLeService.setCharacteristicNotification( mNotifyCharacteristic, false); invalidateOptionsMenu(); logging = false; } }