Measuring CO2 with MQ135


A MQ135 sensor on a small PCB.

The MQ135 is a cheap gas sensor that is primarily intended for detecting the presence of flammable gases. It is marketed as a generalized “air quality” sensor, rather than precision device for measuring the concentration of any specific gas. Nevertheless, according to the data sheet the MQ135 is capable of measuring the concentrations of several gases, one of which is CO2. This makes it an attractive low-cost alternative to more specialized (and more expensive) CO2-specific sensors.

Much has been written about using the MQ135 as a CO2 sensor, mostly focusing on using it together with Arduino. This article will, firstly, discuss the working principles of the MQ135 sensor and how to read the data sheet, and secondly present how to use it with the PSLab.

Theory of operation

The MQ135 consists of a surface covered in a thin layer of SnO2, and a heater resistor which serves to raise the temperature of the SnO2 surface to several hundred degrees Celsius. SnO2 is an n-type semiconductor, in which donor electrons are excited to the conduction band at elevated temperatures. However, SnO2 when exposed to air readily adsorbs oxygen onto its surface. The adsorption reaction consumes electrons from the conduction band to form negatively charged oxygen species. Therefore, SnO2 has low conductivity in clean air. [Shimizu]

In the presence of a flammable gas, the gas will also adsorb onto the sensor surface, where it consumes the adsorbed oxygen species to form CO2 and H2O. This reaction releases the donor electrons back into the conduction band, thereby raising the conductivity of the sensor. By quantifying the conductivity response to the presence of various gases, a thin SnO2 surface can be used to determine the concentration of the gas. [Shimizu]

CO2, notably, is not a flammable gas. Here, the sensing mechanism is different; instead of reacting with adsorbed oxygen directly, CO2 sensing relies on water vapor first reacting with adsorbed oxygen to form adsorbed hydroxide (OH). CO2 then in turn reacts with the adsorbed hydroxide, forming carbonate (CO32-). This process ultimately also returns electrons to the SnO2 conduction band, and therefore results in increased conductivity. [Wang et al.]

SnO2 gas sensors can also detect the presence of reducing gases such as NOx. Here, the sensing mechanism is the opposite; the reducing gas adsorbs onto the sensor surface, consuming additional electrons from the conduction band, and thereby lowers the conductivity further. The MQ135 data sheet does not specify response curves for any reducing gases, so while the sensor should be sensitive to such gases, the user would have to experimentally determine the response curves themselves. [Shimizu]

MQ135 gas response curves

The MQ135 data sheet provides a graph showing how the sensor resistance changes with the concentration of several gases. Figure 1 shows a reproduction of the data sheet graph, where the points have been manually extracted from the data sheet graph using WebPlotDigitizer.

Note that the y-axis is Rs/R0, which is the ratio between the sensor resistance at any given moment (Rs) and the sensor resistance at a specific set of conditions (R0). This is because the sensor resistance depends on the exact amount of active material on the sensor surface, which varies between individual sensors. Therefore, the resistance response must be normalized against a predefined state in order for results from different sensors to be comparable. For the MQ135, this predefined state is 100 ppm ammonia vapor in otherwise clean air at 20 °C and 65% relative humidity. The sensor resistance at this state must be known in order for the curves in Figure 1 to apply. This may seem like a problem, since most people will not be able to reproduce those conditions. Fortunately, there are other ways to derive R0. More on that later.

The responses show clearly linear behavior in loglog-scale, which means that the relationship between resistance and concentration can be expressed as:

RS / R0 = a * cb    (1)

where a and b are constants, and c is gas concentration in ppm.

The curves in the Figure 1 have been fitted using exponential regression, specifically scipy.optimize.curve_fit.

Figure 2: Data sheet gas response values (points) and fitted curves (lines).

Equation 1 gives the sensor resistance as a function of gas concentration, but we are interested in the opposite relation, i.e. gas concentration as a function of sensor resistance. A little elementary algebra gets us there:

c = (1 / a)1/b * (RS / R0)1/b = A * (RS / R0)B    (2)

With that, we can correlate sensor resistance with gas concentration for any of the gases in Figure 1. Now, we need a way to determine the sensor resistance.

MQ135 electrical characteristics

As previously mentioned, the MQ135 behaves as a variable resistor when exposed to different gas concentrations. In order to measure gas concentration using equation 2, we need a way to measure the sensor resistance.

Figure 3: Circuit diagram of a typical sensing circuit using a MQ135. Note the voltage divider in the upper path.

By connecting a resistor in series with the sensor and measuring the voltage between them we can create a voltage divider, as in figure 3. The expression for the output voltage from a voltage divider is:

Vout = RL / (RS + RL) * Vin    (3)

Rearrange to isolate the sensor resistance:

RS = RL * (Vin / Vout – 1)    (4)

Understanding R0

While equation 4 provides a way to measure the sensor resistance, the sensor resistance alone is not enough to determine the gas concentration. This is because every individual sensor has a unique resistance response, based on the amount and distribution of active material on the sensor surface. Therefore, in order to compare the response of one sensor with other sensors, the response must be normalized. R0 is the normalization factor, and is defined as the sensor resistance at a known set of conditions, i.e. a certain temperature, relative, humidity, and most importantly a certain gas concentration. The ratio of the sensor resistance and R0 can be compared between sensors.

The MQ135 data sheet defines R0 as the sensor resistance at 100 ppm ammonia at 20 °C and 65% relative humidity. Of course, it is possible to redefine R0 at some other set of conditions, but if R0 is redefined, the curves in Figure 2 no longer apply! The only situation where it makes sense to redefine R0 is if you have the means to create new response curves for the gas you wish to measure. Otherwise, keeping R0 defined as above is the best available option.

That means we need a way to calculate R0, since most people will not be able to recreate the conditions at which it is defined. By rearranging equation 2, we get:

R0 = RS * (1 / A * c)-1/B    (5)

Since we can measure the sensor resistance, that means the only unknown term in the RHS of equation 5 is the concentration. In other words, if we can measure the sensor resistance in any known gas concentration, we can calculate R0.

As it happens, we do know the concentration of one of the gases in figure 2: Carbon dioxide. The concentration of CO2 in outside air is around 400 ppm (and rising, check for up to date values if you are reading this some years after 2021).

Accounting for temperature and humidity

The sensor response also depends on ambient temperature and humidity. The data sheet specifies the effect on sensor response at four different temperatures and two different humidity levels for ammonia vapor. Figure 4 shows a reproduction of the data sheet graph, plus fitted curves.

Figure 4: Data sheet temperature / humidity corrections (points) and fitter curves (lines).

From figure 4 we can see that the sensor resistance reaches a minimum at some temperature TM and then starts to increase. The reason the resistance decreases as the temperature increases toward TM is that higher surface temperature contributes to faster reaction rates between the gas and the adsorbed oxygen. The increase in sensor resistance at temperatures above TM can be explained by faster re-adsorption of oxygen at higher temperatures, which means more oxygen will cover the sensor surface for the same flammable gas concentration. [Shimizu]

We can also see that sensor resistance seems to decrease with at higher relative humidity. The mechanism for this is less clear. On the one hand, water vapor can adsorb onto the sensor surface, thereby decreasing the oxygen coverage and increasing sensor conductivity. On the other hand, water vapor can heal defective adsorption sites, which increases the surface area available for oxygen adsorption and therefore decreases sensor conductivity. [Wicker et al.] Additionally, recall that the CO2 sensing mechanism is highly dependent on the presence of water. Wang et al. reports a non-linear sensor response to CO2 with respect to humidity, with a response peak at 34% RH. With that in mind, the data sheet humidity dependence graph (which only has two data points and therefore must be assumed to be linear) is of limited utility when measuring CO2.

With that, we have all the pieces we need to use the MQ135 to measure CO2 concentration. Or any other of the gases in figure 2, for that matter. Enough theory, let’s take a look at how to do it in practice using the Pocket Science Lab!

Using the MQ135 with PSLab

If you have just the sensor itself, connect it as shown in figure 5, where the sensor is represented by a SparkFun gas sensor breakout board. CH1 is used to monitor the output voltage of the voltage divider between the sensor and the load resistor (which is 22K in this exaple circuit). Additionally, CH2 is used to monitor the voltage level of the PSLab’s +5V line. It is important that the voltage on the +5V line does not drop below 4.9 V, as the MQ135 will not reach its operating temperature otherwise. The MQ135 heater resistor draws up to 200 mA of current, which may be more power than the USB port powering the PSLab can supply. The PSLab does not regulate the +5V line, so it is up to the USB port to deliver enough current.

Figure 5: Guide to connect MQ135 to PSLab.
Figure 6: A MQ135 mounted on a PCB with a built-in load resistor (highlighted in red), as well as some other circuitry.

If you are using a SNS-MQ135 board, shown in figure 6, simply connect the AO pin to CH1, without the external load resistor. The SNS-MQ135 has its own load resistor, which unfortunately is only 1K. Such a small load resistor means the output voltage on AO will be very small when measuring CO2, which limits measurement accuracy. If possible, you should desolder the built-in SNS-MQ135 load resistor (outlined in red in figure 6), and use an external load resistor as in figure 5 instead. The caveat about the +5V level also applies to the SNS-MQ135.

The MQ135 must be allowed to heat to its operating temperature before it can be used. The data sheet recommends a heating period of at least 24h before use. Additionally, if the sensor hasn’t been used for a long time (or ever, if it is new), it must be allowed to burn in for even longer (>72h) in order to burn off various substances which have reacted with the surface during that time.

After the MQ135 has been allowed to heat up and burn in properly, we can use pslab-python 2.1.0+ to measure the ambient CO2 concentration.

Start by creating a multimeter to measure the voltage level on the +5V line:

>>> import pslab
>>> multimeter = pslab.Multimeter()
>>> multimeter.measure_voltage(“CH2”)

If the voltage is at least 4.9 V, the next step is to determine the sensor’s R0. It is a good idea to use an average value over a period of time (this only needs to be done once for each sensor):

>>> import time
>>> from pslab.external.gas_sensor import MQ135
>>> mq135 = MQ135(gas=”CO2”, r_load=22e3)
>>> r0 = []
>>> for i in range(3600):
>>>     r0.append(mq135.measure_r0(400))
>>>     time.sleep(1)
>>> mq135.r0 = sum(r0) / len(r0)


>>> mq135.measure_concentration()



Shimizu Y. (2014) SnO2 Gas Sensor. In: Kreysa G., Ota K., Savinell R.F. (eds) Encyclopedia of Applied Electrochemistry. Springer, New York, NY.

Wang et al. (2016) CO2-sensing properties and mechanism of nano-SnO2 thick-film sensor, Sensors and Actuators B: Chemical, Volume 227, Pages 73-84, ISSN 0925-4005,

Wicker et al. (2017) Ambient Humidity Influence on CO Detection with SnO2 Gas Sensing Materials. A Combined DRIFTS/DFT Investigation, The Journal of Physical Chemistry C, Volume 121, Number 45, Pages 25064-25073,

Continue Reading Measuring CO2 with MQ135

How to use Mobile Sensors as Instruments in PSLab Android App

This blog demonstrates how to use built-in mobile sensors in an Android application. This blog will mainly feature my work done in PSLab Android repository of making a Compass and Accelerometer instrument using built-in mobile sensors.

How to access built-in mobile sensors?

Android provides an abstract class called SensorManager which is able to communicate with the hardware i.e. here the sensors in the mobile. But the SensorManager can’t provide continuous data fetched by the sensor. For this, Android provides an interface known as SensorEventListener which receives notifications from SensorManager whenever there is a new sensor data.

How to implement the functionality of sensors in Android app?

Following is a step by step process on how to add support for different sensors in an Android app

  • First, make a new class which extends SensorEventListener and override the default methods.
public class SensorActivity extends Activity implements SensorEventListener {

     public SensorActivity() {
        // Default Constructor      

     public void onAccuracyChanged(Sensor sensor, int accuracy) {

     public void onSensorChanged(SensorEvent event) {

Here, the SensorActivity() is the default constructor of the class and the onAccuracyChanged() and onSensorChanged() methods will be explained soon.

  • Now declare the SensorManager and use the sensor needed in the app.
private final SensorManager mSensorManager;
private final Sensor mAccelerometer;

     public SensorActivity() {
         mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
         mAccelerometer =        mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

Here, I have used Sensor.TYPE_ACCELEROMETER to use the built-in Accelerometer in the device. Some of the other options available are:

  1. TYPE_LIGHT – To measure ambient light
  2. TYPE_MAGNETOMETER – To measure magnetic field along different axis
  3. TYPE_GYROSCOPE – To measure movements (sudden changes) in any particular direction

The list of all available sensors in Android can be found in [1].

  • It is necessary to disable the sensors especially when the activity is paused. Failing to do so can drain the battery in just a few hours.

NOTE: The system will not disable sensors automatically when the screen turns off.

So, to save the battery and make the app efficient, we can use the registerListener method to notify the SensorManager to start fetching data from sensor and unregisterListener to notify it to stop.

protected void onResume() {
         mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);

     protected void onPause() {

The onResume() method activates when the app is resumed from a paused state and the onPause() method is called when the app is paused i.e. some other app draws over the current app.

  • Now coming back to onAccuracyChanged() and onSensorChanged() methods, the onAccuracyChanged() method is used to set the accuracy of a sensor. For example, while using GeoLocation sensor, sometimes the position of the mobile isn’t very accurate and so we can define the accuracy level in this method so that the fetched data is used for calculations only if it is in the provided range. And the onSensorChanged() method is the main method where all the data is processed as soon as the new data is notified.

To get the latest value from the sensor, we can use

public void onSensorChanged(SensorEvent event) {
   data = Float.valueOf(event.values[0]);

Here, the event is an instance of the SensorEvent class which provides the updated data fetched from the sensor. Event.values is used to get the values for any of the three axis including the bias in their values. Following is the list of the index for which we can get a necessary value

values[0] = x_uncalib without bias compensation
values[1] = y_uncalib without bias compensation
values[2] = z_uncalib without bias compensation
values[3] = estimated x_bias
values[4] = estimated y_bias 
values[5] = estimated z_bias

So, in this way, we can add support for any built-in mobile sensor in our Android application.


Continue Reading How to use Mobile Sensors as Instruments in PSLab Android App

I2C communication in PSLab Android

PSLab device is a compact electronic device with a variety of features. One of them is the ability to integrate sensors and get readings from them. One might think that why they should use PSLab device to get sensor readings and they can use some other hardware like Arduino. In those devices, user has to create firmware and need to know how to interface them with correct sampling rates and settings. But with PSLab, they all come in the whole package. User just have to plug the device to his phone and the sensor to device and he’s ready.

The idea of this blog post is to show how this sophisticated process is actually happening. Before that, let me give you a basic introduction on how I2C communication protocol works. I2C protocol is superior to UART and SPI protocols as they are device limited or requires many wires. But with I2C, you can literally connect thousands of sensors with just 4 wires. These wires are labeled as follows;

  • VCC – Power line
  • GND – Ground line
  • SDA – Data line
  • SCL – Signal clock

It is required that the SDA and SCL lines need to be connected to VCC line using two pull up resistors. But that’s just hardware. Let’s move on to learn how I2C communicate.

Here there is this communicating concept called master and slave. To start communication, master issues a global signal like a broadcast to all the devices connected to SCL and SDA lines. This signal contains the address of the slave, master needs to address and get data from. If the slave received this call to him, he will pull down the SDA line to signal the master that he heard him and ready to communicate with him. Here communication means reading or writing data. Then the communication happens and the link between master and slave breaks giving opportunity to other masters and slaves.

One might think this is a slow process. But these signals are transmitted at high frequencies. In PSLab it is at 100 kHz and that is one millisecond.

PSLab library has a special class to handle I2C communication. That is

public class I2C {/**/}


Once this class is initiated, one has to call the start function to start communication. This method requires the address we wish to communicate with and the mode of operation stating if it is a read or write operation

public int start(int address, int rw) throws IOException {
   packetHandler.sendByte((address << 1) | rw & 0xff);
   return (packetHandler.getAcknowledgement() >> 4);


Once the address is sent out, protocol requires us to stop and wait for acknowledgement.

public void wait() throws IOException {


If there are no congestion in the lines such as reading from multiple devices, the acknowledgement will be instantaneous. Once that is complete, we can start communication either byte-wise or bulk-wise

public int send(int data) throws IOException {
   return (packetHandler.getAcknowledgement() >> 4);


As an example, reading sensor values at a given interval can be done using the following method call.

public ArrayList<Byte> read(int length) throws IOException {
   ArrayList<Byte> data = new ArrayList<>();
   for (int i = 0; i < length - 1; i++) {
   return data;


Once we get the data bundle, either we can save them or display in a graph whatever the way it’s convenient.



Continue Reading I2C communication in PSLab Android

Saving Sensor Data in CSV format

PSLab Android app by FOSSASIA provides a variety of features to its users. One of them is accessing various types of sensors both built into mobile phone and external sensors connected with PSLab device. In earlier versions users were only able to view the captured data. Moving forward, adding improvements to the app, now there is a feature to save those data displayed in graphs in csv format.

This feature is important in many ways. One is educational. In a classroom, teachers can ask students to perform an experiment and prepare a report using the data collected. By just visualizing they cannot do this. Actual data points must be made available. Another use is sharing data sets related to say environmental data over different demographics.

CSV, or comma-separated values file is a text file where stored data are separated by commas. The file stores these tabular data (numbers and text) in plain text format. Each line of the file represents a data record. Each data record consists of one or more fields, separated by commas. CSV files are commonly used to store sensor data because of its easy use. This post is about how PSLab device uses CSV file to write sensor data in it.

In PSLab android source code, there is a dedicated class to handle read sensor data from different instruments called “CSVLogger”. Developers can easily instantiate this class wherever they want a data logging as follows;

CSVLogger logger = new CSVLogger(<SUBFOLDER>); 
logger .writeCSVFile("Heading1,Heading2,Heading3\n");

This will create a blank folder in “PSLab” folder in device storage.  The CSV file is generated with the following convention according to the date and time where data is saved in the file.


A sample file would have a name like 20180710-07:30:28.csv inside the SUBFOLDER which is specific to each instrument. Folder name will be the one used when initiating the CSVLogger.

With this method, logging data is pretty easy. Simply create a string which is a comma seperated and ended with a new line character. Then simply call the writeCSVFile(data) method with the string as a parameter added to it. It will keep appending string data until a new file is created. File creation can be handled by developers at their own interests and preferences.

String data = String.valueOf(System.currentTimeMillis()) + "," + item.getX() + "," + item.getY() + "\n";


To bring out an example let’s view how it’s implemented in Lux Meter instrument. This is a good source one can refer to when adding this feature in fragments

inside a main activity. In Lux Meter, there is the parent activity named Lux Meter and inside that there are two fragments, one is fragmentdata and the other one is fragmentsettings. Data capturing and saving occurs inside fragmentdata.

Menu icon controlling happens in the parent activity and we have bound a variable across the main activity and child fragment as follows;

LuxMeterActivity parent = (LuxMeterActivity) getActivity();
if (parent.saveData) {/* Save Data */}

This makes it easier listening menu icon clicks and start/stop recording accordingly. How to handle menu icons is beyond the scope of this blog and you can find tutorials on how to do that in the Resources section at the bottom of this blog post.

Once these CSV files are available, users can easily integrate them with advanced software like Matlab or Octave to do further analysis and processing to captured data sets.


  1. CSV Logger:
  2. Android Menu options:

Continue Reading Saving Sensor Data in CSV format

Performing Custom Experiments with PSLab

PSLab has the capability to perform a variety of experiments. The PSLab Android App and the PSLab Desktop App have built-in support for about 70 experiments. The experiments range from variety of trivial ones which are for school level to complicated ones which are meant for college students. However, it is nearly impossible to support a vast variety of experiments that can be performed using simple electronic circuits.

So, the blog intends to show how PSLab can be efficiently used for performing experiments which are otherwise not a part of the built-in experiments of PSLab. PSLab might have some limitations on its hardware, however in almost all types of experiments, it proves to be good enough.

  • Identifying the requirements for experiments

    • The user needs to identify the tools which are necessary for analysing the circuit in a given experiment. Oscilloscope would be essential for most experiments. The voltage & current sources might be useful if the circuit requires DC sources and similarly, the waveform generator would be essential if AC sources are needed. If the circuit involves the use and analysis of data of sensor, the sensor analysis tools might prove to be essential.
    • The circuit diagram of any given experiment gives a good idea of the requirements. In case, if the requirements are not satisfied due to the limitations of PSLab, then the user can try out alternate external features.
  • Using the features of PSLab

  • Using the oscilloscope
    • Oscilloscope can be used to visualise the voltage. The PSLab board has 3 channels marked CH1, CH2 and CH3. When connected to any point in the circuit, the voltages are displayed in the oscilloscope with respect to the corresponding channels.
    • The MIC channel can be if the input is taken from a microphone. It is necessary to connect the GND of the channels to the common ground of the circuit otherwise some unnecessary voltage might be added to the channels.

  • Using the voltage/current source
    • The voltage and current sources on board can be used for requirements within the range of +5V. The sources are named PV1, PV2, PV3 and PCS with V1, V2 and V3 standing for voltage sources and CS for current source. Each of the sources have their own dedicated ranges.
    • While using the sources, keep in mind that the power drawn from the PSLab board should be quite less than the power drawn by the board from the USB bus.
      • USB 3.0 – 4.5W roughly
      • USB 2.0 – 2.5W roughly
      • Micro USB (in phones) – 2W roughly
    • PSLab board draws a current of 140 mA when no other components are connected. So, it is advisable to limit the current drawn to less than 200 mA to ensure the safety of the device.
    • It is better to do a rough calculation of the power requirements in mind before utilising the sources otherwise attempting to draw excess power will damage the device.

  • Using the Waveform Generator
    • The waveform generator in PSLab is limited to 5 – 5000 Hz. This range is usually sufficient for most experiments. If the requirements are beyond this range, it is better to use an external function generator.
    • Both sine and square waves can be produced using the device. In addition, there is a feature to set the duty cycle in case of square waves.
  • Sensor Quick View and Sensor Data Logger
    • PSLab comes with the built in support for several plug and play sensors. The support for more sensors will be added in the future. If an experiment requires real time visualisation of sensor data, the Sensor Quick View option can be used whereas for recording the data for sensors for a period of time, the Sensor Data Logger can be used.
  • Analysing the Experiment

    • The oscilloscope is the most common tool for circuit analysis. The oscilloscope can sample data at very high frequencies (~250 kHz). The waveform at any point can be observed by connecting the channels of the oscilloscope in the manner mentioned above.
    • The oscilloscope has some features which will be essential like Trigger to stabilise the waveforms, XY Plot to plot characteristics graph of some devices, Fourier Transform of the Waveforms etc. The tools mentioned here are simple but highly useful.
    • For analysing the sensor data, the Sensor Quick View can be paused at any instant to get the data at any instant. Also, the logged data in Sensor Data Logger can be exported as a TXT/CSV file to keep a record of the data.
  • Additional Insight

    • The PSLab desktop app comes with the built-in support for the ipython console.
    • The desired quantities like voltages, currents, resistance, capacitance etc. can also be measured by using simple python commands through the ipython console.
    • A simple python script can be written to satisfy all the data requirements for the experiment. An example for the same is shown below.

This is script to produce two sine waves of 1 kHz and capturing & plotting the data.

from pylab import *
from PSL import sciencelab
I.set_gain('CH1', 2) # set input CH1 to +/-4V range
I.set_gain('CH2', 3) # set input CH2 to +/-4V range
I.set_sine1(1000) # generate 1kHz sine wave on output W1
I.set_sine2(1000) # generate 1kHz sine wave on output W2
#Connect W1 to CH1, and W2 to CH2. W1 can be attenuated using the manual amplitude knob on the PSlab
x,y1,y2 = I.capture2(1600,1.75,'CH1') 
plot(x,y1) #Plot of analog input CH1
plot(x,y2) #plot of analog input CH2



Continue Reading Performing Custom Experiments with PSLab

Export Sensor Data from the PSLab Android App

The PSLab Android App allows users to log data from the sensors connected to the PSLab hardware device. Sensor Data is stored locally but can be exported in various formats. Currently the app supports exporting data in .txt and .csv (comma-separated values) format. Exported data can be used by other users or scientists to study or analyze the data. Data can also be used by other softwares like Python, GNU octave, Matlab to further process it or visualise it in 3D. In this post, we will discuss how to export the locally stored realm data in .txt or .csv format. We will take the data of MPU6050 sensor as an example for understanding how locally logged data is exported.

Query Local Realm Data

We have attached a long click listener to sensor list view that detects which list item is selected. Clicking any sensor from sensor list for slightly longer than usual would result in a dialog popping up with the option to

  • Export Data: Results in exporting data in a format which is selected in App settings
  • Share Data: Shares sensor data with other users or on social media (yet to be implemented)

Source: PSLab Android App

As soon as the Export Data option is selected from the dialog, sensor data of the corresponding sensor is queried. The data model of the sensor and how it’s saved in the local realm database is discussed in the post Sensor Data Logging in the PSLab Android App.

RealmResults<DataMPU6050> results = realm.where(DataMPU6050.class).findAll();

Once we get the required data, we need to write it in .txt or .csv format depending on what the user has selected as a preference in App Settings.

Getting User Preference from App Settings

The format in which the sensor data should be exported is presented to the user as a preference in App Settings. Currently the app supports two formats .txt and .csv.

Source: PSLab Android App

private String format;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String formatValue = preferences.getString("export_data_format_list", "0");
if ("0".equals(formatValue))
   format = "txt";
   format = "csv";

Export Data in .txt Format

To export the sensor data in .txt format, we need to create a .txt file in the external storage. folder variable is a path to PSLab Android folder in the external storage. If the folder doesn’t exist, it will be created.

File folder = new File(Environment.getExternalStorageDirectory() + File.separator + "PSLab Android");

After getting reference of the app folder in the external storage, we would create a text file in the PSLab Android folder. As soon as the text file is created, we initialize the FileOutputStream object to write data into the text file. The sensor data that was queried in the previous section is written into the text file just created. Finally after the complete sensor data is written, the stream is closed by stream.close() method.

FileOutputStream stream = null;
File file = new File(folder, "sensorData.txt");
try {
   stream = new FileOutputStream(file);
   for (DataMPU6050 temp : results) {
       stream.write((String.valueOf(temp.getAx()) + " " + temp.getAy() + " " + temp.getAz() + " " +
               temp.getGx() + " " + temp.getGy() + " " + temp.getGz() + " " + temp.getTemperature() + "\n").getBytes());
} catch (IOException e) {
} finally {
   try {
       if (stream != null) {
   } catch (IOException e) {

Export Data in .csv Format

Writing data in .csv format is similar to that in .txt format. As CSV stands for Comma Separated Values, which means each data value is separated by “,” (comma). It is similar to an excel sheet. The first row consists of labels that denote the type of value in that particular column. The other rows consist of the sensor data, with each row corresponding to a sample of the sensor data.

File file = new File(folder, "sensorData.csv");
PrintWriter writer;
try {
   writer = new PrintWriter(file);
   StringBuilder stringBuilder = new StringBuilder();
   for (DataMPU6050 temp : results) {
} catch (FileNotFoundException e) {


Continue Reading Export Sensor Data from the PSLab Android App

Sensor Data Logging in the PSLab Android App

The PSLab Android App allows users to log data from sensors connected to the PSLab hardware device. The Connected sensors should support I2C, SPI communication protocols to communicate with the PSLab device successfully. The only prerequisite is the additional support for the particular sensor plugin in Android App. The user can log data from various sensors and measure parameters like temperature, humidity, acceleration, magnetic field, etc. These parameters are useful in predicting and monitoring the environment and in performing many experiments.

The support for the sensor plugins was added during the porting python communication library code to Java. In this post,  we will discuss how we logged real time sensor data from the PSLab Hardware Device. We used Realm database to store the sensor data locally. We have taken the MPU6050 sensor as an example to understand the complete process of logging sensor data.

Creating Realm Object for MPU6050 Sensor Data

The MPU6050 sensor gives the acceleration and gyroscope readings along the three axes X, Y and Z. So the data object storing the readings of the mpu sensor have variables to store the acceleration and gyroscope readings along all three axes.

public class DataMPU6050 extends RealmObject {

   private double ax, ay, az;
   private double gx, gy, gz;
   private double temperature;

   public DataMPU6050() {  }

   public DataMPU6050(double ax, double ay, double az, double gx, double gy, double gz, double temperature) { = ax;
       this.ay = ay; = az;
       this.gx = gx; = gy;
       this.gz = gz;
       this.temperature = temperature;

  // getter and setter for all variables

Creating Runnable to Start/Stop Data Logging

To sample the sensor data at 500ms interval, we created a runnable object and passed it to another thread which would prevent lagging of the UI thread. We can start/stop logging by changing the value of the boolean loggingThreadRunning on button click. TaskMPU6050 is an AsyncTask which reads each sample of sensor data from the PSLab device, it gets executed inside a while loop which is controlled by boolean loggingThreadRunning. Thread.sleep(500) pauses the thread for 500ms, this is also one of the reason to transfer the logging to another thread instead of logging the sensor data in UI thread. If such 500ms delays are incorporated in UI thread, app experience won’t be smooth for the users.

Runnable loggingRunnable = new Runnable() {
   public void run() {
       try {
           MPU6050 sensorMPU6050 = new MPU6050(i2c);
           while (loggingThreadRunning) {
               TaskMPU6050 taskMPU6050 = new TaskMPU6050(sensorMPU6050);
              // use lock object to synchronize threads
       } catch (IOException   InterruptedException e) {

Sampling of Sensor Data

We created an AsyncTask to read each sample of the sensor data from the PSLab device in the background thread. The getRaw() method read raw values from the sensor and returned an ArrayList containing the acceleration and gyro values. After the values were read successfully, they were updated in the data card in the foreground which was visible to the user. This data card acts as a real-time screen for the user. All the samples read are appended to ArrayList mpu6050DataList, when the user clicks on button Save Data, the collected samples are saved to the local realm database.

private ArrayList<DataMPU6050> mpu6050DataList = new ArrayList<>();

private class TaskMPU6050 extends AsyncTask<Void, Void, Void> {

   private MPU6050 sensorMPU6050;
   private ArrayList<Double> dataMPU6050 = new ArrayList<>();

   TaskMPU6050(MPU6050 mpu6050) {
       this.sensorMPU6050 = mpu6050;

   protected Void doInBackground(Void... params) {
       try {
           dataMPU6050 = sensorMPU6050.getRaw();
       } catch (IOException e) {
       return null;

   protected void onPostExecute(Void aVoid) {
       // update data card TextViews with data read.
       DataMPU6050 tempObject = new DataMPU6050(dataMPU6050.get(0), dataMPU6050.get(1), dataMPU6050.get(2),
               dataMPU6050.get(4), dataMPU6050.get(5), dataMPU6050.get(6), dataMPU6050.get(3));
       synchronized (lock) {

Source: PSLab Android App

There is an option for Start/Stop Logging, clicking on which will change the value of boolean loggingThreadRunning which stops starts/stops the logging thread.

When the Save Data button is clicked, all the samples of sensor data collected from the  PSLab device till that point are saved to the local realm database.

for (DataMPU6050 tempObject : mpu6050DataList) {

Data can also be written asynchronously to the local realm database. For other methods to write to a real database refer write section of Realm docs.


Continue Reading Sensor Data Logging in the PSLab Android App

Generating Real-Time Graphs in PSLab Android App

In PSLab Android App, we need to log data from the sensors and correspondingly generate real-time graphs. Real-time graphs mean a data streaming chart that automatically updates itself after every n second. This was different from what we did in Oscilloscope’s graph, here we need to determine the relative time at which the data is recorded from the sensor by the PSLab.

Another thing we need to take care of was the range of x axis. Since the data to be streamed is ever growing, setting a large range of the x axis will only make reading sensor data tedious for the user. For this, the solution was to make real time rolling window graph. It’s like when the graph exceeds the maximum range of x axis, the graph doesn’t show the initial plots. For example, if I set that graph should show the data only for the 10-second window when the 11th-second data would be plot, the 1st-second data won’t be shown by the graph and maintains the difference between the maximum and the minimum range of the graph. The graph library we are going to use is MPAndroidChart. Let’s break-down the implementation step by step.

First, we create a long variable, startTime which records the time at which the entire process starts. This would be the reference time. Flags make sure when to reset this time.

if (flag == 0) {
   startTime = System.currentTimeMillis();
   flag = 1;


We used Async Tasks approach in which the data is from the sensors is acquired in the background thread and the graph is updated in the UI thread. Here we consider an example of the HMC5883L sensor, which is actually Magnetometer. We are calculating time elapsed by subtracting current time with the sartTime and the result is taken as the x coordinate.

private class SensorDataFetch extends AsyncTask<Void, Void, Void> {
   ArrayList<Double> dataHMC5883L = new ArrayList<Double>();
   long timeElapsed;

   protected Void doInBackground(Void... params) {
     timeElapsed = (System.currentTimeMillis() - startTime) / 1000;

     entriesbx.add(new Entry((float) timeElapsed, dataHMC5883L.get(0).floatValue()));
     entriesby.add(new Entry((float) timeElapsed, dataHMC5883L.get(1).floatValue()));
     entriesbz.add(new Entry((float) timeElapsed, dataHMC5883L.get(2).floatValue()));
     return null;


As we need to create a rolling window graph we require to add few lines of code with the standard implementation of the graph using MPAndroidChart. This entire code is placed under onPostExecute method of AsyncTasks. The following code sets data set for the Line Chart and tells the Line Chart that a new data is acquired. It’s very important to call notifyDataSetChanged, without this the things won’t work.



Now, we will set the visible range of x axis. This means that the graph window of the graph won’t change until and unless the range set by this method is not achieved. Here we are setting it to be 10 as we need a 10-second window.


Then we will call moveViewToX method to move the view to the latest entry of the graph. Here, we have passed data.getEntryCount method which returns the no. of data points in the data set.



We will get following results

To see the entire code visit this link.


Continue Reading Generating Real-Time Graphs in PSLab Android App

Integrating Stock Sensors with PSLab Android App

A sensor is a digital device (almost all the time an integrated circuit) which can receive data from outer environment and produce an electric signal proportional to that. This signal will be then processed by a microcontroller or a processor to provide useful functionalities. A mobile device running Android operating system usually has a few sensors built into it. The main purpose of these sensors is to provide user with better experience such as rotating the screen as he moves the device or turn off the screen when he is making a call to prevent unwanted screen touch events. PSLab Android application is capable of processing inputs received by different sensors plugged into it using the PSLab device and produce useful results. Developers are currently planning on integrating the stock sensors with the PSLab device so that the application can be used without the PSLab device.

This blog is about how to initiate a stock sensor available in the Android device and get readings from it. Sensor API provided by Google developers is really helpful in achieving this task. The process is consist of several steps. It is also important to note the fact that there are devices that support only a few sensors while some devices will support a lot of sensors. There are few basic sensors that are available in every device such as

  • “Accelerometer” – Measures acceleration along X, Y and Z axis
  • “Gyroscope” – Measures device rotation along X, Y and Z axis
  • “Light Sensor” – Measures illumination in Lux
  • “Proximity Sensor” – Measures distance to an obstacle from sensor

The implementing steps are as follows;

  1. Check availability of sensors

First step is to invoke the SensorManager from Android system services. This class has a method to list all the available sensors in the device.

SensorManager sensorManager;
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

Once the list is populated, we can iterate through this to find out if the required sensors are available and obstruct displaying activities related to sensors that are not supported by the device.

for (Sensor sensor : sensors) {
   switch (sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
       case Sensor.TYPE_GYROSCOPE:

  1. Read data from sensors

To read data sent from the sensor, one should implement the SensorEventListener interface. Under this interface, there are two method needs to be overridden.

public class StockSensors extends AppCompatActivity implements SensorEventListener {

    public void onSensorChanged(SensorEvent sensorEvent) {


    public void onAccuracyChanged(Sensor sensor, int i) {


Out of these two methods, onSensorChanged() method should be addressed. This method provides a parameter SensorEvent which supports a method call getType() which returns an integer value representing the type of sensor produced the event.

public void onSensorChanged(SensorEvent sensorEvent) {
   switch (sensorEvent.sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
       case Sensor.TYPE_GYROSCOPE:

Each available sensor should be registered under the SensorEventListener to make them available in onSensorChanged() method. The following code block illustrates how to modify the previous code to register each sensor easily with the listener.

for (Sensor sensor : sensors) {
   switch (sensor.getType()) {
       case Sensor.TYPE_ACCELEROMETER:
           sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
       case Sensor.TYPE_GYROSCOPE:
           sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_UI);

Depending on the readings we can provide user with numerical data or graphical data using graphs plotted using MPAndroidChart in PSLab Android application.

The following images illustrate how a similar implementation is available in Science Journal application developed by Google.


Continue Reading Integrating Stock Sensors with PSLab Android App

Creating Sensor Libraries in PSLab

The I2C bus of the PSLab enables access to a whole range of sensors capable of measuring parameters ranging from light intensity, humidity, and temperature, to acceleration, passive infrared, and magnetism.

Support for each sensor in the communication library is implemented as a small Python library that depends in the I2C communication module for PSLab.

However, most sensors have capabilities that are not just limited to data readouts, but also enable various configuration options.

This blog post explains how a common format followed across the sensor libraries enables the graphical utilities such as data loggers and control panels to dynamically create widgets pertaining to the various configuration options.

The following variables and methods must be present in each sensor file in order to enable the graphical utilities to correctly function:

Name: A generic name for the sensor to be shown in menus and such. e.g. ‘Altimeter BMP180’

GetRaw(): A function that returns a list of values read from the sensor. The values may have been directly read from the sensor, or derived based on some parameters/equations.

For example, the BMP180 altitude sensor is actually a pressure and temperature sensor. The altitude value is derived from these two using an equation. Therefore, the getRaw function for the BMP180 returns a list of three values, viz, [temperature, pressure, altitude]

NUMPLOTS: A constant that stores the number of available dataPoints in the list returned by the getRaw function. This enables the graphical utilities to create required number of traces . In case of the BMP180, it is 3

PLOTNAMES: A list of text strings to be displayed in the plot legend . For the BMP180, the following list is defined : [‘Temperature’, ‘Pressure’, ‘Altitude’]

params: A dictionary that stores the function names for configuring various features of the sensor, and options that can be passed to the function. For example, for the BMP180 altimeter, and oversampling parameter is available, and can take values 0,1,2 and 3 . Therefore, params = {‘setOversampling’: [0, 1, 2, 3]}

The Sensor data Logger application uses this dictionary to auto-generate menus with the ‘key’ as the name , and corresponding ‘values’ as a submenu . When the user opens a menu and clicks on a ‘value’ , the ‘value’ is passed to a function whose name is the corresponding key , and which must be defined in the sensor’s class.

When the above are defined, menus and plots are automatically generated, and saves considerable time and effort for graphical utility development since no sensor specific code needs to be added on that front.

The following Params dictionary defined in the class of MPU6050 creates a menu as shown in the second image:

self.params = { 'powerUp':['Go'],

As shown in the image , when the user clicks on ‘8’ , MPU6050.setAccelRange(8) is executed.

Improving the flexibility of the auto-generated menus

The above approach is a little limited, since only a fixed set of values can be used for configuration options, and there may be cases where a flexible input is required.

This is the case with the Kalman filter option, where the user may want to provide the intensity of the filter as a decimal value. Therefore we shall implement a more flexible route for the params dictionary, and allow the value to certains keys to be objects other than lists.

Functions with user defined variable inputs are defined as Spinbox/QDoubleSpinBox.

KalmanFilter is defined in the following entry in Params:

‘KalmanFilter’:{‘dataType’:’double’,’min’:0,’max’:1000,’prefix’:’value: ‘}

Screenshot of the improved UI with MPU6050.

In this instance, the user can input a custom value, and the KalmanFilter function is called with the same.

Additional Reading:

[1]: Using sensors with PSLab Android

[2]: Analyzing sensor data with PSLab android
[3]: YouTube video to understand analysis of data from MPU6050 with Arduino –

Continue Reading Creating Sensor Libraries in PSLab