CC Blog Projects Research & Design Hub

Home Air Quality Monitoring (Part 2)

The Android Application

Raul continues his article series on building a home air quality monitoring system based on Nordic Semi’s nRF5340 dev kit. In Part 2, he discusses the mobile app that he modified to interact with the IoT device and the web server.

  • How to building an IoT-based home air quality monitoring system

  • How to install the necessary software tools

  • How the monitoring system functions

  • How to add the sensor drivers

  • How to send data over BLE

  • How to implement the display and touchscreen

  • How to implemented running average, low-pass filters

  • Nordic Semi’s nRF5340 dev kit

  • CCS811 eCO2/eTVOC sensor from AMS

  • Sharp GP2Y1010AU0F dust/particle sensor (from Sparkfun)

  • STMicroelectronics LPS22HB barometric pressure sensor

  • DHT22 ambient temperature

  • Relative humidity sensor

  • Arduino Mega2560 prototyping shield 

  • SEGGER J-Link programmer/debugger

  • 2.8″ TFT Touch Shield for Arduino w/Capacitive Touch

In Part 1 of this article series (Circuit Cellar 378, January 2022) [1], I discussed the IoT device prototype for the Home Air Quality Monitoring (HAQM) system. The IoT device is based on Nordic Semiconductor’s nRF5340 Development Kit (DK), it uses air quality sensors to collect data, and a wireless Bluetooth Low Energy (BLE) connection to send them to an Android device. The IoT device reads its sensors every 1-2 seconds (depending on the sensor), and sends the data every 5 seconds to the Android mobile device. The mobile device, in turn, takes the sensor readings and sends them to a web server by using the HTTP protocol.

Here, in Part 2 of the series, I discuss the mobile application that I modified to interact with the IoT device and the web server. I took as a basis the nRF Toolbox for Bluetooth LE Android application from Nordic Semiconductor, which is available for download as an Android Studio source code project [2].

To follow this article, you will need to have a fair knowledge of Android application development with the Android Studio Integrated Development Environment (IDE) and the Java programming language. However, if you don’t have this knowledge, I hope at least you will be able to get a general understanding about how sensor data from the IoT device is received and processed in the Android application, as well as how data is sent to the web server with the HTTP protocol.

FUNCTIONAL DESCRIPTION RECAP

As I explained in Part 1, the HAQM system is composed of three parts: The IoT device, the Android device with a custom application and the server with a web application (Figure 1). The IoT device prototype (Figure 2) is based on the nRF5340 Development Kit (DK) from Nordic Semiconductor. It has four sensors: an AMS CSS811 eCO2/eTVOC sensor, a Sharp GP2Y1010AU0F dust/particle sensor, a STMicroelectronics LPS22HB barometric pressure sensor and a DHT22 ambient temperature and relative humidity sensor. The IoT device takes readings from its sensors and sends the data to the Android device via BLE every 5 seconds.

Figure 1 System block diagram
Figure 1
System block diagram
Figure 2 IoT device prototype based on the nRF5340 development kit
Figure 2
IoT device prototype based on the nRF5340 development kit

The touchscreen display shows the sensor data being sent to the mobile device, and the Bluetooth connection status. A “Send On/Off” button in the GUI allows the activation/deactivation of the data-sending function to the mobile device. The display also shows data received from the mobile device, such as latitude/longitude coordinates, velocity and bearing.

Sensor readings are taken every 1 to 2 seconds (depending on the sensor), and all measurements are passed through low-pass filters to reduce data noise. Every 5 seconds, the current filtered values are sent as a JSON string to the mobile device via BLE. The Android application takes the received measurements and adds data of its own before sending them to the web server by using the HTTP protocol. It adds, for instance, a timestamp, the mobile device’s latitude and longitude coordinates, the username and ID code. The web server receives the data and stores it in a database. When a user enters the web server’s IP address or domain, the server reads from the database and displays graphically current and past sensor data. The data are also displayed in the Android application’s screen GUI.

— ADVERTISMENT—

Advertise Here

ANDROID DEVELOPMENT

Android applications (apps) are developed using Android Studio, the official integrated development environment (IDE) provided by Google. The IDE is built on JetBrains’ IntelliJ IDEA software, and is available for download on Windows-, macOS- and Linux-based operating systems. Before 2019, Java was the preferred programming language to develop Android applications. In 2019, it was officially replaced by Kotlin, a new programming language developed by JetBrains with the help of open-source contributors. However, Java is still supported (as well as C++), and still used by many. Although Kotlin isn’t much different from Java at the general syntax level (both look pretty much like C++), I personally still use Java, because I’m already familiar with its nuances.

As I mentioned before, Nordic Semiconductor offers an Android application called nRF Toolbox for Bluetooth LE. In Part 1 of this article series, I mentioned that it can be installed from the Google Play Store and used to run preliminary tests with the IoT device. It’s also available as a source code Android Studio project.

More than a single-function application, nRF Toolbox is a container app for many BLE example demonstrations of many BLE profiles. Figure 3 is a screenshot of its main screen, showing the available demonstrations. Some of them are: Blood Glucose Monitor (BGM), Blinky, Blood Pressure Monitor (BPM), Continuous Glucose Monitor (CGMS) and Cycling Speed and Cadence (CSC). And at the bottom of the screen there’s also Nordic UART (UART), which demonstrates Nordic’s custom UART BLE service for bidirectional text communication.

Figure 3 nRF Toolbox Android application main screen
Figure 3
nRF Toolbox Android application main screen

In Part 1, I mentioned that I used the peripheral_uart example project from the nRF Connect SDK (NCS) as a basis for the nRF5340-based IoT device’s firmware. This peripheral_uart project works with the UART demonstration in the nRF Toolbox application. So, I took the nRF Toolbox application and modified the UART demonstration source code to suit this project’s needs. You can download the modified Android Studio project from Circuit Cellar’s article code and files webpage. After installing Android Studio IDE, you will be able to open the project to inspect the code, compile, and install the app on your device.

It is noteworthy, however, that communications with the web server come deactivated by default, until you configure your own server’s domain name or IP address in the source code. Please refer to the install_run_android.md file included with the source code on how to do this. Web server implementation will be covered in Part 3 of this article series. In the meantime, you can leave this function disabled, and you still will be able to test the interaction between the IoT device and the Android application.

HOW THE APP WORKS

Before explaining the modifications I made to the app, I will first explain how the unmodified app works, especially when testing it with the IoT device. After the Android device connects to the IoT device, you get the screen shown in Figure 4a. If you swipe the screen to the right, you get the debug screen shown in Figure 4b. Here, the received sensor data JSON string from the IoT device is printed in green. Now, to send data from the Android device to the IoT device, each one of the nine square buttons at the center in Figure 4a can be configured with a given text string. Then, after tapping a square, its corresponding text string will be sent via BLE to the IoT device. The aforementioned install_run_android.md file also contains a detailed guide on how to do these tests.

Figure 4 Original nRF Toolbox UART demo:  a) Main screen; b) Debug screen
Figure 4
Original nRF Toolbox UART demo: a) Main screen; b) Debug screen

The original nRF Toolbox application has Java classes (as in Object Oriented Programming) in charge of defining specific behavior for each demonstration (for instance, the UART demo). There are also classes to share functionality among them. Android applications follow the programming paradigm of “multiple activities.” This means that any Android app is composed of one or many activities, with each one generally associated with a particular screen in the app. The first screen that appears when an app begins to run typically is from the main activity. This, in turn, calls other activities, such as when a button is tapped or a menu is opened. These multiple activities appear as a cohesive entity from the user’s experience point of view, but generally they are loosely bounded by design, and have minimal dependencies among them [3]. If you are new to Android development, the Android app developer’s website [4] is a great place to read about introductory programming concepts, and to get access to official documentation and examples.

In the nRF Toolbox app, I had to modify four classes to add the required functionality for the HAQM system:

  • The ToolboxApplication.java class that extends the Android Application class, which, according to the Android documentation, is the base class for maintaining global application state.
  • The UARTActivity.java class, that’s in charge of creating the UART activity screen and controlling the demonstration.
  • The UARTManager.java class is in charge of managing the BLE connections/disconnections, service discovery and receiving indication. This class is also in charge of retrieving the data bytes received from the remote BLE device (in our case, the IoT device).
  • The UARTControlFragment.java class is in charge of managing the data sending from the nine square buttons shown in Figure 4a.

After analyzing the code in these classes, it was obvious that it would be necessary to share data among them to make the app interact with the IoT device and the web server. Now, in Android programming, there’s more than one way to share data between classes and activities. One way is by using global variables—an approach especially known to many (myself included) with a background in embedded systems! I’m not implying this is the best way in this case (I’m not an Android programming expert after all), but it was easy to implement and it worked very well.

— ADVERTISMENT—

Advertise Here

So, to pass data between classes, I modified the ToolboxApplication.java class to add some extra attributes and methods. Lines 7-25 from Listing 1 show the added attributes and methods. The iotJsonString attribute (line 7) stores the last sensor data JSON string received from the IoT device (Figure 5a). The methods getIotJsonString() and setIotJsonString() (lines 12-17) are used to access the attribute for reading and writing. The mobileJsonString attribute (line 8) stores the JSON string containing the mobile device’s GPS coordinates, velocity and heading that will be sent to the IoT device (Figure 5b). Corresponding methods in lines 20-25 are also used to access that attribute for reading and writing. The rest of the code in this listing is from the unmodified version.

Listing 1
Added code to the ToolboxApplication.java class

1  public class ToolboxApplication extends Application {
2     public static final String CONNECTED_DEVICE_CHANNEL = “connected_device_channel”;
3     public static final String FILE_SAVED_CHANNEL = “file_saved_channel”;
4     public static final String PROXIMITY_WARNINGS_CHANNEL = “proximity_warnings_channel”;
5 
6     /* ----- Custom attributes and methods for the HAQM system ----- */
7     private static String iotJsonString = null; //Stores JSON str. from IoT device
8     private static String mobileJsonString = null; //Stores JSON str. from mobile device
9 
10 
11     // To store and retrieve JSON string from the IoT device
12     public static String getIotJsonString() {
13         return iotJsonString;
14     }
15     public static void setIotJsonString(String str) {
16         iotJsonString = str;
17     }
18 
19     // To store and retrieve JSON string from this mobile device
20     public static String getMobileJsonString() {
21         return mobileJsonString;
22     }
23     public static void setMobileJsonString(String str) {
24         mobileJsonString = str;
25     }
26 
27     /* END:----- Custom attributes and methods for the HAQM system ----- */
28 
29     @Override
30     public void onCreate() {
31         super.onCreate();
32 
33         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
34             // Some code lines from the original source code
35             // go here, omitted for brevity
36             // ...
37         }
38     }
39 }
Figure 5 JSON strings: a) From the IoT device; b) From the mobile device
Figure 5
JSON strings: a) From the IoT device; b) From the mobile device
RECEIVING DATA FROM THE IOT DEVICE

As noted earlier, the code in charge of retrieving the BLE data received from the remote device is in the UARTManager.java class. So, I added code to this class to retrieve the received JSON string from the IoT device and store it in the iotJsonString attribute. Lines 13-25 in Listing 2 show the added code inside the initialize() function in the UARTManagerGattCallback class. initialize() configures a callback function for the txCharacteristic (transmission characteristic) from Nordic’s UART BLE service. This callback will be executed when data are available in the IoT device to be read by the Android device from that characteristic. If you are not familiar with key concepts of Bluetooth Low Energy, such as “services” and “characteristics,” check the Circuit Cellar article materials webpage for suggested reading on this topic.

Listing 2 

Added code to the UARTManager.java class

1 private class UARTManagerGattCallback extends BleManagerGattCallback {
2 
3     @Override
4     protected void initialize() {
5         setNotificationCallback(txCharacteristic)
6             .with((device, data) -> {
7                 final String text = data.getStringValue(0);
8                 // Puts the BLE received data in the application’s “debug” screen
9                 log(LogContract.Log.Level.APPLICATION, “\”” + text + “\” received”);
10 
11                 /* ----- Code added for the HAQM Monitoring system ----- */
12                 // Print received data to Logcat window
13                 Log.d(“MyUARTManager”, “Received data: “ + text);
14 
15                 // Let’s try to parse the received JSON string, just for debugging
16                 try {
17                     JSONObject jsonObj = new JSONObject(text);
18                     String eCO2 = jsonObj.getString(“c”);
19                     Log.d(“MyUARTManager”, “Received eCO2 = “ + eCO2);
20                 } catch (final JSONException e) {
21                     Log.e(“MyUARTManager”, “Json parsing error: “ + e.getMessage());
22                 }
23 
24                 // Store the received JSON string from the IoT device
25                 ToolboxApplication.setIotJsonString(text);
26                 /* END:----- Code added for the HAQM system ----- */
27 
28                 mCallbacks.onDataReceived(device, text);
29             });
30         requestMtu(260).enqueue();
31         enableNotifications(txCharacteristic).enqueue();
32     }
33 
34     @Override
35     public boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
36         final BluetoothGattService service = gatt.getService(UART_SERVICE_UUID);
37         
38         // Some code lines from the original source code
39         // go here, omitted for brevity
40         // ...
41     }
42 
43     @Override
44     protected void onDeviceDisconnected() {
45         rxCharacteristic = null;
46         txCharacteristic = null;
47         useLongWrite = true;
48     }
49 }

The -> symbol in line 6 denotes the callback definition as a “Lambda expression.” When it is called, the received BLE data is already in the data parameter (line 6). Line 7 retrieves this data (hopefully the JSON string) into the text String object. Line 9 prints a debug message on the app’s debug screen (the green text in Figure 4b and Figure 6b). Line 13 prints a similar debug text in Android Studio IDE’s Logcat window. Lines 16-22 take the content of the text String and parses it as a JSON object to extract the eCO2 concentration value—the one associated with the “c” key in the JSON string from Figure 5a. This is just for debugging purposes, to validate the received JSON string. Line 25 calls the setIotJsonString() method added to the ToolboxApplication.java class to write the iotJsonString property with the received JSON string value.

Figure 6 Modified nRF Toolbox UART demo:  a) Main screen; b) Debug screen
Figure 6
Modified nRF Toolbox UART demo: a) Main screen; b) Debug screen
INTERACTING WITH WEB SERVER

Every JSON string received from the IoT device will be sent to the web server, along with additional data gathered from the mobile device itself. Moreover, the server webpage will be downloaded and visualized on the UART demonstration screen. Listing 3 shows the onCreateView() method inside the UARTActivity.java class, which sets up the UART demonstration screen. Lines 2-17 comprise the original code that prepares the screen GUI. I added lines 21-57 for the needed functionality. For instance, lines 21-27 initialize the webview GUI element, first by attaching a web client object to it (lines 21-22). Then, lines 23-24 enable JavaScript (needed to visualize sensor data dynamically). Line 27 loads the web server’s page in the webview GUI element. The white canvas at the center in Figure 6a shows the content of the downloaded webpage, which will also be accessible from any regular web browser.

Listing 3
Added code to the onCreateView() function in the UARTActivity.java class

1 protected void onCreateView(final Bundle savedInstanceState) {
2     setContentView(R.layout.activity_feature_uart);
3     container = findViewById(R.id.container);
4     // Setup the sliding pane if it exists
5     final SlidingPaneLayout slidingPane = slider = findViewById(R.id.sliding_pane);
6     if (slidingPane != null) {
7         slidingPane.setSliderFadeColor(Color.TRANSPARENT);
8         slidingPane.setShadowResourceLeft(R.drawable.shadow_r);
9         slidingPane.setPanelSlideListener(new SlidingPaneLayout.SimplePanelSlideListener() {
10             @Override
11             public void onPanelClosed(final View panel) {
12                 // Close the keyboard
13                 final UARTLogFragment logFragment = (UARTLogFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_log);
14                 logFragment.onFragmentHidden();
15             }
16         });
17     }
18 
19     /* ----- Code added for the Home Air Quality Monitoring system ----- */
20     // Display server web page
21     WebView myWebView = (WebView) findViewById(R.id.webview); //Get ref. to ‘webview’
22     myWebView.setWebViewClient(new WebViewClient()); // Attach web client to ‘webview’
23     WebSettings webSettings = myWebView.getSettings(); // Get ref. to web client setting
24     webSettings.setJavaScriptEnabled(true); // Enable JavaScript in the web client
25 
26     // Load the Home Air Quality Monitoring web page
27     myWebView.loadUrl(WEB_SERVER_ADDRESS + “homeair/”);
28 
29     // Get reference to ‘textview’ GUI element (see ‘activity_feature_uart.xml’). 
30     // This element is located between the ‘webview’ element and ‘CONNECT’ button
31     // It displays debug messages from the HTTP POST requests sent to the server
32     textView = (TextView) findViewById(R.id.textview); 
33 
34     // Get device name (will be sent to the server as ‘user’ name)
35     deviceName = Settings.Secure.getString(getContentResolver(), “bluetooth_name”);
36     Log.d(“NAME”, “Device name: “ + deviceName);
37 
38     // Create the Home Air Quality Monitoring periodic task
39     mHandler = new Handler();
40     startPeriodicTask();
41     
42     // Create queue for the HTTP POST request
43     queue = Volley.newRequestQueue(this);
44 
45     // Set up location services
46     mLastUpdateTime = “”; // For storing time of last location update
47     mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
48     mSettingsClient = LocationServices.getSettingsClient(this);
49 
50     // Start the process of building the LocationCallback, LocationRequest, and
51     // LocationSettingsRequest objects.
52     createLocationCallback();
53     createLocationRequest();
54     buildLocationSettingsRequest();
55     // Start location updates
56     startLocationUpdates();
57     mRequestingLocationUpdates = true; // Enable location updates
58     /* END:----- Code added for the Home Air Quality Monitoring system ----- */
59 }

The WEB_SERVER_ADDRESS “constant” (line 27) stores the web server’s domain or IP address (for instance: “https://<mydomain>.com/”, or “https://<my-ip-address>/”). It is declared in the same class, around line 170, and you will have to set it with your own server’s domain or IP, once your server is ready. The “homeair/” string concatenated to it is the server’s folder name, which contains the web application files. (The server and web application will be discussed in Part 3 of this article series.) Line 32 gets a reference to the textview GUI element located between the webview element (the white canvas) and the black CONNECT/DISCONNECT button shown in Figure 6a. This text view displays short debug messages from the HTTP POST requests sent to the web server. In Figure 6a, for example, it shows part of the received response to an HTTP POST request sent to the web server (“Connected to DB! Received POST data at:”).

Line 35 retrieves the Bluetooth name currently configured in the Android device (the name configured for Bluetooth communications). This name will be used as a username for the IoT device/mobile device pair, to discriminate them between potentially more than one device pair sending Home Air Quality data to the web server. Lines 39-40 start a periodic task or function that will be in charge of sending data to the web server (more on this later) and line 43 creates a queue object that’s required to send HTTP requests with the Android Volley HTTP library. Lines 46-57 set up the mobile device location services that allow location updates (latitude and longitude coordinates) to be obtained. I will not go deeper regarding Android location services and HTTP connectivity. There are some suggested readings about these topics on Circuit Cellar’s article materials webpage.

Periodic Function

Listing 4 shows the code for the periodic function mentioned before. It runs every 5 seconds and basically performs two tasks: First, it retrieves data from the mobile device and builds the JSON string that will be sent to the IoT device (Figure 5b). Lines 7-10 retrieve the latitude, longitude, velocity and bearing from the mobile device. Line 13 concatenates those values with additional characters to build the JSON string, and line 14 stores it in the mobileJsonString attribute added to the ToolboxApplication.java class.

Listing 4
mPeriodicTask periodic function for the UARTActivity.java class

1 Runnable mPeriodicTask = new Runnable() {
2   @Override
3   public void run() {
4   // Use this periodic task also to get required data from the mobile device
5   if (mCurrentLocation != null) {
6     // Get the mobile device’s Lat, Lon, Vel and Bearing
7     String lat = String.valueOf(mCurrentLocation.getLatitude());
8     String lon = String.valueOf(mCurrentLocation.getLongitude());
9     String vel = String.format(“%.1f”, mCurrentLocation.getSpeed());
10     String bearing = String.format(“%.1f”, mCurrentLocation.getBearing());
11 
12     // Build mobile device JSON string that will be sent to the IoT device
13     String mobileJsonString = “{“ + lat + “,” + lon + “,” + vel + “,” + bearing + “}”;
14     ToolboxApplication.setMobileJsonString(mobileJsonString);
15   }
16   // Attempt to send air quality data to web server via HTTP POST request
17   try {
18     String jsonString = ToolboxApplication.getIotJsonString(); // Get JSON string
19 
20     if(jsonString != null) { // Check JSON string, debug print the result
21       Log.d(“MyUARTActivity”, “Here’s jsonString: “ + jsonString);
22     } else {
23       Log.d(“MyUARTActivity”, “jsonString is null!”);
24     }
25 
26     // --------------- Send POST request to server --------------------------
27     // Check location updates is enabled and that there’s a valid JSON string
28     if(mRequestingLocationUpdates == true && jsonString != null) {
29       // Construct the HTTP POST request
30       StringRequest postRequest = new StringRequest(Request.Method.POST, WEB_SERVER_RECEIVE_URL,
31       new com.android.volley.Response.Listener<String>() {
32         @Override
33         public void onResponse(String response) {
34           // Print to Logcat window the server’s response string.
35           Log.d(“MyHTTP”, “POST Response is: “ + response);
36           // Display the first N chars on the GUI debug ‘textview’ element
37           textView.setText(response.substring(0, 80));
38         }
39       }, new com.android.volley.Response.ErrorListener() {
40       @Override
41       public void onErrorResponse(VolleyError error) {
42         final String statusCode = String.valueOf(error.networkResponse.statusCode);
43         // Print debugging info to the Logcat and the GUI ‘textview’ element
44         Log.d(“MyHTTP”, “That didn’t work! Code “ + statusCode);
45         textView.setText(“That didn’t work! Code “ + statusCode);
46       }
47       }) {
48       @Override
49       protected Map<String, String> getParams() { //”key:value” params for request
50         Map<String, String> params = new HashMap<String, String>();
51         // These are the “key:value” params to send
52         long unixTime = System.currentTimeMillis() / 1000L;
53         params.put(“unix_time”, String.valueOf(unixTime));
54         params.put(“name”, deviceName);
55         params.put(“id_code”, idCode); 
56         params.put(“latitude”, String.valueOf(mCurrentLocation.getLatitude()));
57         params.put(“longitude”, String.valueOf(mCurrentLocation.getLongitude()));
58         params.put(“status”, iotDeviceStatus);
59         params.put(“sensors”, jsonString); // IoT device JSON string
60 
61         return params;
62       }
63       };
64 
65       if (queue != null) {
66       queue.add(postRequest); // Add POST request to the RequestQueue
67       }
68     } else {
69       Log.d(“MyHTTP”, “No POST. No location or jsonString null”);
70       textView.setText(“No POST. No location or jsonString null”);
71     }
72     // Nullify last received sensor data until new data from IoT device is received
73     ToolboxApplication.setIotJsonString(null);
74     // END: --------------- Send POST request ------------------------------
75   } finally {
76     // This always executed. Even if exception is thrown (due to an HTTP error)
77     mHandler.postDelayed(mPeriodicTask, mInterval); // Repeat task every mInterval
78   }
79   }
80 };

Second, it sends the HTTP POST request to the web server with the sensor data JSON string from the IoT device, along with additional data collected from the mobile device. A try/catch/finally block sequence type is used to attempt a graceful handling of any exception that may occur in the HTTP communication. In fact, though, no catch block is used at all. Ideally, we would “catch” the exception in its corresponding block, and run some contingency code. But for simplicity, we will just ignore the failure (which in normal conditions will be sporadic) and try to send data in the next execution pass (after 5 seconds). To send the POST request, the first code lines in the “try” block retrieve the IoT device JSON string, and print debug information on whether the JSON string is valid or null (lines 18-24). Next, with line 28, two conditions are evaluated, which must be true in order to send the HTTP request.

First, the requesting for location updates in the mobile device must be enabled (mRequestingLocationUpdates == true). This could be not the case if location services aren’t available (for instance, because the user didn’t grant the necessary permissions). Second, the last JSON string received from the IoT device must not be null (jsonString != null). It will be null before the very first JSON string arrives to the mobile device. It will be also nullified after each POST request is sent (line 73).

Android Volley Library

So, the way the Android volley HTTP library works is as follows: First, a queue must be created (see line 43 from Listing 3). This is a buffer to put in all HTTP requests that will be handled by the Volley library, one after the other, in the same arriving order. Second, an HTTP request must be constructed by calling the StringRequest() constructor, passing four arguments: The first two are the request type (Request.Method.POST in our case) and the server URL (Listing 4, line 30). The last two are a response listener that overrides the onResponse() method (lines 31-38), and a response error listener that overrides the onErrorResponse() method (lines 39-46).

onResponse() is a callback function that’s executed when the response to the HTTP request arrives from the server. onErrorResponse() is another callback function that’s executed when an HTTP error arrives instead. As you can see in the code, in both cases, the server response is just printed to the Logcat window in the IDE and also in the textview element on the mobile device’s screen GUI.

In lines 47-63, there’s also a body definition for the StringRequest() constructor. In this body, the getParams() method is also overridden. This method is called by the volley library to get the key:value pairs that will be sent in the POST request. In the overriding code, a HashMap object is created to store the key:value pairs for the web server. Lines 53-59 invoke HashMap’s put method to add seven key:value pairs to the request. The first key, “unix_time”, references the time stamp value for the current request, obtained from the Android operating system in line 52.

The “name” key, references the mobile device’s Bluetooth name retrieved right after the app starts (line 35 in Listing 3). This will be used as the device pair username in the HAQM system database. “id_code” is a unique code assigned to the device pair. “latitude” and “longitude” are the mobile device’s geolocation coordinates (assumed to also be the device pair’s) and “status” is the device pair’s current status. Finally, “sensors” references the JSON string from the IoT device containing the current sensor readings. “id_code” and “status” are meant to be used more functionally in the project’s next iteration. For now, they just store manually assigned values. After the web server receives these key:value pairs, it stores them in a database.

Listing 5 shows another periodic task added to the UARTControlFragment.java class to send data from the mobile device to the IoT device. If you recall, this class is in charge of managing the data sending functionality from the nine square buttons shown in Figure 4a. I added this mJsonSender periodic task to send to the IoT device the mobile JSON string built and stored before (see lines 13-14 in Listing 4). With line 8 we retrieve the stored JSON string, and in lines 11-12 we use the send() method from the uart object to send the data via BLE. This periodic task executes also every 5 seconds.

Listing 5
mJsonSender periodic function for the UARTControlFragment.java class

1 Runnable mJsonSender = new Runnable() {
2     @Override
3     public void run() {
4         try {
5             Log.d("MyUARTControl", "Sending mobile JSON to IoT device...");
6 
7             // Retrieve mobile device's data
8             String mobileJson = ToolboxApplication.getMobileJsonString();
9 
10             if (mobileJson != null) {
11                 final UARTInterface uart = (UARTInterface) requireActivity();
12                 uart.send(mobileJson);
13                 Log.d("MyUARTControl", "mobileJson sent: " + mobileJson);
14             }else {
15                 Log.d("MyUARTControl", "mobileJson is null!");
16             }
17 
18         } finally {
19             // 100% guarantee that this always happens, even if
20             // your update method throws an exception
21             mHandler.postDelayed(mJsonSender, mInterval);
22         }
23     }
24 };
CONCLUSION

Please don’t take my approach here as an example of good Android development! I’m no Android programming expert, after all. The code, however, worked very well in all my tests, though, some caveats must be taken into consideration.

— ADVERTISMENT—

Advertise Here

First, I had to force the app into portrait screen orientation because, right now, it does not support saving/restoring state (at least for the added parts); which is required to change device orientation (for instance, from portrait to landscape) without resetting the currently running activity. This feature is not really important for a first prototype, so I left it out.

Second, the code I added doesn’t really take into account energy efficiency, so the app could consume a bit more battery power in comparison with the unmodified version. Third, the app doesn’t send data when running in the background—for instance when the screen turns off. Because it’s just a first prototype, I also didn’t care about it that much.

I also didn’t have enough space here to discuss the GUI modifications I did, mainly by graphically dragging, dropping and moving GUI objects in the corresponding activity file, nor the addition of the extra libraries (such as volley and play-services-location) in the build.gradle script. I also did not discuss the modification of the AndroidManifest.xml file for the inclusion of permissions for accessing Internet and location services, which is really as easy as adding a few lines of XML text.

If you know about Android development, you know what I’m talking about. If not, don’t worry much about it! I hope I managed at least to get you a bit interested in Android programming. It is very engaging and rewarding. In any case, I’ll see you in Part 3 for discussing the web server application! 

References:
[1] Home Air Quality Monitoring (Part 1), Circuit Cellar 378, January 2022.
[2] Nordic Semiconductor/Android-nRF-Toolbox Android applicationhttps://github.com/NordicSemiconductor/Android-nRF-Toolbox
[3] Introduction to Activities,  https://developer.android.com/guide/components/activities/intro-activities
[4] Android for Developershttps://developer.android.com/

RESOURCES
Adafruit | www.adafruit.com
Ams | www.ams.com
JetBrains | www.jetbrains.com
Nordic Semiconductor | www.nordicsemi.com
STMicroelectronics | www.st.com

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • FEBRUARY 2022 #379 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
| + posts

Raul Alvarez Torrico has a B.E. in electronics engineering and is the founder of TecBolivia, a company offering services in physical computing and educational robotics in Bolivia. In his spare time, he likes to experiment with wireless sensor networks, robotics and artificial intelligence. He also publishes articles and video tutorials about embedded systems and programming in his native language (Spanish), at his company’s web site www.TecBolivia.com. You may contact him at raul@tecbolivia.com

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2023 KCK Media Corp.

Home Air Quality Monitoring (Part 2)

by Raul Alvarez Torrico time to read: 20 min