Expandable ListView In PSLab Android App

In the PSLab Android App, we show a list of experiments for the user to perform or refer to while performing an experiment, using PSLab hardware device. A long list of experiments need to be subdivided into topics like Electronics, Electrical, School Level, Physics, etc. In turn, each category like Electronics, Electrical, etc can have a sub-list of experiments like:

  • Electronics
    • Diode I-V characteristics
    • Zener I-V characteristics
    • Transistor related experiments
  • Electrical
    • Transients RLC
    • Bode Plots
    • Ohm’s Law

This list can continue in similar fashion for other categories as well. We had to  display  this experiment list to the users with a good UX, and ExpandableListView seemed the most appropriate option.

ExpandableListView is a two-level listView. In the Group view an individual item can be expanded to show it’s children. The Items associated with ExpandableListView come from ExpandableListAdapter.

 

 

 

 

 

 

 

 

Implementation of Experiments List Using ExpandableListView

First, the ExpandableListView was declared in the xml layout file inside some container like LinearLayout/RelativeLayout.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">
   <ExpandableListView
       android:id="@+id/saved_experiments_elv"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:divider="@color/colorPrimaryDark"
       android:dividerHeight="2dp" />
</LinearLayout>

Then we populated the data onto the ExpandableListView, by making an adapter for ExpandableListView by extending BaseExpandableListAdapter and implementing its methods. We then passed a Context, List<String> and Map<String,List<String>> to the Adapter constructor.

Context: for inflating the layout

List<String>: contains titles of unexpanded list

Map<String,List<String>>: contains sub-list mapped with title string

public SavedExperimentAdapter(Context context,
                                 List<String> experimentGroupHeader,
                                 HashMap<String, List<String>> experimentList) {
       this.context = context;
       this.experimentHeader = experimentGroupHeader;
       this.experimentList = experimentList;
   }

In getGroupView() method, we inflate, set title and return group view i.e the main list that we see on clicking and the  sub-list is expanded. You can define your own layout in xml and inflate it. For PSLab Android, we used the default one provided by Android

 android.R.layout.simple_expandable_list_item_2
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
   String headerTitle = (String) getGroup(groupPosition);
   if (convertView == null) {
       LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       convertView = inflater.inflate(android.R.layout.simple_expandable_list_item_2, null);
   }
   TextView tvExperimentListHeader = (TextView) convertView.findViewById(android.R.id.text1);
   tvExperimentListHeader.setTypeface(null, Typeface.BOLD);
   tvExperimentListHeader.setText(headerTitle);
   TextView tvTemp = (TextView) convertView.findViewById(android.R.id.text2);
   tvTemp.setText(experimentDescription.get(groupPosition));
   return convertView;
}

Similarly, in getChildView() method, we inflate, set data and return child view. We wanted simple TextView as sub-list item thus inflated the layout containing only TextView and setText by taking reference of textView from the inflated view.

@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
   String experimentName = (String) getChild(groupPosition, childPosition);
   if (convertView == null) {
       LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       convertView = inflater.inflate(R.layout.experiment_list_item, null);
   }
   TextView tvExperimentTitle = (TextView) convertView.findViewById(R.id.exp_list_item);
   tvExperimentTitle.setText(experimentName);
   return convertView;
}

The complete code for the Adapter can be seen here.

After creating the adapter we proceeded similarly to the normal ListView. Take the reference for ExpandableListView by findViewById() or BindView if you are using ButterKnife and set the adapter as an instance of adapter created above.

@BindView(R.id.saved_experiments_elv)
ExpandableListView experimentExpandableList;
experimentAdapter = new SavedExperimentAdapter(context, headerList, map);
experimentExpandableList.setAdapter(experimentAdapter);
Source: PSLab Android

Roadmap

We are planning to divide the experiment sub-list into categories like

  • Electronics
    • Diode
      • Diode I-V
      • Zener I-V
      • Diode Clamping
      • Diode Clipping
    • BJT and FET
      • Transistor CB (Common Base)
      • Transistor CE (Common Emitter)
      • Transistor Amplifier
      • N-FET output characteristic
    • Op-Amps
  • Electrical

This is a bit more complex than it looks, I tried using an ExpandableListView as a child for a group item but ran into some errors. I will write a post as soon as this view hierarchy has been achieved.

Resources

Communication by pySerial python module in PSLab

In the PSLab Desktop App we use Python for communication between the PC and PSLab device. The PSLab device is connected to PC via USB cable. The power for the hardware device is provided by the host through USB which in this case is a PC. We need well structured methods to establish communication between PC and PSLab device and this is where pySerial module comes in. We will discuss how to communicate efficiently from PC to a device like PSLab itself using pySerial module.

How to read and write data back to PSLab device?

pySerial is a python module which is used to communicate serially with microcontroller devices like Arduino, RaspBerry Pi, PSLab (Pocket Science Lab), etc. Serial data transfer is easier using this module, you just need to open a port and obtain serial object, which provides useful and powerful functionality. Users can send string (which is an array of bytes) or any other data type all data types can be expressed as byte string using struct module in python, read a specific number of bytes or read till some specific character like ‘\n’ is encountered. We are using this module to create custom read and write functions.

How to Install pySerial and obtain serial object for communication?

You can install pySerial using pip by following command

pip install pyserial

Once it’s installed we can now import it in our python script for use.

Obtain Serial Object

In Linux

>>> import serial
>>> ser = serial.Serial(‘/dev/ttyUSB0’)

In Windows

>>> ser = serial.Serial()
>>> ser.baudrate = 19200
>>> ser.port = ‘COM1’

Or

>>> ser = serial.Serial(‘COM1’, 19200)

You can specify other properties like timeout, stopbits, etc to Serial constructor.

Complete list of parameters is available here. Now this “ser” is an object of Serial class that provides all the functionalities through its interface. In PSLab we obtain a serial object and implement custom methods to handle communication which isn’t directly provided by pySerial, for example if we need to implement a function to get the version of the PSLab device connected. Inside the version read function we need to send some bytes to the device in order to obtain the version string from device as a byte response.

What goes under the hood?

We send some sequence of bytes to PSLab device, every sequence of bytes corresponds to a unique function which is already written in device’s firmware. Device recognises the function and responses accordingly.

Let’s look at code to understand it better.

ser.write(struct.Struct(‘B’).pack(11))  #  Sends 11 as byte string
ser.write(struct.Struct(‘B’).pack(5))   #  Sends 5 as bytes string
x = ser.readline()                      #  Reads bytes until ‘\n’ is encountered   

To understand packing and unpacking using struct module, you can have a read at my other blog post Packing And Unpacking Data in JAVA in which I discussed packing and unpacking of data as byte strings and touched a bit on How it’s done in Python.  

You can specify how many bytes you want to read like shown in code below, which is showing and example for 100 bytes :

x = ser.read(100)

After your communication is complete you can simply close the port by:

ser.close()

Based on these basic interface methods more complex functions can be written to handle your specific needs. More details one how to implement custom methods is available at python-communication-library of PSLab which uses pySerial for communication between Client and PSLab device.

An example of custom read function is suppose I want to write a function to read an int from the device. int is of 2 bytes as firmware is written in C, so we read 2 bytes from device and unpack them in client side i.e on PC. For more such custom functions refer packet_handler.py of PSLab python communication library.

def getInt(self):
      “””
      reads two bytes from the serial port and
      returns an integer after combining them
      “””
      ss = ser.read(2)  # reading 2 bytes from serial object
      try:
          if len(ss) == 2:
              return CP.ShortInt.unpack(ss)[0]  # unpacking bytes to make int
      except Exception as ex:
          self.raiseException(ex, “Communication Error , Function : get_Int”)

Resources

Environment Monitoring with PSLab

In this post, we shall explore the working principle and output signals of particulate matter sensors, and explore how the PSLab can be used as a data acquisition device for these.

Working Principle

A commonly used technique employed by particulate matter sensors is to study the diffraction of light by dust particles, and estimate the concentration based on a parameter termed the ‘occupancy factor’. The following image illustrates how the most elementary particle sensors work using a photogate, and a small heating element to ensure continuous air flow by convection.

Occupancy Rate

Each time a dust particle of aerodynamic diameters 2.5um passes through the lit area, a phenomenon called Mie scattering which defines scattering of an electromagnetic plane wave by a homogenous sphere of diameter comparable to the wavelength of incident light, results in a photo-signal to be detected by the photosensor.  In more accurate dust sensors, a single wavelength source with a high quality factor such as a laser is used instead of LEDs which typically have broader spectra.

The signal output from the photosensor is in the form of intermittent digital pulses whenever a particle is detected. The occupancy ratio can be determined by measuring the sum total of time when a positive signal was output from the sensor to the total averaging time. The readings can be taken over a fairly long amount of time such as 30 seconds in order to get a more accurate representation of the occupancy ratio.

Using the Logic analyzer to capture and interpret signals

The PSLab has a built-in logic analyzer that can acquire data signals up to 67 seconds long at its highest sampling rate, and this period is more than sufficient to record and interpret a dataset from a dust sensor. An inexpensive dust sensor, DSM501A was chosen for the readings, and the following results were obtained

Dust sensor readings from an indoor, climate controlled environment. After the 100 second mark, the windows were opened to expose the sensor to the outdoor environment.

A short averaging time has resulted in large fluctuations in the readings, and therefore it is important to maintain longer averaging times for stable measurements.

Recording data with a python script instead of the app

The output of the dust sensor must be connected to ID1 of the PSLab, and both devices must share a common ground which is a prerequisite for exchange of DC signals. All that is required is to start the logic analyzer in single channel mode, wait for a specified averging time, and interpret the acquired data

Record_dust_sensor.py

from PSL import sciencelab   #import the required library
import time
import numpy as np
I = sciencelab.connect()           #Create the instance
I.start_one_channel_LA(channel='ID1',channel_mode=1,trigger_mode=0)  #record all level changes
time.sleep(30)   #Wait for 30 seconds while the PSLab gathers data from the dust sensor
a,_,_,_,e =I.get_LA_initial_states()      #read the status of the logic analyzer
raw_data =I.fetch_long_data_from_LA(a,1)  #fetch number of samples available in chan #1
I.dchans[0].load_data(e,raw_data)  
stamps =I.dchans[0].timestamps    #Obtain a copy of the timestamps
if len(stamps)>2:   #If more than two timestamps are available (At least one dust particle was detected
		if not self.I.dchans[0].initial_state:   #Ensure the starting position of timestamps
			stamps = stamps[1:] - stamps[0]   # is in the LOW state
	diff = np.diff(stamps)   #create an array of individual time gaps between successive level changes


	lows = diff[::2]      #Array of time durations when a particle was not present
	highs = diff[1::2]    #Array of time durations when a particle was present
	low_occupancy = 100*sum(lows)/stamps[-1] #Occupancy ratio
print (low_occupancy) # datasheets of individual dust sensors also provide a mathematical
                      #equation to interpret the occupancy ratio as concentration of
				#particulate matter

Further Reading, and application notes:

[1] LED based  dust Sensor application note

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

 

Establishing Communication between PSLab and an Android Device using the USB Host API

In this post, we are going to learn how to establish communication between the PSLab USB device and a connected Android device. We will implement our own custom read & write methods by using functions provided by USB Host API of Android SDK.

At first we need to enable communication to PSLab device by connecting it to Android Phone by an On-The Go (OTG) cable. We are communicating via the USB Host API of Android.

About Android USB

Android supports USB peripherals through two modes:

  • Android Accessory: In this mode external USB device acts as host.
  • Android Host: In this mode Android Device acts as host and powers the external device.
Source : Android Developers Docs

Obtaining Permission to access USB device

When a USB device is connected to Android device, you need to obtain permissions to access the USB device. You have two ways, I have used intent-filter method to obtain permission in PSLab project, but you can also use the approach to implement a broadcast receiver.

Option 1:

Add a intent filter in the activity which would handle that connected USB device. This is an implicit way to obtain permission.

<activity ...>
...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
</activity>

And add device details like your vendor ID and product ID in device_filter.xml

<resources>

    <usb-device vendor-id="1240" product-id="223" />

</resources>

Now when you connect your USB device, permission dialog like below would pop up:

Option 2:

  • If you want to obtain permission explicitly, first create broadcastreceiver which would be broadcasted which you call requestPermission().

    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
    
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if(device != null){
                       }
                    }
                    else {
                        Log.d(TAG, "permission denied for device " + device);
                    }
                }
            }
        }
    };

    Register this broadcastreceiver in your onCreate method of your activity.

    UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    ...
    mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(mUsbReceiver, filter);

    And call requestPermission method to show a dialog for permission

    UsbDevice device;
    ...
    mUsbManager.requestPermission(device, mPermissionIntent);

    Now when you open your App permission dialog like shown below would pop up:

Obtain Read & Write Endpoints

Now that you have permission to communicate with a USB device connected. Next step is to obtain read and write Endpoints to read and write to USB device by using bulkTransfer() function.

The definition of bulkTransfer() methods is

int bulkTransfer (UsbEndpoint endpoint, 
                byte[] buffer, 
                int length, 
                int timeout)

endpoint : Usb Endpoint ( the endpoint for this transaction )

buffer : byte ( buffer for data to send or receive )

length : int ( length of data to send/receive )

timeout : int ( in milliseconds, 0 is infinite )

For code to obtain read, write Endpoint through Data Interface of USB device. Open() method of PSLab can be referenced.

There are two ways for communication :

  • Synchronous
  • Asynchronous

In PSLab, we use synchronous communication using bulkTransfer() method. Create a USB device connection object

mConnection = mUsbManager.openDevice(mUsbDevice);

As bulkTransfer methods are exposed by USB connection object. Using these you can implement your read & write functions to meet your project’s requirements. Or use bulkTransfer() directly to read & write data.

For example:

mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, bytesToRead, timeoutMillis)

So this covers the required for obtaining permission to access USB device and basics of how you can read data from and write data to USB device.

Also if this project interest you, feel free to contribute or raise any issue. PSLab-Android.

Resources

How to Collaborate Design on Hardware Schematics in PSLab Project

Generally ECAD tools are not built to support collaborative features such as git in software programming. PSLab hardware is developed using an open source ECAD tool called KiCAD. It is a practice in the electronic industry to use hierarchical blocks to support collaboration. One person can work on a specific block having rest of the design untouched. This will support a workaround to have a team working on a one hardware design just like a software design. In PSLab hardware repository, many developers can work simultaneously using this technique without having any conflicts in project files.

Printed Circuit Board (PCB) designing is an art. The way the components are placed and how they are interconnected through different type of wires and pads, it is an art for hardware designing engineers. If they do not use auto-route, PCB design for the same schematic will be quite different from one another.

There are two major approaches in designing PCBs.

  • Top Down method
  • Bottom Up method

Any of these methods can be implemented in PSLab hardware repository to support collaboration by multiple developers at the same time.

Top Down Method

In this method the design is starting from the most abstract definitions. We can think of this as a black box with several wires coming out of it. The user is aware of how to use the wires and to which devices they need to be connected. But the inside of the black box is not visible. Then a designer can open up this box and break the design down to several small black boxes which can perform a subset of functionalities the bigger black box did. He can go on breaking it down to even smaller boxes and reach the very bottom where basic components are found such as transistors, resistors, diodes etc.

Bottom Up Method

In the bottom up method, the opposite approach of the top down method is used. Small parts are combined together to design a much bigger part and they are combined together to build up an even bigger part which will eventually create the final design. Our human body is a great example for a use of bottom up method. Cells create organ; organs create systems and systems create the body.

Designing Top Down Designs using KiCAD

In PCB designing, the designers are free to choose whatever the approach they prefer more suitable for their project. In this blog, the Top Down method is used to demonstrate how to create a design from the abstract concepts. This will illustrate how to create a design with one layer deep in design using hierarchical blocks. However, these design procedures can be carried out as many times as the designer want to create depending on the complexity of the project.

Step 01 – Create a new project in KiCAD

Step 02 – Open up Eeschema to begin the design

Step 03 – Create a Hierarchical Sheet

Step 04 – Place the hierarchical sheet on the design sheet and give it a name

Step 05 – Enter sheet

Step 06 – Place components and create a schematic design inside the sheet and place hierarchical labels

Step 07 – Define the labels as input or output and give them an identifier. Once done, place them on appropriate places and connect with wires

Step 08 – Go back to main sheet to complete the hierarchical block

Step 09 – Place hierarchical pins on the block

Click on the “Place hierarchical pin” icon from the toolbar and click on the block. The pins can be placed on anywhere on the block. As a convention, input pins are placed on the left side and the output pins are placed on the right side of the block.

Step 10 – Complete the circuit

Resources: