OCPPlot

 avatar
unknown
css
4 years ago
23 kB
12
Indexable
using Android.App;
using Android.Widget;
using Android.OS;
using PalmSens.Core.Simplified.Android;
using SDKPlot.Android;
using PalmSens.Techniques;
using PalmSens.Devices;
using PalmSens.Core.Simplified.Data;
using PalmSens;
using System.Threading.Tasks;
using System;
using PalmSens.Comm;
using Android.Content.PM;
using Android;
using Android.Support.V4.App;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Views;
using System.Collections.Generic;

namespace PSSDKPlotExample
{
    [Activity(Label = "OCPPlot", MainLauncher = true, Icon = "@mipmap/emspico")]
    public class MainActivity : Activity
    {
        /// <summary>
        /// The psCommSimpleAndroid control that allows you to control your PalmSens/EmStat device
        /// </summary>
        private PSCommSimpleAndroid _psCommSimpleAndroid;

        /// <summary>
        /// The main view
        /// </summary>
        private View _view;

        /// <summary>
        /// List of permission ids used in the OnRequestPermissionResult
        /// </summary>
        private enum PermissionGroupIDs
        {
            BlueTooth = 0,
            ExternalStorage = 1
        }

        /// <summary>
        /// The refresh devices button
        /// </summary>
        private Button _btnRefresh;

        /// <summary>
        /// The (dis)connect button
        /// </summary>
        private Button _btnConnect;

        /// <summary>
        /// The start/abort measurement button
        /// </summary>
        private Button _btnMeasure;

        /// <summary>
        /// The potential textview
        /// </summary>
        private TextView _txtPotential;

        /// <summary>
        /// The current textview
        /// </summary>
        private TextView _txtCurrent;

        /// <summary>
        /// The status textview
        /// </summary>
        private TextView _txtStatus;

        /// <summary>
        /// The connected devices spinner 
        /// </summary>
        private Spinner _spinnerConnectedDevices;

        /// <summary>
        /// The connected devices list adapter 
        /// </summary>
        private ArrayAdapter<string> _adapterConnectedDevices;

        /// <summary>
        /// The SDKPlot android plot object
        /// </summary>
        private Plot _plot;

        /// <summary>
        /// The instance of method class containing the Cyclic Voltammetry parameters
        /// </summary>
        private Method _method;

        /// <summary>
        /// The connected PalmSens & EmStat devices
        /// </summary>
        private Device[] _connectedDevices = new Device[0];

        /// <summary>
        /// The active SimpleMeasurement
        /// </summary>
        private SimpleMeasurement _activeMeasurement = null;

        /// <summary>
        /// The active SimpleCurve
        /// </summary>
        private SimpleCurve _activeCurve = null;

        /// <summary>
        /// The active SimpleCurve
        /// </summary>
        private List<SimpleCurve> _activeCurves = null;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            _view = FindViewById(Resource.Id.mainLayout);

            //Get reference to psCommSimpleAndroid control
            _psCommSimpleAndroid = FindViewById<PSCommSimpleAndroid>(Resource.Id.pSCommSimpleAndroid);
            _psCommSimpleAndroid.ReceiveStatus += _psCommSimpleAndroid_ReceiveStatus;
            _psCommSimpleAndroid.StateChanged += _psCommSimpleAndroid_StateChanged;
            _psCommSimpleAndroid.MeasurementStarted += _psCommSimpleAndroid_MeasurementStarted;
            _psCommSimpleAndroid.MeasurementEnded += _psCommSimpleAndroid_MeasurementEnded;
            _psCommSimpleAndroid.SimpleCurveStartReceivingData += _psCommSimpleAndroid_SimpleCurveStartReceivingData;
            _psCommSimpleAndroid.Disconnected += _psCommSimpleAndroid_Disconnected;

            //Get reference to spinner control
            _spinnerConnectedDevices = FindViewById<Spinner>(Resource.Id.spinnerConnectedDevices);
            _adapterConnectedDevices = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleSpinnerDropDownItem);
            _spinnerConnectedDevices.Adapter = _adapterConnectedDevices;

            //Get references to button controls
            _btnRefresh = FindViewById<Button>(Resource.Id.btnRefresh);
            _btnRefresh.Click += _btnRefresh_Click;
            _btnConnect = FindViewById<Button>(Resource.Id.btnConnect);
            _btnConnect.Click += _btnConnect_Click;
            _btnMeasure = FindViewById<Button>(Resource.Id.btnMeasure);
            _btnMeasure.Click += _btnMeasure_Click;

            //Get references to textview controls
            _txtPotential = FindViewById<TextView>(Resource.Id.txtPotential);
            _txtCurrent = FindViewById<TextView>(Resource.Id.txtCurrent);
            _txtStatus = FindViewById<TextView>(Resource.Id.txtStatus);

            //Get reference to SDKPlot plot control
            _plot = FindViewById<Plot>(Resource.Id.plot);

            InitMethod(); //Create the open circuit potentiometry method that defines the measurement parameters
            InitPlot(); //Resets and initiates the plot control
        }

        /// <summary>
        /// Called after <c><see cref="M:Android.App.Activity.OnCreate(Android.OS.Bundle)" /></c> &amp;mdash; or after <c><see cref="M:Android.App.Activity.OnRestart" /></c> when
        /// the activity had been stopped, but is now again being displayed to the
        /// user.
        /// </summary>
        /// <remarks>
        /// <para tool="javadoc-to-mdoc">Called after <c><see cref="M:Android.App.Activity.OnCreate(Android.OS.Bundle)" /></c> &amp;mdash; or after <c><see cref="M:Android.App.Activity.OnRestart" /></c> when
        /// the activity had been stopped, but is now again being displayed to the
        /// user.  It will be followed by <c><see cref="M:Android.App.Activity.OnResume" /></c>.
        /// </para>
        /// <para tool="javadoc-to-mdoc">
        ///   <i>Derived classes must call through to the super class's
        /// implementation of this method.  If they do not, an exception will be
        /// thrown.</i>
        /// </para>
        /// <para tool="javadoc-to-mdoc">
        ///   <format type="text/html">
        ///     <a href="http://developer.android.com/reference/android/app/Activity.html#onStart()" target="_blank">[Android Documentation]</a>
        ///   </format>
        /// </para>
        /// </remarks>
        /// <since version="Added in API level 1" />
        /// <altmember cref="M:Android.App.Activity.OnCreate(Android.OS.Bundle)" />
        /// <altmember cref="M:Android.App.Activity.OnStop" />
        /// <altmember cref="M:Android.App.Activity.OnResume" />
        protected override void OnStart()
        {
            base.OnStart();
            //Set the current app context in the psCommSimpleAndroid control
            _psCommSimpleAndroid.CurrentContext = this;
        }

        /// <summary>
        /// Called when app process is resumed and after OnStart when app is started
        /// </summary>
        protected override void OnResume()
        {
            base.OnResume();

            RequestDangerousPermissions(); //Necessary as of Android version 23
        }

        /// <summary>
        /// Request permissions that require user confirmation since android 23
        /// </summary>
        private void RequestDangerousPermissions()
        {
            //Only request permissions on Android versions after 22
            if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                //Check if location permission is granted, required for BlueTooth
                if (ActivityCompat.CheckSelfPermission(this, Manifest.Permission.AccessFineLocation) != (int)Permission.Granted)
                {
                    //Request location permission
                    ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.AccessFineLocation }, (int)PermissionGroupIDs.BlueTooth);
                }
            }
        }

        /// <summary>
        /// Callback received when user permission request has been completed
        /// </summary>
        /// <param name="requestCode"></param>
        /// <param name="permissions"></param>
        /// <param name="grantResults"></param>
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            switch ((PermissionGroupIDs)requestCode)
            {
                case PermissionGroupIDs.BlueTooth:
                    if (grantResults.Length == 1 && grantResults[0] == Permission.Granted)
                    {
                        //Permission granted
                        if (!_psCommSimpleAndroid.Connected)
                            DiscoverConnectedDevices(); //Search for BlueTooth devices
                    }
                    else
                    {
                        //Permission denied
                        if (ActivityCompat.ShouldShowRequestPermissionRationale(this, Manifest.Permission.AccessFineLocation))
                        {
                            //Inform user that BlueTooth 
                            Snackbar.Make(_view, "Without granting permission to fine location BlueTooth will not work in this app.", Snackbar.LengthLong).Show();
                        }
                        else
                        {
                            //Occurs when user previously denied permission and selected never ask again
                            Snackbar.Make(_view, "Without granting permission to fine location BlueTooth will not work in this app.\nTo use BlueTooth you need to manualy grant the coarse location for this app in the android settings", Snackbar.LengthLong).Show();
                        }
                    }
                    break;
                case PermissionGroupIDs.ExternalStorage: //Not used in this example
                default:
                    base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
                    break;
            }
        }

        /// <summary>
        /// Initializes the plot control.
        /// </summary>
        private void InitPlot()
        {
            _plot.ClearAll(); //Clear all curves and data from plot
                              //Set the Axis labels
            _plot.XAxisLabel = "s";
            _plot.YAxisLabel = "V";
            _plot.AxisTextColor = OxyPlot.OxyColors.Black;
            _plot.AddData("", new double[0], new double[0]); //Add a empty data array to draw an empty plot
        }

        /// <summary>
        /// Initializes the method.
        /// </summary>
        private void InitMethod()
        {
            var ocp = new OpenCircuitPotentiometry(); //Create a new linear sweep method with the default settings
            ocp.BeginPotential = -.5f; //Sets the potential to start the sweep from
            ocp.IntervalTime = 1f; // Set the interval time to 1s
            ocp.RunTime = 1200f; // Set the run time to 1200s

            _method = ocp;
        }

        /// <summary>
        /// Discovers the connected PalmSens & EmStat devices and adds them to the spinner control.
        /// </summary>
        private async Task DiscoverConnectedDevices()
        {
            _btnRefresh.Click -= _btnRefresh_Click;
            _btnRefresh.Text = "Refreshing...";
            _btnRefresh.Enabled = false;
            _adapterConnectedDevices.Clear();
            DisplayMessage($"Searching for available devices.");

            try
            {
                _connectedDevices = await _psCommSimpleAndroid.GetConnectedDevices(10000); //Discover connected devices
            }
            catch (Exception ex)
            {
                DisplayMessage(ex.Message);
            }

            foreach (Device d in _connectedDevices)
                _adapterConnectedDevices.Add(d.ToString()); //Add connected devices to spinner control

            int nDevices = _connectedDevices.Length;

            DisplayMessage($"Found {nDevices} device(s).");

            _btnConnect.Enabled = nDevices > 0;
            _btnRefresh.Text = "Refresh";
            _btnRefresh.Enabled = true;
            _btnRefresh.Click += _btnRefresh_Click;
        }

        /// <summary>
        /// Displays a Toast message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void DisplayMessage(string message)
        {
            Toast toast = Toast.MakeText(this, message, ToastLength.Short);
            toast.Show();
        }

        /// <summary>
        /// Handles the Click event of the _btnRefresh control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private async void _btnRefresh_Click(object sender, System.EventArgs e)
        {
            await DiscoverConnectedDevices();
        }

        /// <summary>
        /// Handles the Click event of the btnConnect control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private async void _btnConnect_Click(object sender, System.EventArgs e)
        {
            _btnConnect.Enabled = false;
            if (!_psCommSimpleAndroid.Connected) //Determine whether a device is currently connected
            {
                if (_adapterConnectedDevices.Count == 0)
                    return;

                try
                {
                    //Connect to the device selected in the devices combobox control
                    await _psCommSimpleAndroid.Connect(_connectedDevices[_spinnerConnectedDevices.SelectedItemPosition]);
                    DisplayMessage($"Connected to {_psCommSimpleAndroid.ConnectedDevice.ToString()}");
                }
                catch (Exception ex)
                {
                    DisplayMessage(ex.Message);
                }
                finally
                {
                    //Update UI based on connection status
                    _spinnerConnectedDevices.Enabled = !_psCommSimpleAndroid.Connected;
                    _btnRefresh.Enabled = !_psCommSimpleAndroid.Connected;
                    _btnConnect.Text = _psCommSimpleAndroid.Connected ? "Disconnect" : "Connect";
                    _btnMeasure.Enabled = _psCommSimpleAndroid.Connected;
                }
            }
            else
            {
                await _psCommSimpleAndroid.Disconnect(); //Disconnect from the connected device
            }
            _btnConnect.Enabled = true;
        }

        /// <summary>
        /// Handles the Click event of the btnMeasure control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
        private async void _btnMeasure_Click(object sender, EventArgs e)
        {
            _btnMeasure.Enabled = false;
            if (_psCommSimpleAndroid.DeviceState == PalmSens.Comm.CommManager.DeviceState.Idle) //Determine whether the device is currently idle or measuring
            {
                try
                {
                    _activeMeasurement = await _psCommSimpleAndroid.Measure(_method); //Start measurement defined in the method

                    // Adding new curve Potential on X-Axis and Time on Y-Axis
                    _activeCurves = _activeMeasurement.NewSimpleCurve(PalmSens.Data.DataArrayType.Time, PalmSens.Data.DataArrayType.Potential, "OCP E vs t");
                    _plot.ClearAll(); // Clear the plot
                    _plot.AddSimpleCurves(_activeCurves); // Add thew new curve
                }
                catch (Exception ex)
                {
                    DisplayMessage(ex.Message);
                }
            }
            else
            {
                try
                {
                    await _psCommSimpleAndroid.AbortMeasurement(); //Abort the active measurement
                }
                catch (Exception ex)
                {
                    DisplayMessage(ex.Message);
                }
            }
            _btnMeasure.Enabled = true;
        }

        /// <summary>
        /// Raised when device status package is received (the device does not send status packages while measuring)
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="PalmSens.Comm.StatusEventArgs"/> instance containing the event data.</param>
        private void _psCommSimpleAndroid_ReceiveStatus(object sender, PalmSens.Comm.StatusEventArgs e)
        {
            Status status = e.GetStatus(); //Get the PalmSens.Comm.Status instance from the event data
            double potential = status.PotentialReading.Value; //Get the potential
            double currentInRange = status.CurrentReading.ValueInRange; //Get the current expressed inthe active current range
            CurrentRange cr = status.CurrentReading.CurrentRange; //Get the active current range

            _txtPotential.Text = $"Potential: {potential.ToString("F3")} V";
            _txtCurrent.Text = $"Current: {currentInRange.ToString("F3")} * {cr}";
        }

        /// <summary>
        /// Raised when the connected device's status changes
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="CurrentState">State of the current.</param>
        private void _psCommSimpleAndroid_StateChanged(object sender, CommManager.DeviceState CurrentState)
        {
            _txtStatus.Text = $"Status: {CurrentState.ToString()}"; //Updates the device state indicator textbox
            _btnConnect.Enabled = CurrentState == PalmSens.Comm.CommManager.DeviceState.Idle;
            _btnMeasure.Text = CurrentState == PalmSens.Comm.CommManager.DeviceState.Idle ? "Measure" : "Abort";
        }

        /// <summary>
        /// Raised when the measurement is ended
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void _psCommSimpleAndroid_MeasurementEnded(object sender, EventArgs e)
        {
            DisplayMessage("Measurement ended.");
        }

        /// <summary>
        /// Raised when the measurement is started
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void _psCommSimpleAndroid_MeasurementStarted(object sender, EventArgs e)
        {
            DisplayMessage("Measurement started.");
        }

        /// <summary>
        /// Raised when a Simple Curve in the active SimpleMeasurement starts receiving data
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="activeSimpleCurve">The active simple curve.</param>
        private void _psCommSimpleAndroid_SimpleCurveStartReceivingData(object sender, SimpleCurve activeSimpleCurve)
        {
            _activeCurve = activeSimpleCurve; //Get the reference to the active SimpleCurve
            _plot.AddSimpleCurve(_activeCurve);

            //Subscribe to the curve's events to receive updates when new data is available and when it iss finished receiving data
            _activeCurve.CurveFinished += _activeCurve_CurveFinished;

            DisplayMessage("Curve is receiving new data...");
        }

        /// <summary>
        /// Raised when a SimpleCurve stops receiving new data points
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void _activeCurve_CurveFinished(object sender, EventArgs e)
        {
            if (Looper.MyLooper() != Looper.MainLooper) //Data is parsed asynchronously in the case this event was raised on a different thread it must be invoked back onto the UI thread
            {
                using (var h = new Handler(Looper.MainLooper)) h.Post(() => _activeCurve_CurveFinished(sender, e));
                return;
            }
            int nDataPointsReceived = _activeCurve != null ? _activeCurve.NDataPoints : 0;
            DisplayMessage($"{nDataPointsReceived} data point(s) received.");

            //Unsubscribe from the curves events to avoid memory leaks
            _activeCurve.CurveFinished -= _activeCurve_CurveFinished;

            DisplayMessage("Curve Finished");
        }

        /// <summary>
        /// Raised when a device is disconnected.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="CommErrorException">The comm error exception, this only has a value when a disconnect occured due to a communication error.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void _psCommSimpleAndroid_Disconnected(object sender, Exception CommErrorException)
        {
            DisplayMessage("Disconnected");
            if (CommErrorException != null)
                DisplayMessage(CommErrorException.Message);

            //Update UI based on connection status
            _spinnerConnectedDevices.Enabled = true;
            _btnRefresh.Enabled = true;
            _btnConnect.Text = "Connect";
            _btnMeasure.Enabled = false;
        }
    }
}
Editor is loading...