Part two of the beginner's guide to getting started with the IoT and Raspberry Pi.
By Aradhana Kumar and Vaibhav Mathur
In part 2, we are building the Android application for the Raspberry Pi IoT project from part 1 of this tutorial. The project files for the Android application are also available. If you are a beginner to Android, resources like these will be helpful:
Creating the Android Application and Connecting It to the Server
Objectives:
- Receive the temperature readings on your Android device.
- Display the database of the temperature readings sent by the Pi.
Structural Flow:
- To receive the temperature readings on your Android device, we need our Android app to receive data from the server created above.
- For this, we will use Google Cloud Messaging, HTTP Protocol (downstream messaging). The following will be the directional flow of data in GCM downstream messaging:
Web Server-->Google Cloud Messaging
(The web application will connect to the Google cloud messaging API URL and send a notification message to the GCM server to push it to the registered Android device.)
Before we write the script to send GCM push notifications, you’ll have to create a Google API key for your application.
- Save the project number created with the project. You’ll have to use it when configuring the Android application.
- From the API Manager, select Overview and then enable Google Cloud Messaging (from under Mobile APIs).
Now, select Credentials, click on Create Credentials and then on the API key, and make a new server key (do this from the same device that hosts your web server). Save the API key generated; it will be used when the server communicates with Google service.
Create a new file, "sending-push-notifications.php", in your DocumentRoot Directory, or download the code file.
What This Code Does:
When a new application user registers with Google GCM, the application sends the GCM RegID to the server, which gets saved in the table regions that we created earlier. As soon as the recorded temperature exceeds the set threshold, the file "write-to-database.py" sends a URL request to the server to run this file to send an alert to the user. Your web server then connects to the Google server and sends the message, which is pushed by the server to registered devices. That's all there is to the server side scripting for this project.
Building the Android Application for the Raspberry Pi IoT System
The Android application is a GCM client and it will register with the GCM server and receive a GCM-Registration ID. Then it will share the GCM-Reg-ID with the application server. Finally, it will receive a push notification from the GCM server and alert the user.
*The application is developed on Android Studio, thus it is recommended that you install Android studio.
*Make sure that you are running/testing the application on a real Android device rather than on the emulator. Please make sure, before starting, that you have the following:
Google Play Services:
It is at the bottom, below Extras in the SDK Manager.
After installing, Add the following line of code in your build.gradle:
Inside dependencies, add the following line:
compile "com.google.android.gms:play-services:6.5.87"
Sync the project after addition. If everything looks alright, move to the next step.
2. Google Project ID:
During development of the web server, we created a project in the Google console for creating the Google API server key. Google will assign a project -ID. We need to use that Google project ID to register this Android application with the GCM server.
Now let’s start creating activities for the app.
1. Create a class Connection.java:
Make sure the MESSAGE_KEY is same here and at the server (in our case, we have used m).
APP_SERVER_URL is the URL of your server that you created before (Put your server url in here).
GOOGLE_PROJECT_ID is the project id that you created before. (Put your Google project ID here).
2. Create a class Register.java:
package com.example.android.gcmapp;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import java.io.IOException;
import java.security.AccessControlContext;
/**
* Created by my on 7/12/2016.
*/
public class Register extends Activity {
Button btnGCMRegister;
Button btnAppShare;
GoogleCloudMessaging gcm;
Context context;
String regId;
public static final String REG_ID = "regId";
private static final String APP_VERSION = "appVersion";
static final String TAG = "Register Activity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
context = getApplicationContext();
btnGCMRegister = (Button) findViewById(R.id.btnGCMRegister);
btnGCMRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
if (TextUtils.isEmpty(regId)) {
regId = registerGCM();
Log.d("RegisterActivity", "GCM RegId: " + regId);
} else {
Toast.makeText(getApplicationContext(),
"Already Registered with GCM Server!",
Toast.LENGTH_LONG).show();
}
}
});
btnAppShare = (Button) findViewById(R.id.btnAppShare);
btnAppShare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
if (TextUtils.isEmpty(regId)) {
Toast.makeText(getApplicationContext(), "RegId is empty!",
Toast.LENGTH_LONG).show();
} else {
Intent i = new Intent(getApplicationContext(),
MainActivity.class);
i.putExtra("regId", regId);
Log.d("RegisterActivity",
"onClick of Share: Before starting main activity.");
startActivity(i);
finish();
Log.d("RegisterActivity", "onClick of Share: After finish.");
}
}
});
}
public String registerGCM() {
gcm = GoogleCloudMessaging.getInstance(this);
regId = getRegistrationId(context);
if (TextUtils.isEmpty(regId)) {
registerInBackground();
Log.d("RegisterActivity",
"registerGCM - successfully registered with GCM server - regId: "
+ regId);
} else {
Toast.makeText(getApplicationContext(),
"RegId already available. RegId: " + regId,
Toast.LENGTH_LONG).show();
}
return regId;
}
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getSharedPreferences(
MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
String registrationId = prefs.getString(REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
int registeredVersion = prefs.getInt(APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
Log.d("RegisterActivity",
"I never expected this! Going down, going down!" + e);
throw new RuntimeException(e);
}
}
private void registerInBackground() {
new AsyncTask() {
@Override
protected Object doInBackground(Object[] params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regId = gcm.register(Connection.GOOGLE_PROJECT_ID);
Log.d("RegisterActivity", "registerInBackground - regId: "
+ regId);
msg = "Device registered, registration ID=" + regId;
storeRegistrationId(context, regId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
Log.d("RegisterActivity", "Error: " + msg);
}
Log.d("RegisterActivity", "AsyncTask completed: " + msg);
return msg;
//return null;
}
//@Override
/*protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regId = gcm.register(Connection.GOOGLE_PROJECT_ID);
Log.d("RegisterActivity", "registerInBackground - regId: "
+ regId);
msg = "Device registered, registration ID=" + regId;
storeRegistrationId(context, regId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
Log.d("RegisterActivity", "Error: " + msg);
}
Log.d("RegisterActivity", "AsyncTask completed: " + msg);
return msg;
}*/
//@Override
protected void onPostExecute(String msg) {
Toast.makeText(getApplicationContext(),
"Registered with GCM Server." + msg, Toast.LENGTH_LONG)
.show();
}
}.execute(null, null, null);
}
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getSharedPreferences(
MainActivity.class.getSimpleName(), Context.MODE_PRIVATE);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(REG_ID, regId);
editor.putInt(APP_VERSION, appVersion);
editor.commit();
}
}
This class registers your Android device with the Google Cloud Server. Google Cloud Server sends a Registration ID to your Android Device. Now we need to Send this ID to the server.
3. Create class ShareWithServer.java:
package com.example.android.gcmapp;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created by my on 7/12/2016.
*/
public class ShareWithServer {
public String shareRegIdWithAppServer(final Context context,
final String regId) {
String result = "";
Map<String, String> paramsMap = new HashMap<String, String>();
paramsMap.put("regId", regId);
try {
URL serverUrl = null;
try {
serverUrl = new URL(Connection.APP_SERVER_URL);
} catch (MalformedURLException e) {
Log.e("AppUtil", "URL Connection Error: "
+ Connection.APP_SERVER_URL, e);
result = "Invalid URL: " + Connection.APP_SERVER_URL;
}
StringBuilder postBody = new StringBuilder();
Iterator<Map.Entry<String, String>> iterator = paramsMap.entrySet()
.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> param = iterator.next();
postBody.append(param.getKey()).append('=')
.append(param.getValue());
if (iterator.hasNext()) {
postBody.append('&');
}
}
String body = postBody.toString();
byte[] bytes = body.getBytes();
HttpURLConnection httpCon = null;
try {
httpCon = (HttpURLConnection) serverUrl.openConnection();
httpCon.setDoOutput(true);
httpCon.setUseCaches(false);
httpCon.setFixedLengthStreamingMode(bytes.length);
httpCon.setRequestMethod("POST");
httpCon.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
OutputStream out = httpCon.getOutputStream();
out.write(bytes);
out.close();
int status = httpCon.getResponseCode();
if (status == 200) {
result = "RegId shared with Application Server. RegId: "
+ regId;
} else {
result = "Post Failure." + " Status: " + status;
}
} finally {
if (httpCon != null) {
httpCon.disconnect();
}
}
} catch (IOException e) {
result = "Post Failure. Error in sharing with App Server.";
Log.e("AppUtil", "Error in sharing with App Server: " + e);
}
return result;
}
}
The above code is used to send the registration ID to the server. But the app never did, so now let’s finish that task too!
4. Create class MainActivity.java:
package com.example.android.gcmapp;
import android.content.Context;
import android.os.AsyncTask;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
public class MainActivity extends Activity {
ShareWithServer appUtil;
String regId;
AsyncTask shareRegidTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appUtil = new ShareWithServer();
regId = getIntent().getStringExtra("regId");
//regId = getIntent().getStringExtra("message");
Log.d("MainActivity", "regId: " + regId);
//getLastIndexOfs=regId.lastIndexOf("s");
//msg=regId.substring(getLastIndexOfs);
//TextView displayTemp = (TextView) findViewById(R.id.lblMessage);
// displayTemp.setText("Temperature is " + regId);
final Context context = this;
shareRegidTask = new AsyncTask() {
@Override
protected Object doInBackground(Object[] params) {
String result = appUtil.shareRegIdWithAppServer(context, regId);
return result;
}
//@Override
protected void onPostExecute(String result) {
shareRegidTask = null;
Toast.makeText(getApplicationContext(), result,
Toast.LENGTH_LONG).show();
}
};
shareRegidTask.execute(null, null, null);
/*Intent newIntent=new Intent(this,DisplayDatabase.class);
newIntent.putExtra("key",regId);
startActivity(newIntent);*/
}
}
Next, add a class GCMNotificationIntentS for notification generation whenever there is a temperature alert.
5. Create class GCMNotificationIntentS.java:
package com.example.android.gcmapp;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
/**
* Created by my on 7/12/2016.
*/
public class GCMNotificationIntentS extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public GCMNotificationIntentS() {
super("GcmIntentService");
}
public static final String TAG = "GCMNotificationIntentS";
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
for (int i = 0; i < 3; i++) {
Log.i(TAG,
"Working... " + (i + 1) + "/5 @ "
+ SystemClock.elapsedRealtime());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
sendNotification("" + extras.get(Connection.MESSAGE_KEY));
Log.i(TAG, "Received: " + extras.toString());
}
}
GcmBroadcastR.completeWakefulIntent(intent);
}
private void sendNotification(String msg) {
Log.d(TAG, "Preparing to send notification...: " + msg);
mNotificationManager = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
/* PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
*/
Intent pendingIntent = new Intent(this, DisplayText.class);
pendingIntent.putExtra("message", msg.substring(msg.lastIndexOf(" ")));
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
pendingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Temperature Notification")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
Log.d(TAG, "Notification sent successfully.");
}
}
6. Create GcmBroacastR.java:
package com.example.android.gcmapp;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
/**
* Created by my on 7/12/2016.
*/
public class GcmBroadcastR extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentS.class.getName());
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
Now we will create an activity where the user will get the temperature reading, and a button on that activity will open the URL where we stored the database of the past temperatures.
7. Create class DisplayText.java:
package com.example.android.gcmapp;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.widget.TextView;
/**
* Created by my on 7/12/2016.
*/
public class DisplayText extends Activity {
String regId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.display_database);
//String data = getIntent().getExtras().getString("key");
regId = getIntent().getStringExtra("message");
TextView display=(TextView)findViewById(R.id.showData);
display.setText(regId);
// db.insertTemp(regId);
}
public void readRecords(View view){
WebView webview = (WebView) findViewById(R.id.webview);
webview.loadUrl("http://192.168.0.117/temp-readings.php");
/*Cursor r= db.getData(1);
TextView display=(TextView)findViewById(R.id.showData);
String show= r.getString(r.getColumnIndex(DatabaseHandler.TEMP_READINGS_VALUE));
display.setText(show);
*/}
}
Now we are almost finished creating the application. You don’t want the app to show the registration prompt every time the app is opened, so you'll need to add the following code. This will register the user’s Android device the first time they open it. Otherwise, the DisplayText.java will open.
8. Create class StartActivity.java :
package com.example.android.gcmapp;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
/**
* Created by my on 7/12/2016.
*/public class StartActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
SharedPreferences settings = getSharedPreferences("prefs", 0);
boolean firstRun = settings.getBoolean("firstRun", false);
if (firstRun == false)//if running for first time
//Splash will load for first time
{
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("firstRun", true);
editor.commit();
Intent i = new Intent(StartActivity.this, Register.class);
startActivity(i);
finish();
} else {
Intent a = new Intent(StartActivity.this, DisplayText.class);
startActivity(a);
finish();
}
}
}
Now, download and paste the Android Manifest file, the three layout files we have used:
- activity_main.xml
- activity_register.xml
- display_database.xml
And other files ...
- styles.xml
- strings.xml
- dimens.xml
... from xmlFiles.zip. Alternatively, instead of building the project from scratch, you can download all the project files from here:
Android App.
Edit your styles.xml, strings.xml, dimens.xml, etc. accordingly.
Note: You should add images to the drawable folder.
Yay! We are finally done with the Android app. I hope you were able to develop the app by following the steps. On first open, your app should look like this:
First, register your app with Google GCM and then send a GCM-RegId with the server. After that, you will see this:
Now, whenever there's a notification or you open your app again, you will be directed to this:
If there is a notification, it will be displayed above the records.
Congratulations! You have completed the tutorial. We hope you are able to develop the project with minimal problems.