Measuring CO2 with MQ135

Introduction

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 co2.earth 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”)
4.961014

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)

Finally:

>>> mq135.measure_concentration()
418.067514310052683

Done!

References

Shimizu Y. (2014) SnO2 Gas Sensor. In: Kreysa G., Ota K., Savinell R.F. (eds) Encyclopedia of Applied Electrochemistry. Springer, New York, NY. https://doi.org/10.1007/978-1-4419-6996-5_475

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, https://doi.org/10.1016/j.snb.2015.12.025

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, https://doi.org/10.1021/acs.jpcc.7b06253

Continue ReadingMeasuring CO2 with MQ135

pslab-python 2.0 Release Notes

pslab-python 2.0 is now available! Get it from PyPI or Github.

Large parts of pslab-python have been rewritten to improve user friendliness. pslab-python version 1.0 was written primarily as a back end for pslab-desktop. While it could be used as a standalone library, it was not designed with that use case foremost in mind.

pslab-python 2.0 still serves as the back end for pslab-desktop, but it has been rewritten to be more user-friendly when being used as a standalone library.

Also worth mentioning is pslab-python’s new test suite. Although tests are not immediately useful for users, they help developers avoid accidentally introducing regressions, which in turn makes pslab-python more pleasant to use.

The rewrite touches almost every part of pslab-python and is too extensive to go into detail, so here are the most important user-facing improvements:

Highlights

General

  • Package renamed from PSL to pslab.
  • Simplified API.
  • Improved documentation now available on readthedocs.
  • pslab-python now has a command line interface. Type pslab --help in a terminal for more information.
  • pslab-python can now be installed from PyPI: pip install pslab
  • Reworked exception handling. Exceptions are now raised as soon as possible instead of being suppressed and allowed to propagate.
  • Many, many bug fixes.

Oscilloscope

  • Oscilloscope-related functionality can now be found in the Oscilloscope class.
  • The many different capture methods (capture1, capture2, capture4, capture_multiple, capture_fullspeed, capture_fullspeed_hr, capture_traces, capture_highres_traces) have been combined into a single capture method.
  • Trigger settings can optionally be configured directly in the capture call, without the need to call configure_trigger separately.

Logic Analyzer

  • Logic analyzer-related functionality can now be found in the LogicAnalyzer class.
  • The many different capture methods (capture_edges1, start_one_channel_LA, start_two_channel_LA, start_three_channel_LA, start_four_channel_LA, start_one_channel_LA_backup__) have been combined into a single capture method.
  • The type of logic event to capture is now given as a string instead of a number (e.g. “falling” instead of 2 to capture only falling edges).

Multimeter

  • Multimeter-related functionality can now be found in the Multimeter class.
  • Several different methods for measuring capacitance (capacitance_via_RC_discharge, get_capacitor_range, get_capacitance) have been combined into measure_capacitance.
  • New calibrate_capacitance method added, which allows for measurement of very small (single picofarad) capacitors.

Waveform Generator

  • Analog waveform-related functionality can now be found in the WaveformGenerator class.
  • The many different methods for generating waveforms (set_wave, set_sine1, set_sine2, set_w1, set_w2, set_waves) have been combined into a single generate method.
  • Waveform amplitude is now set directly as part of a loaded function.

PWM Generator

  • PWM-related functionality can now be found in the PWMGenerator class.
  • Several different methods for generator PWM signals (sqr1, sqr1_pattern, sqr2, set_sqrs, sqrPWM) have been combined into a single generate method.

Power Supply

  • Power supply-related functionality can now be found in the PowerSupply class.
  • Voltage and current are now set via properties, e.g. to set the voltage on pin PV1 to 3 V, write power_supply.pv1 = 3.
  • The load resistance limitation on PCS is now clearly documented.

I2C

  • The I2C class has been split into the I2CMaster and I2CSlave classes.
  • Primitive I2C calls (start, stop, wait, restart, etc.) are no longer available. Instead, use read and write which take care of I2C session handling under the hood.

Known issues

  • Not every part of pslab-python has been rewritten. Specifically, the external subpackage (previously known as SENSORS) contains many sensor classes which remain untested.
Continue Readingpslab-python 2.0 Release Notes

Creating Activity for Visualizing Recorded Sensor Data from List Items

In previous blog Using RealmRecyclerView Adapter to Show Recorded Sensor Experiments[2], I  have created a DataLoggerActivity in PSLab Android app containing RecyclerView showing a list having all the recorded experiments where every list item shows the date, time and the sensor instrument used for recording the data, but there arises below questions:-

  • What if the user wants to see the actual recorded data in form of a graph?
  • How the user can see the location recorded along with the data on the map?
  • How can the user export that data?

There is no way I could show all of that information just on a list item so I created another activity called “SensorGraphViewActivity” the layout of that activity is shown in the figure below:

Figure 1 shows the layout of the Activity as produced in Android editor

The layout contains three views:-

  1. At the top there is graph view which I created using Android MP chart which will show the recorded data plotted on it forming the exact same curve that was created while recording it, this way it is useful to visualize the data and also there is also a play button on the top which simulates the data as it was being plotted on the graph in real time.
  2. In the middle, there is a Card view containing two rows which will simply show the date and time of recording.
  3. At the bottom, there is a Map view which shows the location of the user which would be fetched when the user recorded the data.

This is the gist of the layout file created for the activity.

But now the question arises:-

How to show the data in the activity for the item that the user wanted?

For that, I implemented click listener on every list item by simply adding it inside the onBindViewHolder() method

@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
   SensorLogged temp = getItem(position);
   holder.sensor.setText(temp.getSensor());
   Date date = new Date(temp.getDateTimeStart());
   holder.dateTime.setText(String.valueOf(sdf.format(date)));
   holder.cardView.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
                    ...
       });
}

and inside the click listener I performed following three steps:-

  1. First I stored the position of the item clicked inside a variable.

    int positionVar = holder.getAdapterPosition();
  2. Then I used that position from the variable to fetch related data from the Realm database by simply using getItem() method which returns the SensorLogged[1] RealmObject at that position as I used a special type of RecyclerView Adapter called as RealmRecyclerViewAdapter[2].

    int positionVar = holder.getAdapterPosition();
  3. Then I created an Intent to open the SensorGraphViewActivity and passed the related data (i.e., sensortype, foreignkey, latitude, longitude, timezone, date, time) from SensorLogged[1] object to activity in form of extras.
    Intent intent = new Intent(context, SensorGraphViewActivity.class);
    intent.putExtra(SensorGraphViewActivity.TYPE_SENSOR, item.getSensor());
    intent.putExtra(SensorGraphViewActivity.DATA_FOREIGN_KEY, item.getUniqueRef());
    intent.putExtra(SensorGraphViewActivity.DATE_TIME_START,item.getDateTimeStart());
    intent.putExtra(SensorGraphViewActivity.DATE_TIME_END,item.getDateTimeEnd());
    intent.putExtra(SensorGraphViewActivity.TIME_ZONE,item.getTimeZone());
    intent.putExtra(SensorGraphViewActivity.LATITUDE,item.getLatitude());
    intent.putExtra(SensorGraphViewActivity.LONGITUDE,item.getLongitude());
    
    context.startActivity(intent);
    

And, in the SensorGraphViewActivity, I used getIntent() method to fetch all those extra data in the form of Bundle.
For showing the data in the graph I used the foreign key fetched from the intent and queried all the LuxData[1] RealmObject containing that foreignkey in the form of RealmResult<LuxData>[2] ArrayList and used that list to populate the graph.

Long foreignKey = intent.getLongExtra(DATA_FOREIGN_KEY, -1);
Realm realm = Realm.getDefaultInstance();
entries = new ArrayList<>();
RealmResults<LuxData> results = realm.where(LuxData.class).equalTo(DATA_FOREIGN_KEY, foreignKey).findAll();
for (LuxData item : results) {
   entries.add(new Entry((float) item.getTimeElapsed(), item.getLux()));
}

For the map, I fetched the latitude and longitude again from the intent and used the coordinates to show the location on the open street view map.

Thread thread = new Thread(new Runnable() {
   @Override
   public void run() {
       IMapController mapController = map.getController();
       mapController.setZoom((double) 9);
       GeoPoint startPoint = new GeoPoint(latitude, latitude);
       mapController.setCenter(startPoint);
   }
});

For map purposes, of course, I used a separate thread as it is a heavy and time-consuming process and it could lead the app to lag for a long time which could hamper the User Experience.

Thus after the data being plotted on the map and coordinated being plotted on the map, we can see the layout of the activity as shown in Figure 2.

Figure 2 shows the layout of the activity after being populated with data.

I also created the export button in the toolbar that will use the CSVLogger[3] class implemented inside the PSLab android app to export the data in the form of CSV file and save it in the external storage directory.

Resources

  1. Storing Recorded Sensor Data in Realm Database – My blog where I created the Realm Model classes to store recorded data.
  2. Using RealmRecyclerView Adapter to Show Recorded Sensor Experiments – My previous blog where I created the RecyclerView.
  3. Saving Sensor Data in CSV format – Blog by Padmal storing the data in CSV format.

Continue ReadingCreating Activity for Visualizing Recorded Sensor Data from List Items

How to pass data between fragments of an Activity in Android app

This blog demonstrates how to pass values of a variable between two fragments of a single activity. The blog will mainly include the demonstration of passing values between fragments while using BottomSheet Navigation as done in PSLab Android application.

This blog contains the work done by me in the Lux Meter instrument of the PSLab Android app of passing data from LuxMeterConfiguration fragment to LuxMeterData fragment as shown in the featured image to set the high limit for the pointer and to set the update period of the Lux Sensor. The blog will solve the difficult task of communication between two fragments of a single activity. For passing data between multiple fragments of different activities, refer to [1].

How to pass data between fragments?

In this blog, I will pass data from Fragment 2 to Fragment 1 only. But vice versa or passing data from both the fragments can also be made using the same given approach.

  • First, make a static method in Fragment 1 which can set the parameters i.e. the value of the variables as soon as the fragment is inflated as follow
public static void setParameters(int one, int two, int three) {
        Fragment1.firstValue = one;
        Fragment1.secondValue = two;
        Fragment1.thirdValue = three;
    }
  • Now, there is one point to mark that Fragment 1 will be inflated only when Fragment 2 gets destroyed. Else, other than default inflation of Fragment 1, there is no way Fragment 1 can be inflated after navigating to Fragment 2.
  • So, override the OnDestroy() method of Fragment 2 and use the setParameters() method to set the value of variables from Fragment 2 to be used in Fragment 1.
@Override
    public void onDestroyView() {
        super.onDestroyView();
        highValue = getValueFromText(highLimit, 0, highLimitMax);
        updatePeriodValue = getValueFromText(updatePeriod, updatePeriodMin, updatePeriodMax + 100);
        Fragment1.setParameters(selectedSensor, highValue, updatePeriodValue);
    }

Here, the highValue, updatePeriodValue and selectedSensor are the variables being used in the Lux Meter fragment in PSLab Android app. But they can be replaced by the necessary variables as per the app.

So, in this way, we can pass data between the fragments of the same Activity in an Android application. Above demonstration can be extended in passing values between multiple fragments of the same Activity by creating different methods in different fragments.

Resources

  1. Blog on how to pass data between fragments of different/same activities: https://www.journaldev.com/14207/android-passing-data-between-fragments

Continue ReadingHow to pass data between fragments of an Activity in Android app

Prevent Android Activity from Operating while using Bottom Sheet in PSLab App

This blog demonstrates how to prevent the Android Activity in the background from operating while the Bottom Sheet is up in the foreground. The demonstration will be purely from the work I have done under PR #1355 in PSLab Android repository.

Why prevent the Activity from operating?

When using Bottom Sheet in Android, it is preferable to dim the screen behind the Bottom Sheet to provide a good user experience. But the dimming of the screen is itself an indication that the screen won’t work. Also, if the Bottom Sheet is open and while sliding it, if, by mistake, any button in the background of the bottom sheet gets pressed, then if the function related to that button starts executing then it can create a bad user experience.

For example, in PSLab Android app, in Accelerometer instrument, there are record/pause and delete buttons in the toolbar as shown in figure 1. Now, if the bottom sheet is opened and while closing it if the delete button is by mistake pressed by the user, then whole recorded data gets deleted. Thus, it’s a good practice to prevent the background Activity from operating while Bottom Sheet is opened.

Figure 1. Accelerometer Instrument in PSLab Android app

How to prevent the Activity from operating?

In this demonstration, I will use the method followed by PSLab Android app in creating a Bottom Sheet and making the background dim using a View widget. A step by step guide on how to make a Bottom Sheet as in PSLab Android app can be found in [1] and [2].

Strategy

The strategy used in solving this problem is setting an OnClickListener to the View that is used to dim the background and close the Bottom Sheet (if open) and hide the View as soon as the method is called. The View is again made visible when an upward slide gesture is made to open the Bottom Sheet.

Follow the below steps to get the desired results:

  • First, in OnCreate() method, set the OnTouchListener to the view.
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                              if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED)
                    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
tvShadow.setVisibility(View.GONE);
      }
});
  • Now, override the OnSlide() method of the GestureDetector class and add the following code to it.
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
    Float value = (float) MathUtils.map((double) slideOffset, 0.0, 1.0, 0.0, 0.8);
    view.setVisibility(View.VISIBLE);
    view.setAlpha(value);
   }

So, now test the Bottom Sheet and you will find that the Bottom Sheet will get closed as soon as the click is made outside it if it is opened. The demonstration of the working of the above code is shown in figure 2.

Figure 2. Demonstration of preventing the background Activity from operating while Bottom Sheet is up

Resources

  1. http://thetechnocafe.com/make-bottom-sheet-android/: Blog on how to make a Bottom Sheet in Android

Continue ReadingPrevent Android Activity from Operating while using Bottom Sheet in PSLab App

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      
     }

     @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }

     @Override
     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.

@Override
protected void onResume() {
         super.onResume();
         mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
     }

@Override
     protected void onPause() {
         super.onPause();
         mSensorManager.unregisterListener(this);
     }


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

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

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.

Resources

Continue ReadingHow to use Mobile Sensors as Instruments in PSLab Android App

How to Add Icons or Menus into the PSLab Android App Toolbar

This blog demonstrates how to add different icons like help, play, pause, etc. and/or menu bar in the toolbar of an Android app along with setting their visibilities on the toolbar i.e. to display the icons only when space is available else to add them in the menu. The topic will be mainly explained by taking the example of menus and icons added to the PSLab app.

How to add a menu in a toolbar?

Following are the steps to add a menu or an icon in the toolbar widget of the Android app

  • First, add toolbar widget to the main layout file as follows
<android.support.v7.widget.Toolbar
   android:id="@+id/compass_toolbar"
   android:layout_width="match_parent"
   android:layout_height="?attr/actionBarSize"
   android:background="?attr/colorPrimary"
   app:popupTheme="@style/AppTheme.PopupOverlay"
   app:title="@string/compass" />

Here, popupTheme is the theme that activates when inflating the toolbar. Usually, it is kept similar to the default theme of the toolbar.

  • Now as the toolbar is ready, we can make the menu that needs to be inflated on the toolbar. For making a menu, make a folder named menu in the resources folder. Now, add a menu resource file in it by giving a proper name and then add the following code
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item
       android:id="@+id/compass_help_icon"
       android:icon="@drawable/compass_help_icon"
       android:title="@string/show_axis_help"
       app:showAsAction="always" />
</menu>

A detailed explanation of the above code is as follows:

  1. The <menu>…</menu> covers all the items in the menu. There can be sub-menu and also sub-sub-menu too. To make a sub-menu, use <menu>…</menu> inside the main menu.
  2. The <item> tag inside the menu defines a specific item to be included in the menu. The icon attribute of an item is used to show the icon on the toolbar. The title attribute of an item is used to show the text inside the menu if space isn’t available to show the icon on the toolbar. The showAsAction attribute is used to define the method of an item i.e. how the item should be visible to the user. Following are some of the values that showAsAction attribute can take:
    • always – It is used to show the icon of the item on the toolbar everytime
    • never – It is used to show the item as a text in the menu everytime the activity is opened
    • ifRoom – It is used to show the icon on the toolbar if there is enough space else the item is included in the menu

NOTE: Always give IDs to menu items as they are used to distinctly identify the item in the java code.

Figure 1. Example of menu and icons in toolbar in PSLab app

As shown in figure 1, the first two icons have always value in their showAsAction attribute whereas other items have never values in their showAsAction attribute.

  • Now the layout and the menu are ready to be inflated from the Java code. First, the toolbar needs to be set up from the Java code. So find the toolbar with its id and then write the following line in the code.
setSupportActionBar(mToolbar);
  • Now the toolbar is ready and so the menu can be inflated on it. So, override the following method to inflate the menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
   MenuInflater inflater = getMenuInflater();
   inflater.inflate(R.menu.activity_compass_help_menu, menu);
   return true;
}

Here, the getMenuInflater() method is used to inflate the menu on the toolbar.

  • Now override the onCreateOptionsMenu() method to do the predefined task of selecting the icon or the item from the menu.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case R.id.compass_help_icon:
           // Do something
           break;
       default:
           break;
   }
   return true;
}

So, in this way a menu can be made so that the number of items delivered to the user can be increased by using the minimum space possible.

Resources

  1. https://developer.android.com/guide/topics/ui/menus – Android Developers guide on how to make a menu in Android
Continue ReadingHow to Add Icons or Menus into the PSLab Android App Toolbar

Detecting Barometer sensor in PSLab Android App

The Pocket Science Lab Android app has Barometer Instrument implemented in it. Although the instrument  is currently working through the mobile sensors and not the PSLab i2c library as there were some issues in the i2c communication with PSLab device.

Thus as the barometer was completely working on through the mobile sensors, there was a major problem coming up. Majority of the mobiles don’t have the barometer sensor which was required, only a  few of the latest devices have the sensors in them.

This issues created problem as now anyone who would have used the barometer instrument would have made an impression that the App was itself not working.

Figure(1) : Showing the stagnant barometer Instrument

Thus this created a bad impression for both the app and it’s developers.

Solving the issue

To solve this major bug, it required to first detect the barometer instrument and then implementing an alert dialog-box showing that barometer sensor is not present in his device.

  • Detecting the Barometer Sensor[2]

The barometer sensor was detected using the sensorManager class of Java

sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager != null ? sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) : null;

Thus using this code the barometer sensor is detected, if sensor was not present the  sensorManger would be null.

  • Implementing the alert-box[1]

Thus if the sensorManger variable was null it notified that the sensor was not present in           the device and corresponding to which an alert-box was implemeneted

if (sensor == null) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (!isFinishing()) {
                new AlertDialog.Builder(BarometerActivity.this)
                    .setTitle(R.string.barometer_alert_title)
                    .setMessage(R.string.barometer_alert_description)
                    .setCancelable(false)
                    .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();
            }
        }
    });

Thus as we can see in the code snippet the alert dialog-box will appear if  sensor is not present as shown in figure (2).

Figure(2) : Screenshot showing alert-box

Resources

  1. Android Alert-box Example, Mkyong.com:
    https://www.mkyong.com/android/android-alert-dialog-example/
  2. Creating a Barometer Application for Android, medium.com:
    https://medium.com/@ssaurel/creating-a-barometer-application-for-android-1c0a5c10b20e

Continue ReadingDetecting Barometer sensor in PSLab Android App

Implementing Toolbar and Exporting Data in CSV format in Multimeter

The latest feature which was implemented in the  Pocket Science Lab Android app is the data exporting feature which was implemented in almost each and every sensor. Now in addition to sensors now the data saving functionality is also implemented in Multimeter and this blog is regarding how the data exporting functionality was implemented for multimeter.

For implementing the data exporting feature first the Toolbar was implemented in the PSLab Android App.

Implementing the Toolbar[1]

For implementing the firstly the front-end of multimeter was changed and the Toolbar was added to it.

<android.support.design.widget.AppBarLayout
  android:id="@+id/top_app_bar_layout"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:theme="@style/AppTheme.AppBarOverlay">

  <android.support.v7.widget.Toolbar
      android:id="@+id/multimeter_toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary"
      app:popupTheme="@style/AppTheme.PopupOverlay"
      app:title="Multimeter" />

The above xml codes shows the implementation of toolbar.

Figure (1): Showing the implementation of multimeter toolbar

  • Backend

For implementing the toolbar the onCreatemenu  was implemented for multimeter

@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.multimeter_log_menu, menu);
this.menu = menu;
return true;
}

For the same a separate menu was created for multimeter

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/record_pause_data"
android:icon="@drawable/record_icon"
android:title="@string/record_csv_data"
app:showAsAction="always" />
<item
android:id="@+id/delete_csv_data"
android:icon="@drawable/delete_icon"
android:title="@string/delete_csv_data"
app:showAsAction="ifRoom" />
<item
android:id="@+id/record_csv_data"
android:icon="@drawable/menu_icon_save"
android:title="@string/save_csv_data"
app:showAsAction="never" />
<item
android:id="@+id/settings"
android:title="@string/nav_settings"
app:showAsAction="never" />
</menu>

Figure(2): Showing Toolbar of Multimeter

Thus through this the basic UI of the toolbar was implemented.

Implementing the Data Export Feature[2]

After implementing the toolbar for multimeter the multimeter the data exporting feature was implemented through the toolbar. The data export feature was implemented in such a manner that the user can start data recording by clicking on the record button after which the recording starts.

Figure(3):Showing snackbar after the data recording is started

and then when the user wants to end the experiment he may click on the pause button after which he gets an option either to export the data in csv format or either to delete the data.

Figure(4):Showing the menu bar for exporting data in csv

After clicking on the export data button the snacker appears which finally tells the user that the data is exported in this particular location (mentioned in the snackbar)

Figure(5):Showing the snackbar for showing location of the CSV folder

Further going to the csv folder we get the excel sheet in which the data is exported

Figure(6):Showing the excel sheet of data recorded

And this is how the data exporting feature was implemented in the PSLab android app. The users can use this functionality to perform experiments using multimeter in PSLab.

Resources

  1. Medium, Android Toolbar for AppCompatActivity:
    https://medium.com/@101/android-toolbar-for-appcompatactivity-671b1d10f354
  2. Code Project, Exporting data in CSV format:
    https://www.codeproject.com/questions/491823/read-fwritepluscsvplusinplusplusandroid

 

Continue ReadingImplementing Toolbar and Exporting Data in CSV format in Multimeter