Measuring
Starting a measurement is done by sending method parameters to a PalmSens/EmStat device. Events are raised when a measurement has been started/ended, when a new curve/scan is started/finished, and when new data is received during a measurement.
Setting up a measurement
Add these namespaces at the top of the document.
using PalmSens.Core.Simplified.Data;
Subscribing to these events informs you on the status of a measurement and gives you references to the active SimpleCurve instances.
psCommSimple is a reference to the instance of the psCommSimple component.
psCommSimple.MeasurementStarted += PsCommSimple_MeasurementStarted; (1)
psCommSimple.MeasurementEnded += PsCommSimple_MeasurementEnded; (2)
psCommSimple.SimpleCurveStartReceivingData += PsCommSimple_SimpleCurveStartReceivingData; (3)
| 1 | Raised when a measurement begins |
| 2 | Raised when a measurement is ended |
| 3 | Raised when a new SimpleCurve instance starts receiving datapoints, returns a reference to the active SimpleCurve instance |
This line starts the measurement described in the instance of the method class.
It returns a reference to the instance of the SimpleMeasurement, in the case of a connection error or invalid method parameters it returns null.
Optionally, when using a multiplexer the channel can be specified as an integer, for example psCommSimple.Measure(method, 2).
method is a reference to an instance of the PalmSens.Method class, methods can be found in the namespace PalmSens.Tecnhniques.
SimpleMeasurement activeSimpleMeasurement = psCommSimple.Measure(method);
Receiving data
This code shows you how to obtain a reference to the instance of the active SimpleCurve currently receiving data from the SimpleCurveStartReceivingData event.
It also shows how to subscribe this SimpleCurve’s NewDataAdded and CurveFinished events and how these events can be used to retrieve the values of new data points from SimpleCurve as soon as they are available.
During a measurement the property psCommSimple.DeviceState property equals either CommManager.DeviceState.Pretreatment or CommManager.DeviceState.Measurement.
SimpleCurve _activeSimpleCurve;
private void PsCommSimple_SimpleCurveStartReceivingData(object sender, SimpleCurve activeSimpleCurve) {
_activeSimpleCurve = activeSimpleCurve;
_activeSimpleCurve.NewDataAdded += _activeSimpleCurve_NewDataAdded;
_activeSimpleCurve.CurveFinished += _activeSimpleCurve_CurveFinished;
}
private void _activeSimpleCurve_NewDataAdded(object sender, PalmSens.Data.ArrayDataAddedEventArgs e) {
int startIndex = e.StartIndex;
int count = e.Count
double[] newData = new double[count];
(sender as SimpleCurve).YAxisValues.CopyTo(newData, startIndex);
}
private void _activeCurve_Finished(object sender, EventArgs e) {
_activeSimpleCurve.NewDataAdded -= _activeSimpleCurve_NewDataAdded;
_activeSimpleCurve.Finished -= _activeSimpleCurve_Finished;
}
To retrieve x and y data from the curve.
private void OnNewDataAdded(object sender, PalmSens.Data.ArrayDataAddedEventArgs e)
{
SimpleCurve activeSimpleCurve = sender as SimpleCurve;
int startIndex = e.StartIndex;
int count = e.Count;
for (int i = startIndex; i < startIndex + count; i++)
{
double xValue = activeSimpleCurve.XAxisValue(i);
double yValue = activeSimpleCurve.YAxisValue(i);
}
}
Current ranges and status
Use the snippet below for more advanced use-cases, such as reading the current/potential ranges or status updates:
for (int i = startIndex; i < startIndex + count; i++)
{
var xValue = activeSimpleCurve.Curve.XAxisDataArray[i];
if (xValue is CurrentReading current)
{
_ = current.Value; // µA
_ = current.ValueInRange; // in Current Range
_ = current.CurrentRange;
_ = current.ReadingStatus;
_ = current.TimingStatus;
}
else if (xValue is VoltageReading voltage)
{
_ = voltage.Value; // V
_ = voltage.ValueInRange; // in potential range
_ = voltage.Range; // PotentialRange
_ = voltage.ReadingStatus;
_ = voltage.TimingStatus;
}
else
{
_ = xValue.Value;
}
}
Disconnecting and disposing the device
The com port is automatically closed when the instance of the CommManager is disconnected or disposed.
psCommSimple.Disconnect();
// or
psCommSimple.Dispose();
The psCommSimple.Disconnected event is raised when the device is disconnected.
This can be particularly useful when the device was disconnected due to a communication error, because the event also returns the exception as an argument in that case.
Communication issues
Communication issues can occur when certain commands are executed at the same time.
The problem with starting a measurement and triggering a read potential at the same time will result in the device receiving commands in an incorrect order.
These issues typically arise when a timer is used, when using multiple threads, and when invoking commands in a callback on one on the psCommSimple/psMultiCommSimple events.
When using the simplified core wrapper, communication issues are prevented as much as possible.
Using commands to control the device from your psCommSimple/psMultiCommSimple event callbacks is blocked, to prevent communication issues.
With the asynchronous methods you can control your device from one of these callbacks as the command will be delayed and run after completion of the previous command
However, as it can be run at a later point in time it is important to check whether all conditions for executing the command are still true.
This can be adjusted in the PSCommSimple.cs or PSMultiCommSimple.cs files in the PalmSens.Core.Simplified project.
When using the PalmSens.Core directly, useful aids to prevent threading issues are the comm.ClientConnection.Run and comm.ClientConnection.Run<T> methods.
These assure the commands are run on the correct context which prevents communication errors due to multiple threads communicating with the device simultaneously. When using multiple threads it is highly recommended to use these helper methods when invoking methods that communicate with the device (i.e. Measure, Current, Potential, CurrentRange and CellOn) from a different thread.
Setting a value safely:
comm.ClientConnection.Run(() => { comm.CellOn = true; }).Wait();
or when connected to a device asynchronously
await comm.ClientConnection.RunAsync(() => comm.SetCellOnAsync(true));
Getting a value safely:
Task<float> GetPotentialTask = comm.ClientConnection.Run<float>(
new Task<float>(() => { return comm.Potential; })
);
GetPotentialTask.Wait();
float potential = GetPotentialTask.Result;
Or when connected to a device asynchronously:
float potential = comm.ClientConnection.RunAsync<float>(() => comm.GetPotentialAsync());
Bluetooth
On Android Bluetooth communication is partially handled on the main/UI thread. As a result your UI can become unresponsive or you can lose data packets. When designing your app you should take this into account. For example, reduce the sampling rate of your measurement to display live results, and limit the use of the UI during the measurement to perform measurements at high sampling rates.
When data is lost during a measurement the device disconnects automatically, most of the time you can reconnect. However, in the worst case the Bluetooth module may need to be reset.
A stable connection with bluetooth is very hardware/chip dependent. In the table below is an overview of the recommended maximum and maximum throughput in points per second we observed on different android devices.
|
Debug mode
Take into account that throughput is lower when debugging your application. |
| Instrument | Recommended max. (points per second) | Max. (points per second) |
|---|---|---|
PalmSens4 |
75 [1] |
100 [1] |
Emstat3(+) Blue |
75 [1] |
100 [1] |
EmStat Go (EmStat3) |
50 [1] |
100 [1] |
EmStat Go (EmStat Pico) |
400 |
600 |
Sensit BT |
400 |
600 |
EmStat Pico |
400 |
600 |
Development board |
400 |
600 |
| Instrument | Recommended max. (points per second) | Max. (points per second) |
|---|---|---|
PalmSens4 |
100 |
150 |
Emstat3(+) Blue |
n/a |
n/a |
EmStat Go (EmStat3) |
100 |
150 |
EmStat Go (EmStat Pico) |
100 |
150 |
Sensit BT |
100 |
150 |
EmStat Pico |
100 |
150 |
Development board |
100 |
150 |