com.t2.androidspineexample.AndroidSpineExampleActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.t2.androidspineexample.AndroidSpineExampleActivity.java

Source

/*****************************************************************
SPINE - Signal Processing In-Node Environment is a framework that 
allows dynamic on node configuration for feature extraction and a 
OtA protocol for the management for WSN
    
Copyright (C) 2007 Telecom Italia S.p.A. 
     
GNU Lesser General Public License
     
This library is free software; you can redistribute
modify it under the terms of the sub-license (below).
    
*****************************************************************/

/*****************************************************************
BSPAN - BlueTooth Sensor Processing for Android is a framework 
that extends the SPINE framework to work on Android and the 
Android Bluetooth communication services.
    
Copyright (C) 2011 The National Center for Telehealth and 
Technology
    
Eclipse Public License 1.0 (EPL-1.0)
    
This library is free software; you can redistribute it and/or
modify it under the terms of the Eclipse Public License as
published by the Free Software Foundation, version 1.0 of the 
License.
    
The Eclipse Public License is a reciprocal license, under 
Section 3. REQUIREMENTS iv) states that source code for the 
Program is available from such Contributor, and informs licensees 
how to obtain it in a reasonable manner on or through a medium 
customarily used for software exchange.
    
Post your updates and modifications to our GitHub or email to 
t2@tee2.org.
    
This library is distributed WITHOUT ANY WARRANTY; without 
the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE.  See the Eclipse Public License 1.0 (EPL-1.0)
for more details.
     
You should have received a copy of the Eclipse Public License
along with this library; if not, 
visit http://www.opensource.org/licenses/EPL-1.0
    
*****************************************************************/

package com.t2.androidspineexample;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.Vector;

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 org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import spine.SPINEFactory;
import spine.SPINEFunctionConstants;
import spine.SPINEListener;
import spine.SPINEManager;
import spine.SPINESensorConstants;
import spine.datamodel.Address;
import spine.datamodel.Data;
import spine.datamodel.MindsetData;
import spine.datamodel.Node;
import spine.datamodel.ServiceMessage;
import spine.datamodel.ShimmerData;
import spine.datamodel.functions.ShimmerNonSpineSetupSensor;

import com.t2.Constants;
import com.t2.R;
import com.t2.SpineReceiver;
import com.t2.SpineReceiver.BioFeedbackStatus;
import com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener;
import com.t2.biofeedback.device.shimmer.ShimmerDevice;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

/**
 * This is a trivial example activity to show how to connect an Android application to the Spine server
 *   Note that only the NeuroSky Mindset and Shimmer devices are implemented in this example
 *   
 * @author scott.coleman
 *
 */
public class AndroidSpineExampleActivity extends Activity
        implements OnBioFeedbackMessageRecievedListener, SPINEListener {

    private static final String TAG = "BFDemo";

    // ****** THESE MUST BE CHANGED TO CORRESPOND TO YOUR SHIMMER DEVICES! ******
    // This mapping must be set so we can correspond specific Shimmer devices to specific parameters
    //   private static final String GSR_SENSOR_NAME = "RN42-A6A1";
    private static final String GSR_SENSOR_NAME = "RN42-B819";
    private static final String ECG_SENSOR_NAME = "RN42-A774";
    private static final String EMG_SENSOR_NAME = "RN42-1111";

    // Charting stuff
    private final static int SPINE_CHART_SIZE = 20;
    private int mSpineChartX = 0;
    public XYSeries mSeries;

    /**
     * Static instance of this activity
     */
    private static AndroidSpineExampleActivity mInstance;

    /**
     * The Spine manager contains the bulk of the Spine server. 
     */
    private static SPINEManager mSpineManager;

    /**
    * This is a broadcast receiver. Note that this is used ONLY for command/status messages from the AndroidBTService
    * All data from the service goes through the mail SPINE mechanism (received(Data data)).
    */
    private SpineReceiver mCommandReceiver;

    /**
     * Mindset data - storage for incoming mindset data
     */
    private MindsetData mCurrentMindsetData;

    /**
     * Text view to display incoming attention data
     */
    private TextView mTextViewDataMindset;

    /**
     * Text view to display incoming attention Shimmmer ecg data
     */
    private TextView mTextViewDataShimmerEcg;

    /**
     * Text view to display incoming attention Shimmer gsr data
     */
    private TextView mTextViewDataShimmerGsr;

    /**
     * Text view to display incoming attention Shimmer emg data
     */
    private TextView mTextViewDataShimmerEmg;

    /**
     * Node object for shimmer device as returned by spine
     */
    public Node mShimmerNode = null;

    /**
     * Node object for mindset device as returned by spine
     */
    public Node mMindsetNode = null;

    /**
     * List of all currently PAIRED BioSensors
     */
    private ArrayList<BioSensor> mBioSensors = new ArrayList<BioSensor>();

    /**
     * Whether or not the device has bluetooth enabled
     */
    private Boolean mBluetoothEnabled = false;

    /**
     * Class to help in saving received data to file
     */
    private LogWriter mLogWriter;

    /**
     * The configured GSR resistance range
     */
    private int mConfiguredGSRRange = ShimmerDevice.GSR_RANGE_HW_RES_3M3;

    /**
     * Chart view for parameter
     */
    private GraphicalView mDeviceChartView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d(TAG, this.getClass().getSimpleName() + ".onCreate()");
        mInstance = this;
        setContentView(R.layout.main);

        // Set up member variables to UI Elements
        mTextViewDataMindset = (TextView) findViewById(R.id.textViewData);
        mTextViewDataShimmerEcg = (TextView) findViewById(R.id.textViewDataShimmerEcg);
        mTextViewDataShimmerGsr = (TextView) findViewById(R.id.textViewDataShimmerGsr);
        mTextViewDataShimmerEmg = (TextView) findViewById(R.id.textViewDataShimmerEmg);

        // ----------------------------------------------------
        // Initialize SPINE by passing the fileName with the configuration properties
        // ----------------------------------------------------
        Resources resources = this.getResources();
        AssetManager assetManager = resources.getAssets();
        try {
            mSpineManager = SPINEFactory.createSPINEManager("SPINETestApp.properties", resources);
        } catch (InstantiationException e) {
            Log.e(TAG, "Exception creating SPINE manager: " + e.toString());
            Log.e(TAG,
                    "Check to see that valid defaults.properties, and SpineTestApp.properties files exist in the Assets folder!");
            e.printStackTrace();
        }

        // ... then we need to register a SPINEListener implementation to the SPINE manager instance
        // to receive sensor node data from the Spine server
        // (I register myself since I'm a SPINEListener implementation!)
        mSpineManager.addListener(this);

        // Create a broadcast receiver. Note that this is used ONLY for command messages from the service
        // All data from the service goes through the mail SPINE mechanism (received(Data data)).
        // See public void received(Data data)
        this.mCommandReceiver = new SpineReceiver(this);

        // Set up filter intents so we can receive broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.t2.biofeedback.service.status.BROADCAST");
        this.registerReceiver(this.mCommandReceiver, filter);

        try {
            mCurrentMindsetData = new MindsetData(this);
        } catch (Exception e1) {
            Log.e(TAG, "Exception creating MindsetData: " + e1.toString());
        }

        // Since Mindset is a static node we have to manually put it in the active node list
        // Note that the sensor id 0xfff1 (-15) is a reserved id for this particular sensor
        Node MindsetNode = null;
        MindsetNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_MINDSET));
        mSpineManager.getActiveNodes().add(MindsetNode);

        // Since Shimmer is a static node we have to manually put it in the active node list
        mShimmerNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_SHIMMER));
        mSpineManager.getActiveNodes().add(mShimmerNode);

        // Set up graph(s)
        mSeries = new XYSeries("parameter");
        generateChart();

    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, this.getClass().getSimpleName() + ".onStart()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, this.getClass().getSimpleName() + ".onResume()");

        mLogWriter = new LogWriter(this);

        // Create a log file name from the date/time
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);
        String currentDateTimeString = sdf.format(new Date());
        String logFileName = "_" + currentDateTimeString + ".log";
        mLogWriter.open(logFileName);

        mSpineManager.discoveryWsn();
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(TAG, this.getClass().getSimpleName() + ".onPause()");
        StopBioSensors();
        mLogWriter.close();
    }

    @Override
    protected void onStop() {
        Log.d(TAG, this.getClass().getSimpleName() + ".onStop()");

        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, this.getClass().getSimpleName() + ".onDestroy()");

        mSpineManager.removeListener(this);
        this.unregisterReceiver(this.mCommandReceiver);
    }

    @Override
    public void newNodeDiscovered(Node newNode) {
    }

    @Override
    public void received(ServiceMessage msg) {
        Log.i(TAG, "received(ServiceMessage msg)");
    }

    /**
     * This is where we receive sensor data that comes through the Spine channel. 
     * @param data      Generic Spine data packet. Should be cast to specified data type indicated by data.getFunctionCode()
     *
     * @see spine.SPINEListener#received(spine.datamodel.Data)
     */
    @Override
    public void received(Data data) {
        if (data != null) {
            switch (data.getFunctionCode()) {

            case SPINEFunctionConstants.MINDSET: {
                Node source = data.getNode();
                MindsetData mindsetData = (MindsetData) data;

                // We'll get here once every time a main update occurs from the mindset
                // i.e. once a second.
                // The main update has the spectral data
                if (mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM) {
                    mCurrentMindsetData.updateSpectral(mindsetData);
                    mCurrentMindsetData.updateRawWave(mindsetData);

                    Log.i(TAG, ", " + mCurrentMindsetData.getLogDataLine());
                    int delta = mCurrentMindsetData.getFeatureValue(MindsetData.DELTA_ID);
                    mTextViewDataMindset.setText("Mindset: Delta = " + delta);

                    mLogWriter.write("Mindset: Delta = " + delta);
                }

                if (mindsetData.exeCode == Constants.EXECODE_POOR_SIG_QUALITY) {
                    mCurrentMindsetData.poorSignalStrength = mindsetData.poorSignalStrength;
                }

                if (mindsetData.exeCode == Constants.EXECODE_ATTENTION) {
                }

                if (mindsetData.exeCode == Constants.EXECODE_MEDITATION) {
                    mCurrentMindsetData.meditation = mindsetData.meditation;
                }

                break;
            } // End case SPINEFunctionConstants.MINDSET:

            case SPINEFunctionConstants.SHIMMER: {
                Node source = data.getNode();
                ShimmerData shimmerData = (ShimmerData) data;

                switch (shimmerData.sensorCode) {
                case SPINESensorConstants.SHIMMER_GSR_SENSOR:
                    int resistance = Util.GsrResistance(shimmerData.gsr, shimmerData.gsrRange, mConfiguredGSRRange);
                    String verboseLogLine = String.format("sensor:%-5d gsrADC= %-5d, range= %d, resistance= %d",
                            shimmerData.sensorCode, shimmerData.gsr, shimmerData.gsrRange, resistance);
                    Log.d(TAG, verboseLogLine);

                    mTextViewDataShimmerGsr.setText(verboseLogLine);
                    mLogWriter.write("Shimmer: GSR = " + resistance);

                    mSeries.add(mSpineChartX, resistance);
                    //                     mSeries.add(mSpineChartX, shimmerData.gsr);   
                    //                     series.add(mSpineChartX, 50);   
                    if (mSeries.getItemCount() > SPINE_CHART_SIZE) {
                        mSeries.remove(0);
                    }
                    mSpineChartX++;

                    if (mDeviceChartView != null) {
                        mDeviceChartView.repaint();
                    }

                    break;

                case SPINESensorConstants.SHIMMER_EMG_SENSOR:
                    verboseLogLine = String.format("sensor:%-5d emgADC= %04x", shimmerData.sensorCode,
                            shimmerData.emg);
                    Log.d(TAG, verboseLogLine);

                    mTextViewDataShimmerEmg.setText(verboseLogLine);
                    mLogWriter.write("Shimmer: EMG = " + shimmerData.emg);

                    break;

                case SPINESensorConstants.SHIMMER_ECG_SENSOR:
                    int ecgLa_Ra = shimmerData.ecgLaLL - shimmerData.ecgRaLL;
                    verboseLogLine = String.format("sensor:%-5d ECGLaLL= %5d ,ECGRaLL= %5d ,ECGLaRa= %5d",
                            shimmerData.sensorCode, shimmerData.ecgLaLL, shimmerData.ecgRaLL, ecgLa_Ra);
                    Log.d(TAG, verboseLogLine);
                    mTextViewDataShimmerEcg.setText("ECG = " + ecgLa_Ra);
                    mLogWriter.write("Shimmer: ECG = " + ecgLa_Ra);

                    break;
                }
            } // End case SPINEFunctionConstants.SHIMMER:
            }
        }
    }

    /* (non-Javadoc)
     * This is called by the Spine service a specified number of seconds after the discovery command.
     * The idea is that Spine should have found all of it's sensors by now.
     * The only thing we do here is to tell the spind manager to give us a list
     * of connected devices and their status.
     * 
     * @see spine.SPINEListener#discoveryCompleted(java.util.Vector)
     */
    @Override
    public void discoveryCompleted(Vector activeNodes) {
        // Tell the bluetooth service to send us a list of bluetooth devices and system status
        // Response comes in public void onStatusReceived(BioFeedbackStatus bfs) STATUS_PAIRED_DEVICES
        mSpineManager.pollBluetoothDevices();
    }

    @Override
    public void onStatusReceived(BioFeedbackStatus bfs) {
        Log.d(TAG, this.getClass().getSimpleName() + ".onStatusReceived(" + bfs.messageId + ")");

        String name = bfs.name;
        if (name == null)
            name = "sensor node";

        if (bfs.messageId.equals("CONN_CONNECTING")) {
            Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name);
            Toast.makeText(getApplicationContext(), "Connecting to " + name, Toast.LENGTH_SHORT).show();
        } else if (bfs.messageId.equals("CONN_ANY_CONNECTED")) {
            Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name);
            Toast.makeText(getApplicationContext(), name + " Connected", Toast.LENGTH_SHORT).show();

            startBioSensors();
        } else if (bfs.messageId.equals("CONN_CONNECTION_LOST")) {
            Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name);
            Toast.makeText(getApplicationContext(), name + " Connection lost ****", Toast.LENGTH_SHORT).show();
        } else if (bfs.messageId.equals("STATUS_PAIRED_DEVICES")) {
            // We get here in response to mSpineManager.pollBluetoothDevices() from above
            // bfs.address contains a json arrary containing status of all BT Devices
            Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name);
            Log.i(TAG, this.getClass().getSimpleName() + bfs.address);

            populateBioSensors(bfs.address);
            validateBioSensors();
            startBioSensors();
        }
    }

    /**
     * Receives a json string containing data about all of the paired sensors
     * the adds a new BioSensor for each one to the mBioSensors collection
     * 
     * @param jsonString String containing info on all paired devices
     */
    private void populateBioSensors(String jsonString) {

        Log.d(TAG, this.getClass().getSimpleName() + " populateBioSensors");
        // Now clear it out and populate it. The only difference is that
        // if a sensor previously existed, then 
        mBioSensors.clear();
        try {
            JSONArray jsonArray = new JSONArray(jsonString);
            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                Boolean enabled = jsonObject.getBoolean("enabled");
                String name = jsonObject.getString("name");
                String address = jsonObject.getString("address");
                int connectionStatus = jsonObject.getInt("connectionStatus");

                if (name.equalsIgnoreCase("system")) {
                    mBluetoothEnabled = enabled;
                } else {

                    Log.d(TAG, "Adding sensor " + name + ", " + address + (enabled ? ", enabled" : ", disabled")
                            + " : " + Util.connectionStatusToString(connectionStatus));
                    BioSensor bioSensor = new BioSensor(name, address, enabled);
                    bioSensor.mConnectionStatus = connectionStatus;
                    mBioSensors.add(bioSensor);
                }
            }
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * Validates sensors, makes sure that bluetooth is on and each sensor has a parameter associated with it
     */
    void validateBioSensors() {

        // First make sure that bluetooth is enabled
        if (!mBluetoothEnabled) {
            AlertDialog.Builder alert1 = new AlertDialog.Builder(this);

            alert1.setMessage("Bluetooth is not enabled on your device.");

            alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                }
            });
            alert1.show();
        }
        String badSensorName = null;
    }

    /**
     * Sends startup command to every shimmer device
     */
    void startBioSensors() {
        for (BioSensor sensor : mBioSensors) {
            Log.d(TAG, "Sending Start command to sensor " + sensor.mBTName + " (" + sensor.mBTAddress + ")");

            ShimmerNonSpineSetupSensor shimmerSetupCommand = new ShimmerNonSpineSetupSensor();

            if (sensor.mBTName.equalsIgnoreCase(GSR_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_GSR_SENSOR);
            } else if (sensor.mBTName.equalsIgnoreCase(ECG_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_ECG_SENSOR);
            } else if (sensor.mBTName.equalsIgnoreCase(EMG_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_EMG_SENSOR);
            }

            shimmerSetupCommand.setBtAddress(Util.AsciiBTAddressToBytes(sensor.mBTAddress));

            shimmerSetupCommand.setCommand(ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE);
            mConfiguredGSRRange = Util.getGsrRangeFromShimmerCommand(
                    ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE);
            mSpineManager.setup(mShimmerNode, shimmerSetupCommand);
        }
    }

    /**
     * Sends stop command to every shimmer device
     */
    void StopBioSensors() {
        for (BioSensor sensor : mBioSensors) {
            Log.d(TAG, "Sending Stop command to sensor " + sensor.mBTName + " (" + sensor.mBTAddress + ")");
            ShimmerNonSpineSetupSensor shimmerSetupCommand = new ShimmerNonSpineSetupSensor();

            if (sensor.mBTName.equalsIgnoreCase(GSR_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_GSR_SENSOR);
            } else if (sensor.mBTName.equalsIgnoreCase(ECG_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_ECG_SENSOR);
            } else if (sensor.mBTName.equalsIgnoreCase(EMG_SENSOR_NAME)) {
                shimmerSetupCommand.setSensor(SPINESensorConstants.SHIMMER_EMG_SENSOR);
            }

            shimmerSetupCommand.setBtAddress(Util.AsciiBTAddressToBytes(sensor.mBTAddress));

            shimmerSetupCommand.setCommand(ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_STOPPED);
            mSpineManager.setup(mShimmerNode, shimmerSetupCommand);
        }
    }

    private void generateChart() {
        // Set up chart
        XYMultipleSeriesDataset deviceDataset = new XYMultipleSeriesDataset();
        XYMultipleSeriesRenderer deviceRenderer = new XYMultipleSeriesRenderer();

        LinearLayout layout = (LinearLayout) findViewById(R.id.deviceChart);
        if (mDeviceChartView != null) {
            layout.removeView(mDeviceChartView);
        }
        if (true) {
            mDeviceChartView = ChartFactory.getLineChartView(this, deviceDataset, deviceRenderer);
            mDeviceChartView.setBackgroundColor(Color.BLACK);
            layout.addView(mDeviceChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        }

        deviceRenderer.setShowLabels(false);
        deviceRenderer.setMargins(new int[] { 0, 5, 5, 0 });
        deviceRenderer.setShowAxes(true);
        deviceRenderer.setShowLegend(false);

        deviceRenderer.setZoomEnabled(false, false);
        deviceRenderer.setPanEnabled(false, false);
        deviceRenderer.setYAxisMin(0);
        //        deviceRenderer.setYAxisMax(1000);

        // This is set rather arbitrarily based on observed resistance values in the log
        deviceRenderer.setYAxisMax(8000000);

        deviceDataset.addSeries(mSeries);

        XYSeriesRenderer seriesRenderer = new XYSeriesRenderer();
        seriesRenderer.setColor(Color.WHITE);
        seriesRenderer.setPointStyle(PointStyle.CIRCLE);

        deviceRenderer.addSeriesRenderer(seriesRenderer);
    }

}