main.cs
2 years ago in CSS
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 linear sweep voltammetry 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" : "Stop";
}
/// <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;
}
}
}