Real time Sensor Data Analysis on PSLab Android

PSLab device has the capacity to connect plug and play sensors through the I2C bus. The sensors are capable of providing data in real time. So, the PSLab Android App and the Desktop app need to have the feature to fetch real time sensor values and display the same in the user interface along with plotting the values on a simple graph.

The UI was made following the guidelines of Google’s Material Design and incorporating some ideas from the Science Journal app. Cards are used for making each section of the UI. There are segregated sections for real time updates and plotting where the real time data can be visualised. A methods for fetching the data are run continuously in the background which receive the data from the sensor and then update the screen.

The following section denotes a small portion of the UI responsible for displaying the data on the screen continuously and are quite simple enough. There are a number of TextViews which are being constantly updated on the screen. Their number depends on the type and volume of data sent by the sensor.

<TextView
       android:layout_width="wrap_content"
       android:layout_height="30dp"
       android:layout_gravity="start"
       android:text="@string/ax"
       android:textAlignment="textStart"
       android:textColor="@color/black"
       android:textSize="@dimen/textsize_edittext"
       android:textStyle="bold" />

<TextView
       android:id="@+id/tv_sensor_mpu6050_ax"
       android:layout_width="wrap_content"
       android:layout_height="30dp"
       android:layout_gravity="start"
       android:textAlignment="textStart"
       android:textColor="@color/black"
       android:textSize="@dimen/textsize_edittext"
       android:textStyle="bold" />

 

The section here represents the portion of the UI responsible for displaying the graph. Like all other parts of the UI of PSLab Android, MPAndroidChart is being used here for plotting the graph.

<LinearLayout
       android:layout_width="match_parent"
       android:layout_height="160dp"
       android:layout_marginTop="40dp">

       <com.github.mikephil.charting.charts.LineChart
               android:id="@+id/chart_sensor_mpu6050"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#000" />
</LinearLayout>

 

Since the updates needs to continuous, a process should be continuously run for updating the display of the data and the graph. There are a variety of options available in Android in this regard like using a Timer on the UI thread and keep updating the data continuously, using ASyncTask to run a process in the background etc.

The issue with the former is that since all the processes i.e. fetching the data and updating the textviews & graph will run on the UI thread, the UI will become laggy. So, the developer team chose to use ASyncTask and make all the processes run in the background so that the UI thread functions smoothly.

A new class SensorDataFetch which extends AsyncTask is defined and its object is created in a runnable and the use of runnable ensures that the thread is run continuously till the time the fragment is used by the user.

scienceLab = ScienceLabCommon.scienceLab;
i2c = scienceLab.i2c;
try {
    MPU6050 = new MPU6050(i2c);
} catch (IOException e) {
    e.printStackTrace();
}
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        while (true) {
            if (scienceLab.isConnected()) {
                try {
                    sensorDataFetch = new SensorDataFetch();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                sensorDataFetch.execute();
            }
        }
    }
};
new Thread(runnable).start();

 

The following is the code for the ASyncTask created. There are two methods defined here doInBackground and onPostExecute which are responsible for fetching the data and updating the display respectively.

The raw data is fetched using the getRaw method of the MPU6050 object and stored in an ArrayList. The data type responsible for storing the data will depend on the return type of the getRaw method of each sensor class and might be different for other sensors. The data returned by getRaw is semi-processed and the data just needs to be split in sections before presenting it for display.

The PSLab Android app’s sensor files can be viewed here and they can give a better idea about how the sensors are calibrated, how the intrinsic nonlinearity is taken care of, how the communication actually works etc.

After the data is stored, the control moves to the onPostExecute method, here the textviews on the display and the chart are updated. The updation is slowed down a bit so that the user can visualize the data received.

private class SensorDataFetch extends AsyncTask<Void, Void, Void> {
   MPU6050 MPU6050 = new MPU6050(i2c);
   ArrayList<Double> dataMPU6050 = new ArrayList<Double>();

   private SensorDataFetch(MPU6050 MPU6050) throws IOException {
   }

   @Override
   protected Void doInBackground(Void... params) {
       try {
           if (MPU6050 != null) {
               dataMPU6050 = MPU6050.getRaw();
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
           return null;
   }

   protected void onPostExecute(Void aVoid) {
       super.onPostExecute(aVoid);
       tvSensorMPU6050ax.setText(String.valueOf(dataMPU6050.get(0)));
       tvSensorMPU6050ay.setText(String.valueOf(dataMPU6050.get(1)));
       tvSensorMPU6050az.setText(String.valueOf(dataMPU6050.get(2)));
       tvSensorMPU6050gx.setText(String.valueOf(dataMPU6050.get(3)));
       tvSensorMPU6050gy.setText(String.valueOf(dataMPU6050.get(4)));
       tvSensorMPU6050gz.setText(String.valueOf(dataMPU6050.get(5)));
       tvSensorMPU6050temp.setText(String.valueOf(dataMPU6050.get(6)));
   }
}

The detailed implementation of the same can be found here.

Additional Resources

  1. Learn more about how real time sensor data analysis can be used in various fields like IOT http://ieeexplore.ieee.org/document/7248401/
  2. Google Fit guide on how to use native built-in sensors on phones, smart watches etc. https://developers.google.com/fit/android/sensors
  3. A simple starter guide to build an app capable of real time sensor data analysis http://developer.telerik.com/products/building-an-android-app-that-displays-live-accelerometer-data/
  4. Learn more about using AsyncTask https://developer.android.com/reference/android/os/AsyncTask.html

Analyzing Sensor Data on PSLab

PSLab Android App and Desktop app have the functionality of reading data from the sensors. The raw sensor data received is in the form of a long string and needs to parsed to understand what the data actually conveys.

The sensor data is unique in terms of volume of data sent, the units of measurement of the data etc., however none of this is reflected in the raw data. The blog describes how the sensor data received by the Android/Desktop app is parsed, interpreted and finally presented to the user for viewing.

The image below displays the raw data sent by the sensors

blog_post_9_2

Fig: Raw Sensor data displayed below the Get Raw button

  • In order to understand the data sent from the sensor, we need to understand what the sensor does.
    • For example, HMC5883L is a 3-axis magnetometer and it returns the value of the magnetic field in the x, y & z axes in the order of nanoTeslas.
    • Similarly, the DAC of PSLab – MCP4728 can also be used like other sensors, it returns the values of channels in millivolts.
    • The sensor MPU6050 being 3-axes accelerometer & gyroscope which returns the values of acceleration & angular momentum of the x, y & z axes in their SI units respectively.
  • Each sensor has a sensitivity value. The sensitivity of the sensor can be modified to adjust the accuracy of the data received. For PSLab, the data returned is a float number with each data point having 4 bytes of memory with the highest sensitivity. Although sensitivity is not a reliable indicator of the accuracy of the data. Each value received has a lot of trailing values after the decimal and it is evident that no sensor can possibly achieve accuracy that high, so the data after 2-3 decimal places is garbage and not taken into consideration.
  • Some sensors are configurable up to a great extent like MPU6050 where limits can also be set on the range of data, volume of data sent etc. whereas some are not configurable and are just meant for sending the data at regular intervals.
  • In order to parse the above data, if the sensor returns a single value, then the data is ready to be used. However, in most cases like above where the sensors return multiple values, the data stream can be divided into equal parts since each value occupies equal space and each value can be stored in different variables.
  • The stored data has to be presented to the user in a better understandable format where it is clear that what each value represents. For example, in case of the 3 axes sensors, the data of each axis must be distinctly represented to the user.

Shown below are the mock-ups of the sensor UIs in which each value has been distinctly represented.

         

Fig: Mock-ups for the sensor UIs (a) – HMC5883L (b) – MPU6050

Each UI has a card to display those values. These values are updated in real time and there are additional options to plot the data received in real time and in some cases also configure the sensor. In addition to that there are features for data logging where the data is recorded for a given time interval specified by the user and on completion of recording, calculations like the mean, standard deviation etc. are presented to the user.

Additional Resources

  1. Analyzing sensor data using Arduino, similar to method for PSLab – http://tronixstuff.com/2014/01/21/online-data-analysis-arduino-plotly/
  2. YouTube video to understand analysis of data from MPU6050 in Arduino – https://www.youtube.com/watch?v=taZHl4Mr-Pk

Creating Multiple Device Compatible Layouts in PSLab Android

The developer’s goal is that PSLab Android App as an app should run smoothly on all the variety of Android devices out in the market. There are two aspects of it – the app should be able to support maximum number of Android versions possible which is related to the core software part and the other being the app should be able to generate the same user experience on all sizes of screens. This post focuses on the later.

There are a whole range of android devices available in the market right from 4 inch mobile phones to 12 inch tablets and the range in the screen sizes is quite large. So, the challenge in front of app designers is to make the app compatible with the maximum  number of devices without doing any specific tweaks related to a particular resolution range. Android has its mechanism of scaling the app as per the screen size and it does a good job almost all the time, however, still there are cases where android fails to scale up or scale down the app leading to distorted layout of the app.

This blog discusses some of the tricks that needs to be kept in mind while designing layouts that work independent of screen sizes.

Avoid using absolute dimensions

It is one of the most common things to keep in mind before starting any UI design. Use of absolute dimensions like px, inch etc. must be avoided every time as they are fixed in size and don’t scale up or scale down while screen sizes are changed. Instead relative dimensions like dp should be used which depend on the resolution and scale up or scale down. ( It’s a fair assumption that bigger screens will have better resolution compared to the smaller ones although exceptions do exist) .

Ensure the use of correct layout/View group

Since, android provides a variety of layouts like Linearlayout, Constrainedlayout, Relativelayout, Tablelayout and view groups like ScrollView, RecyclerView, ListView etc. it is often confusing to know which layout/viewgroup should be used. The following list gives a rough idea of when to use a particular layout or view group.

  • Linearlayout – Mostly used for simple designs when the elements are stacked in ordered horizontal/vertical fashion and it needs explicit declaration of orientation.
  • Relativelayout – Mostly used when the elements need to defined relative to the parent or the neighbouring elements. Since, the elements are relative, there is no need to define the orientation.
  • Constraintlayout – It has all the features of Relativelayout and in addition a feature of adding constraints to the child elements or neighbouring elements.
  • Tablelayout – Tablelayout is helpful to when all the views/widgets are arranged in an ordered fashion.

All the above layouts can be used interchangeably most of the times, however, certain cases make some more favourable than others like when than views/ widgets are not present in an organised manner, it is better to stick to Linearlayout or Relativelayout.

  • ListView – Used when the views/ widgets in a screen are repeated, so using a listview ensures that the volume of the code is reduced and all the repetitive views are identical in nature.
  • RecyclerView – More of an improved version of ListView. It is recommended to use this view over ListView. Additionally this view group supports features like swipe to refresh.
  • ScrollView – Used when the UI screen cannot fit within the given screen space. ScrollView supports one direct child layout. So, to implement a scrollview, all the views must be under a particular layout and then masked by scrollview.

Choosing the correct layout or view group would help to create a better UI.

Use of layout_weight

Ensuring the layout width assigned in XML file covers the entire width on the screen. For ensuring this, one possible solution is to use layout_weight instead of layout_width.

Example –

<TextView
   android:id="@+id/tv_control_read9"
   android:layout_width="0dp"
   android:layout_weight="1"
   android:layout_height="30dp"
   android:layout_marginTop="10dp"
/>

 

In order to use layout_weight, layout_width must be set to 0 else it would interfere with the width and as layout_width is a compulsory parameter it cannot be omitted. Layout weight can be any number and the space is allocated to each view in proportion to the weights assigned. Since it does not involve numerical dimensions, the distribution would be uniform for all types of screens. The result is clearly evident here. The same UI in different screen sizes is displayed below.

blog_post_8_2

Fig: Screenshot taken on a 6” phone and on a 4” phone. Although the screen area of 4” phone is 44% that of the 6” phone, the UIs are identically the same.

Create different layout directories for different resolutions

  • Creating different layouts for different screen sizes ensures that the limitations of smaller screen sizes are taken care of and the advantages offered by bigger screen sizes are put to the best use.
  • The Android documentation here mentions the conventions to be followed while designing.
  • Although over the years, android has become better at auto-adjusting layouts for different screen sizes. However, if the no. of views and widgets are high, auto-adjusting does not work well as in case of PSLab and it is better to create different sets of layouts.
  • As evident from the picture of the 8” tablet, although the auto-adjusted layout is manageable, the layout looks stretched and does not utilize the entire screen space, so it a better UI can be made by creating a dedicated layout directory for bigger screens.

Additional resources

 

Using Sensors with PSLab Android App

The PSLab Android App as of now supports quite a few sensors. Sensors are an essential part of many science experiments and therefore PSLab has a feature to support plug & play sensors. The list of sensors supported by PSLab can be found here.

  • AD7718 – 24-bit 10-channel Low voltage Low power Sigma Delta ADC
  • AD9833 – Low Power Programmable Waveform generator
  • ADS1115 – Low Power 16 bit ADC
  • BH1750 – Light Intensity sensor
  • BMP180 – Digital Pressure Sensor
  • HMC5883L – 3-axis digital magnetometer
  • MF522 – RFID Reader
  • MLX90614 – Infrared thermometer
  • MPU6050 – Accelerometer & gyroscope
  • MPU925x – Accelerometer & gyroscope
  • SHT21 – Humidity sensor
  • SSD1306 – Control for LED matrix
  • Sx1276 – Low Power Long range Transceiver
  • TSL2561 – Digital Luminosity Sensor

All the sensors except Sx1276 communicate using the I2C protocol whereas the Sx1276 uses the SPI protocol for communication. There is a dedicated set of ports on the PSLab board for the communication under the label I2C with the ports named 3.3V, GND, SCL & SDA.

blog_post_7_1

Fig; PSLab board sketch

Any I2C sensor has ports named 3.3V/VCC, GND, SCL, SDA at least along with some other ports in some sensors. The connections are as follows:

  1. 3.3V on PSLab – 3.3V/VCC on sensor
  2. GND on PSLab – GND on sensor
  3. SCL on PSLab – SCL on sensor
  4. SDA on PSLab – SDA on sensor

The diagram here shows the connections

For using the sensors with the Android App, there is a dedicated I2C library written in communication in Java for the communication. Each sensor has its own specific set of functionalities and therefore has its own library file. However, all these sensors share some common features like each one of them has a getRaw method which fetches the raw sensor data. For getting the data from a sensor, the sensor is initially connected to the PSLab board.

The following piece of code is responsible for detecting any devices that are connected to the PSLab board through the I2C bus. Each sensor has it’s own unique address and can be identified using it. So, the AutoScan function returns the addresses of all the connected sensors and the sensors can be uniquely identified using those addresses.

public ArrayList<Integer> scan(Integer frequency) throws IOException {
	if (frequency == null) frequency = 100000;
	config(frequency);
	ArrayList<Integer> addresses = new ArrayList<>();
	for (int i = 0; i < 128; i++) {
		int x = start(i, 0);
		if ((x & 1) == 0) {
			addresses.add(i);
		}
		stop();
	}
	return addresses;
}

 

As per the addresses fetched, the sensor library corresponding to that particular sensor can be imported and the getRaw method can be called. The getRaw method will return the raw sensor data. For example here is the getRaw method of ADS1115.

public int[] getRaw() throws IOException, InterruptedException {
	String chan = typeSelection.get(channel);
	if (channel.contains("UNI"))
		return new int[]{(int) readADCSingleEnded(Integer.parseInt(chan))};
	else if (channel.contains("DIF"))
		return new int[]{readADCDifferential(chan)};
	return new int[0];
}

Here the raw data is returned in the form of voltages in mV.

Similarly, the other sensors return some values like luminosity sensor TSL2561 returns values of luminosity in Lux, the accelerometer & gyroscope MPU6050 returns the angles of the 3-axes.

In order to initiate the process of getting raw data from the sensor in Sensor Activity, the object for the sensor is created and the method of getRaw is called. The following is the implementation for ADS1115. The rest of the sensors also have an implementation similar to this. There are try-catch statements in the code to handle some of the exceptions thrown during process of method calls.

ADS1115 ADS1115 = null;
try {
	ADS1115 = new ADS1115(i2c);
} catch (IOException | InterruptedException e) {
	e.printStackTrace();
}

int[] dataADS1115 = null;
String datadispADS1115 = null;
try {
	if (ADS1115 != null) {
		dataADS1115 = ADS1115.getRaw();
	}
} catch (IOException | InterruptedException e) {
	e.printStackTrace();
}

if (dataADS1115 != null) {
	for(int i = 0; i < dataADS1115.length; i++)
		datadispADS1115 += String.valueOf(dataADS1115[i]);
	}

tvSensorGetRaw.setText(datadispADS1115);

 

Additional Resources

  1. Sensor implementation in PSLab Python repository – https://github.com/fossasia/pslab-python/tree/development/PSL/SENSORS
  2. Using the sensors with Arduino in case you have worked with Arduino before – The basic connections are same as PSLab http://www.instructables.com/id/Arduino-MPU-6050-Getting-It-to-Work/

Creating Custom Components in the PSLab Android App

PSLab Android App supports a lot of features and each of these features need components & views for their implementation. A typical UI of PSLab is shown in the figure below. Considering the number of views & components used in the figure, implementation of each view & component separately would lead to a huge volume of repetitive and inefficient code. As it is evident that the EditText and two buttons beside it keep repeating a lot, it is wiser to create a single custom component consisting of an EditText and two buttons. This not only leads to efficient code but also results in a drastic reduction of the volume of code.

Android has a feature which allows creating components. For almost all the cases, the pre-defined views in Android serve our purpose of creating the UIs. However, sometimes there is a need to create custom components to reduce code volume and improve quality. Custom components are used when a particular set of component needed by us is not present in the Android view collection or when a pattern of components is frequently repeated or when we need to reduce the code complexity.

The above set can be replaced by defining a custom component which includes an edittext and two buttons and then treating it like just any other component. To get started with creating a custom component, the steps are the following:

Create a layout for the custom component to be designed

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal" android:layout_width="match_parent"
   android:layout_height="match_parent">

   <Button
       android:id="@+id/button_control_plus"
       android:layout_width="0dp"
       android:layout_weight="0.5"
       android:layout_height="20dp"
       android:background="@drawable/button_minus" />

   <EditText
       android:id="@+id/edittext_control"
       android:layout_width="0dp"
       android:layout_weight="2"
       android:layout_height="24dp"
       android:layout_marginTop="@dimen/control_margin_small"
       android:inputType="numberDecimal"
       android:padding="@dimen/control_edittext_padding"
       android:background="@drawable/control_edittext" />

   <Button
       android:id="@+id/button_control_minus"
       android:layout_width="0dp"
       android:layout_weight="0.5"
       android:layout_height="20dp"
       android:background="@drawable/button_plus" />
</LinearLayout>

The layout file edittext_control.xml is created with three views and each one of them has been assigned an ID along with all the other relevant parameters.

Incorporate the newly created custom layout in the Activity/Fragment layout file

<org.fossasia.pslab.others.Edittextwidget
       android:id="@+id/etwidget_control_advanced1"
       android:layout_height="wrap_content"
       android:layout_width="0dp"
       android:layout_weight="2"
       android:layout_marginLeft="@dimen/control_margin_small"
       android:layout_marginStart="@dimen/control_margin_small"
/>

The custom layout can be added the activity/fragment layout just like any other view and can be assigned properties similarly.

Create the activity file for the custom layout

public class Edittextwidget extends LinearLayout{

   private EditText editText;
   private Button button1;
   private Button button2;
   private double leastCount;
   private double maxima;
   private double minima;

 
   public Edittextwidget(Context context, AttributeSet attrs, int defStyle) {
       super(context, attrs, defStyle);
       applyAttrs(attrs);
   }

   public Edittextwidget(Context context, AttributeSet attrs) {
       super(context, attrs);
       applyAttrs(attrs);
   }

   public Edittextwidget(Context context) {
       super(context);
   }

  public void init(Context context, final double leastCount, final double minima, final double maxima) {
       View.inflate(context, R.layout.edittext_control, this);
       editText = (EditText) findViewById(R.id.edittext_control);
       button1 = (Button) findViewById(R.id.button_control_plus);
       button2 = (Button) findViewById(R.id.button_control_minus);

       button1.setOnClickListener(new OnClickListener() {
           @Override
           public void onClick(View v) {
               Double data = Double.valueOf(editText.getText().toString());
               data = data - leastCount;
               data = data > maxima ? maxima : data;
               data = data < minima ? minima : data;
               editText.setText(String.valueOf(data));
           }
       });

       button2.setOnClickListener(new OnClickListener() {
           @Override
           public void onClick(View v) {
               Double data = Double.valueOf(editText.getText().toString());
               data = data + leastCount;
               data = data > maxima ? maxima : data;
               data = data < minima ? minima : data;
               editText.setText(String.valueOf(data));
           }
       });
   }

   private void applyAttrs(AttributeSet attrs) {
       TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Edittextwidget);
       final int N = a.getIndexCount();
       for (int i = 0; i < N; ++i) {
           int attr = a.getIndex(i);
           switch (attr) {
               case R.styleable.Edittextwidget_leastcount:
                   this.leastCount = a.getFloat(attr, 1.0f);
                   break;
               case R.styleable.Edittextwidget_maxima:
                   this.maxima = a.getFloat(attr, 1.0f);
                   break;
               case R.styleable.Edittextwidget_minima:
                   this.minima = a.getFloat(attr, 1.0f);
           }
       }
       a.recycle();
   }
}

In the activity file Editextwidget.java, the views of the custom layout are defined and functionalities are assigned to them. For example, here there are two buttons which work as increment/decrement buttons and an edittext which takes numeric input. The buttons are initiated just like the way they are done in other activity/fragment using OnClickListener.

Define the attributes for the custom layout

<declare-styleable name="Edittextwidget">
     <attr name="leastcount" format="float" />
     <attr name="maxima" format="float" />
     <attr name="minima" format="float" />
</declare-styleable>

The attributes for the custom layout are defined in the attrs.xml file. Each attribute is assigned a name and a format which can be int, float, double, string etc.

Finally call the methods of the custom layout from the desired activity/fragment

Edittextwidget etwidgetControlAdvanced1 = (Edittextwidget)view.findViewById(R.id.etwidget_control_advanced1);

etwidgetControlAdvanced1.init(getContext(), 1.0, 10.0, 5000.0);

The init method of Edittextwidget.java is called while passing the relevant parameters like context, least count, maxima and minima.

Additional Resources on Custom Components

  1. Official Android Guide on Custom components – https://developer.android.com/guide/topics/ui/custom-components.html
  2. Simple example of creating a custom component to get started – https://www.tutorialspoint.com/android/android_custom_components.htm

Trigger Controls in Oscilloscope in PSLab

PSLab Desktop App has a feature of oscilloscope. Modern day oscilloscopes found in laboratories support a lot of advanced features and addition of trigger controls in oscilloscope was one such attempt in adding an advanced feature in the oscilloscope. As the current implementation of trigger is not robust enough, this feature would help in better stabilisation of waveforms.

Captured waveforms often face the problem of distortion and trigger helps to solve this problem. Trigger in oscilloscope is an essential feature for signal characterisation.  as it synchronises the horizontal sweep of the oscilloscope to the proper point of the signal. The trigger control enables users to stabilise repetitive waveforms as well as capture single-shot waveforms. By repeatedly displaying similar portion of the input signal, the trigger makes repetitive waveform look static. In order to visualise how an oscilloscope looks with or without a trigger see the following figures below.

blog_post_5_1

blog_post_5_2

Fig 1: (a) Without trigger  (b) With trigger

The Fig:1(a) is the actual waveform received by the oscilloscope and it can be easily noticed that interpreting it is confusing due to the overlapping of multiple waveforms together. So, in Fig:1(b) the trigger control stabilises the waveforms and captures just one waveform.

In general the commonly used trigger modes in laboratory oscilloscopes are:-

  • Auto – This trigger mode allows the oscilloscope to acquire a waveform even when it does not detect a trigger condition. If no trigger condition occurs while the oscilloscope waits for a specific period (as determined by the time-base setting), it will force itself to trigger.
  • Normal – The Normal mode allows the oscilloscope to acquire a waveform only when it is triggered. If no trigger occurs, the oscilloscope will not acquire a new waveform, and the previous waveform, if any, will remain on the display.
  • Single – The Single mode allows the oscilloscope to acquire one waveform each time you press the RUN button, and the trigger condition is detected.
  • Scan – The Scan mode continuously sweeps waveform from left to right.

Implementing Trigger function in PSLab

PSLab has a built in basic functionality of trigger control in the configure_trigger method in sciencelab.py. The method gets called when trigger is enabled in the GUI. The trigger is activated when the incoming wave reaches a certain voltage threshold and the PSLab also provides an option of either selecting the rising or falling edge for trigger. Trigger is especially useful in experiments handling waves like sine waves, square wave etc. where trigger helps to get a clear picture.

In order to initiate trigger in the PSLab desktop app, the configure_trigger method in sciencelab.py is called. The configure_trigger method takes some parameters for input but they are optional. If values are not specified the default values are assumed.

def configure_trigger(self, chan, name, voltage, resolution=10, **kwargs):
        
  prescaler = kwargs.get('prescaler', 0)
        try:
            self.H.__sendByte__(CP.ADC)
            self.H.__sendByte__(CP.CONFIGURE_TRIGGER)
            self.H.__sendByte__(
                (prescaler << 4) | (1 << chan))  
            if resolution == 12:
                level = self.analogInputSources[name].voltToCode12(voltage)
                level = np.clip(level, 0, 4095)
            else:
                level = self.analogInputSources[name].voltToCode10(voltage)
                level = np.clip(level, 0, 1023)

            if level > (2 ** resolution - 1):
                level = (2 ** resolution - 1)
            elif level < 0:
                level = 0

            self.H.__sendInt__(int(level))  # Trigger
            self.H.__get_ack__()
        
        except Exception as ex:
  	    self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name)

The method takes the following parameters in the method call

  • chan – Channel . 0, 1,2,3. corresponding to the channels being recorded by the capture routine(not the analog inputs).
  • name – The name of the channel. ‘CH1’… ‘V+’.
  • voltage – The voltage level that should trigger the capture sequence(in Volts).

The similar feature will also be used in oscilloscope in the Android app with the code corresponding to this method  in ScienceLab written in Java.

Additional Resources

  1. Read more about Trigger here – http://www.radio-electronics.com/info/t_and_m/oscilloscope/oscilloscope-trigger.php
  2. Learn more about trigger modes in oscilloscopes – https://www.picotech.com/library/oscilloscopes/advanced-digital-triggers
  3. PSLab Python repository to know the underlying code – https://github.com/fossasia/pslab-python

 

Designing Control UI of PSLab using Moqups

Mockups are an essential part of app development cycle. With numerous mock-up tools available for android apps (both offline and online), choosing the right mock-up tool becomes quite essential. The developers need a tool that supports the latest features like drag & drop elements, support collaboration upto some extent and allow easy sharing of mockups. So, Moqups was chosen as the mockups tool for the team.

Like other mock-up tools available in the market, using moqups is quite simple and it’s neat & simple user interface makes the job easier. This blog discusses some of the important aspects that need to be taken care of while designing mockups.

A typical online mock-up tool would look like this having a palette to drag & drop UI elements like Buttons, Text boxes, Check boxes etc. Additionally a palette to modify the features of each element ( here on the right ) and other options at the top related to prototyping, previewing etc.

    • The foremost challenge while designing any mock-up is to keep the design neat and simple such that even a layman doesn’t face problems while using it. A simple UI is always appealing and the current trend of UIs is creating flat & crisp UIs.

    • For example, the above mock-up design has numerous advantages for both a user and also as a programmer. There are seek bars as well as text boxes to input the values along with the feature of displaying the value that actually gets implemented and it’s much simpler to use. From the developer’s perspective, presence of seven identical views allows code reuse. A simple layout can be designed for one functionality and since all of them are identical, the layout can be reused in a Recyclerview.
    • The above design is a portion of the Control UI which displays the functionalities for  using PSLab as a function generator and as a voltage/current source.

    • The other section of the UI is of the Read portion. This has the functionalities to measure various parameters like voltage, resistance, capacitance, frequency and counting pulses. Here, drop-down boxes have been provided at places where channel selection is required. Since voltages are most commonly measured values in any experiment, voltages of all the channels have been displayed simultaneously.
    • Attempts should always be made to keep the smaller views as identical as possible since it becomes easier for the developer to implement it and also for the user to understand.

 

The Control UI has an Advanced Section which has features like Waveform Generators allows to generate sine/square waves of a given frequency & phase, Configuring Pulse Width Modulation (PWM)  and selecting the Digital output channel. Since, the use of such features are limited to higher level experiments, they have been separately placed in the Advanced section.

Even here drop-down boxes, text boxes & check boxes have been used to make UI look interactive.

The common dilemma faced while writing the XML file is regarding the view type to be chosen as Android provides a lot of them like LinearLayout, ConstraintLayout, ScrollView, RecyclerView, ListView etc. So, although there are several possible ways of designing a view. Certain things like using ListView or RecyclerView where there is repetition of elements is easier and when the elements are quite distinct from each other, it is better to stick to LinearLayout and ConstraintLayout.

Using ButterKnife in PSLab Android App

ButterKnife is an Android Library which is used for View injection and binding. Unlike Dagger, ButterKnife is limited to views whereas Dagger has a much broader utility and can be used for injection of anything like views, fragments etc. Being limited to views makes it much more simpler to use compared to dagger.

The need for using ButterKnife in our project was felt due to the fact binding views would be much more simpler in case of layouts like that of Oscilloscope Menu which has multiple views in the form of Textboxes, Checkboxes, Seekbars, Popups etc. Also, ButterKnife makes it possible to access the views outside the file in which they were declared. In this blog, the use of ButterKnife is limited to activities and fragments.

ButterKnife can used anywhere we would have otherwise used findViewById(). It helps in preventing code repetition while instantiating views in the layout. The ButterKnife blog has neatly listed all the possible uses of the library.

The added advantage of using Butterknife are-

  • The hassle of using Boilerplate code is not needed and the code volume is reduced significantly in some cases.
  • Setting up ButterKnife is quite easy as all it takes is adding one dependency to your gradle file.
  • It also has other features like Resource Binding (i.e binding String, Color, Drawable etc.).
  • Other uses like simplification of code while using buttons. For example, there is no need of using findViewById and setOnClickListener, simple annotation of the button ID with @OnClick does the task.

Using butterknife was essential for PSLab Android since for the views shown below which has too many elements, writing boilerplate code can be a very tedious task.

Using ButterKnife in activities

The PSLab App defines several activities like MainActivity, SplashActivity, ControlActivity etc. each of which consists of views that can be injected with ButterKnife.

After setting up ButterKnife by adding dependencies in gradle files, import these modules in every activity.

import butterknife.BindView;
import butterknife.ButterKnife;

Traditionally, views in Android are defined as follows using the ID defined in a layout file. For this findViewById is used to retrieve the widgets.

private NavigationView navigationView;
private DrawerLayout drawer;
private Toolbar toolbar;

navigationView = (NavigationView) findViewById(org.fossasia.pslab.R.id.nav_view);
drawer = (DrawerLayout) findViewById(org.fossasia.pslab.R.id.drawer_layout);
toolbar = (Toolbar) findViewById(org.fossasia.pslab.R.id.toolbar);

However, with the use of Butterknife, fields are annotated with @BindView and a View ID for finding and casting the view automatically in the layout files.
After the annotation, finding and casting of views ButterKnife.bind(this) is called to bind the views in the corresponding activity.

@BindView(R.id.nav_view) NavigationView navigationView;
@BindView(R.id.drawer_layout) DrawerLayout drawer;
@BindView(R.id.toolbar) Toolbar toolbar;
setContentView(R.layout.activity_main);
ButterKnife.bind(this);

Using ButterKnife in fragments

The PSLab Android App implements ApplicationFragment, DesignExperimentsFragment, HomeFragment etc., so ButterKnife for fragments is also used.

Using ButterKnife is fragments is slightly different from using it in activities as the binded views need to be destroyed on leaving the fragment as the fragments have different life cycle( Read about it here). For this an Unbinder needs to be defined to unbind the views before they are destroyed.

Quoting from the official documentation

When binding a fragment in OnCreateView, set the views to null in OnDestroyView. Butter Knife returns an Unbinder instance when you call bind to do this for you. Call its unbind method in the appropriate lifecycle callback.

So, an additional module of ButterKnife is imported

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;

Similar as that of activities, the code below can be replaced by the corresponding ButterKnife code.

TextView tvDeviceStatus = (TextView)view.findViewById(org.fossasia.pslab.R.id.tv_device_status);
TextView tvVersion = (TextView)view.findViewById(org.fossasia.pslab.R.id.tv_device_version);
ImageView imgViewDeviceStatus = (ImageView)view.findViewById(org.fossasia.pslab.R.id.img_device_status);
@BindView(R.id.tv_device_status) TextView tvDeviceStatus;
@BindView(R.id.tv_device_version) TextView tvVersion;
@BindView(R.id.img_device_status) ImageView imgViewDeviceStatus;
private Unbinder unbinder;

Additionally, the unbinder object is used to store the returned Unbinder instance when ButterKnife.bind is called for Fragments.

unbinder = ButterKnife.bind(this,view);

Finally, the unbind method of unbinder is called while view is destroyed.

@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}

Prototyping Android Apps using Invision

Often, while designing apps, we need planning and proper designing before actually building the apps. This is where mock-up tools and prototyping is useful. While designing user interfaces, the first step is usually creating mockups. Mockups give quite a good idea about the appearance of various layouts of the app. However, mockups are just still images and they don’t give a clue about the user experience of the app. This is where prototyping tools are useful. Prototyping helps to get an idea about the user experience without actually building the app.

Invision is an online prototyping service which was used for initial testing of the PSLab app. Some of pictures below are the screenshots of our prototype taken in Invision. Since, it supports collaboration among developers, it proves to be a very useful tool. 

  • Using Invision is quite simple. Visit the Invision website and sign up for an account.Before using invision for prototyping, the mockups of the UI layouts must be ready since Invision is simply meant for prototyping and not creating mockups. There are a lot of mock-up tools available online which are quite easy to use.
  • Create a new project on Invision. Select the project type – Prototype in this case followed by selecting the platform of the project i.e. Android, iOS etc.
  • Collaborators can be added to a project for working together.
    • After project creation and adding collaborators is done with, the mock-up screens can be uploaded to the project directory
    • Select any mock-up screen, the window below appears, there are a few modes available in the bottom navbar – Preview mode, Build mode, Comment mode, Inspect Mode and History Mode.
        • Preview Mode – View your screen and test the particular screen prototype.
        • Build Mode – Assign functionality to buttons, navbars, seek bars, check boxes etc. and other features like transitions.
        • Comment Mode – Leave comments/suggestions regarding performance/improvement for other collaborators to read.
        • Inspect mode – Check for any unforeseen errors while building.
        • History Mode – Check the history of changes on the screen.
  • Switch to the build mode, it would now prompt to click & drag to create boxes around buttons, check boxes, seek bars etc,(shown above). Once a box ( called as “hotspot” in Invision ), a dialog box pops up asking to assign functionalities.
  • The hotspot/box which was selected must link to another menu/layout or result in some action like app closing. This functionality is provided by the “Link To:” option.
  • Then the desired gesture for activating the hotspot is selected which can be tap for buttons & check boxes, slide for navbars & seek bars etc from the “Gesture:” option.
  • Lastly, the transition resulting due to moving from the hotspot to the assigned window in “Link To:” is selected from the “Transition:” menu.

This process can be repeated for all the screens in the project. Finally for testing and previewing the final build, the screen which appears when the app starts is selected and further navigation, gestures etc. are tested there. So, building prototypes is quite an interesting and easy task.

Porting PSLab Libraries – Python to Java

PSLab has existing communication libraries and sensor files in Python which were created during the development of Python Desktop Application.

The initial task and challenge was porting this existing code to Java to be used by the Android App. Since, the python libraries also utilized the object oriented model of programming, porting from Python to Java had the similar code structure and organization.

Common problems faced while porting from Python to Java

  • The most common problem is explicitly assigning data types to variables in Java since Python manages data types on its own. However, most of the time the data types are quite evident from the context of their use and understanding the purpose of the code can make the task much simpler.
  • Another task was migrating the Python data structures to their corresponding Java counterparts like a List in Python represents an ArrayList in Java, similarly a Dictionary corresponds to a HashMap and so on.
  • Some of the sections of the code uses highly efficient libraries like Numpy and Scipy for some mathematical functions. Finding their corresponding Java counterparts in libraries was a challenge. This was partly solved by using Apache Common Math which is a library dedicated for mathematical functions. Some of the functions were directly implemented using this library and for rest of the portions, the code was written after understanding the structure and function of Numpy methods.

While porting the code from Python to Java, some of the steps which we followed:

  • Matching corresponding data-structures

The Dictionary in python…

Gain_scaling = OrderedDict ([('GAIN_TWOTHIRDS', 0.1875), ('GAIN_ONE', 0.125), ('GAIN_TWO', 0.0625), ('GAIN_FOUR', 0.03125), ('GAIN_EIGHT', 0.015625), ('GAIN_SIXTEEN', 0.0078125)])

…was mapped to corresponding Java HashMap in the manner given below. A point to be noted here is for adding elements to a HashMap can be done only from a method and not at the time of declaration of HashMap.

private HashMap <String,Double> gainScaling = new HashMap <String,Double>();

gainScaling.put("GAIN_TWOTHIRDS",0.1875);
gainScaling.put("GAIN_ONE",0.125);
gainScaling.put("GAIN_TWO",0.0625);
gainScaling.put("GAIN_FOUR",0.03125);
gainScaling.put("GAIN_EIGHT",0.015625);
gainScaling.put("GAIN_SIXTEEN",0.0078125);

Similarly, the List in Python can be  be converted to the corresponding ArrayList in Java.

  • Assigning data types and access modifiers to corresponding variables in Java
POWER_ON = 0x01
gain_choices = [RES_500mLx, RES_1000mLx, RES_4000mLx]
ain_literal_choices = ['500mLx', '1000mLx', '4000mLx']
scaling = [2, 1, .25]
private int POWER_ON = 0x01;
public int[] gainChoices = {RES_500mLx,RES_1000mLx,RES_4000mLx};
public String[] gainLiteralChoices = {"500mLx", "1000mLx", "4000mLx"};
public double[] scaling = {2,1,0.25};

Assigning data types and the corresponding access modifiers can get tricky sometimes. So, understanding the code is essential to know whether a variable in limited to the class or needs to be accessed outside the class, whether a variable is int, short, float or double etc.

  • Porting Numpy & Scipy functions to Java using Apache Common Math

For example, this piece of code gives the pitch of acceleration. It uses mathematical functions like arc-tan.

pitchAcc = np.arctan2(accData[1], accData[2]) * 180 / np.pi

The corresponding version of arc-tan in Apache Common Math is used in Java.

double pitchAcc = Math.atan2(accelerometerData[1], accelerometerData[2]) * 180 / pi;
  • Porting by writing the code for Numpy and Scipy functions explicitly

In the code below, rfftfreq is used to calculate the Discrete Fourier Transform(DFT) sample frequencies.

freqs = self.fftpack.rfftfreq(N, d=(xReal[1] - xReal[0]) / (2 * np.pi))

Since, hardly any library in Java supports DFT, the corresponding code for rfftfreq was self-written.

double[] rfftFrequency(int n, double space){
    double[] returnArray = new double[n + 1];
    for(int i = 0; i < n + 1; i++){
        returnArray[i] =  Math.floor(i / 2) / (n * space);
    }
    return Arrays.copyOfRange(returnArray, 1, returnArray.length);
}

After porting of all communication libraries and sensor files are done, the testing of features can also be initiated. Currently, the ongoing development includes porting of the some of the remaining files and working on the the best possible User Interface.