Hey all, so I decided I would go ahead and post all the messy code I used to create my senior project!
First of all, the project has three main components: A "Master App," a "Non-Master App," and the microcontroller. The two apps mentioned are both Android applications that use the Amarino Toolkit library in order to communicate with the microcontroller via Bluetooth. The microcontroller is an Arduino Uno R3 with a SeeedStudio Bluetooth Shield.
The idea behind the project is that the Master App can connect to the microcontroller automatically, unlock the car door, and change settings while connected. These settings include: Allowing/Disallowing Non-Master Apps to connect, and setting a pass-code. Right now, the pass code is only five digits long (just digits, not letters or other symbols). The Non-Master App displays a GUI for the user to input the Device Address of the Bluetooth shield and the pass code. If everything checks out, the non-master app is allowed to unlock/lock the vehicle.
Please note that EVERYTHING is in its prototype version and will likely stay that way, as I don't intend to keep working on this. For example, when a Master App user intends to change the five digit pass code, I didn't use any exception handling if they tried to enter more or less than 5 digits. My engineering teacher doesn't know programming, so all I had to do was demonstrate that it worked. Therefore, the methods I used to accomplish all of this were a bit messy. I also rarely commented (bad idea, I know).
Settings, such as whether or not non-master app users are allowed to connect and the five digit pass code, are stored in EEPROM memory on the microcontroller. EEPROM memory, for our purposes, can be thought of as a VERY small hard drive on a microcontroller. The data will be stored in the memory even if the microcontroller shuts off.
As far as formatting this post, and placing this post in a specific section, this project used Java, XML, and Arduino programming. Therefore, I wasn't sure whether I should post this in the Java section, the Other section, or the Code Library. But the project is mainly Java, so I posted it here. I'll let the Moderators decide whether or not to move it. Sorry! I highlighted the Java and XML code snippets appropriately, and I highlighted the Arduino code in C because it's basically the same syntax.
Alright, let's get to the code. The best way to program Android applications is with Eclipse and the Android SDK plugins installed. This is all available in one package on the Android website.
The Master App The first feature I needed to implement in the Master App was starting the App when the Android device booted up. In order to do this, I had to use a Broadcast Receiver. Android Apps use Intents to send messages to the rest of the device, and any application that implements a receiver to that specific Intent will be notified when the Intent is sent. This allows developers to act on it. For this feature, I needed to receive the "android.intent.action.BOOT_COMPLETED" intent. So, I declare the Receiver in my AndroidManifest.xml file here:
<receiver android:name="BootupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
This means that the BootupReceiver.java class is where the code for the Broadcast Receiver is located. Let's take a look:
package grabb.corp.ikey2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import at.abraxas.amarino.Amarino;
public class BootupReceiver extends BroadcastReceiver{
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
public void onReceive(Context context, Intent intent) {
Amarino.connect(context, DEVICE_ADDRESS);
}
}
What this code does is wait for the phone to finish booting up, and connect to the microcontroller once it has. We use yet another Broadcast Receiver to act after a connection has been made:
<receiver android:name="ConnectionReceived">
<intent-filter>
<action android:name="amarino.intent.action.CONNECTED" />
</intent-filter>
</receiver>
As a side note, I also implemented Broadcast Receivers to catch when a connection fails or is disconnected, and I just retry the connection if this happens. Not the best way to do it, but whatever.
Let's take a look at what happens in ConnectionReceived.java:
package grabb.corp.ikey2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import at.abraxas.amarino.Amarino;
public class ConnectionReceived extends BroadcastReceiver{
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
@Override
public void onReceive(Context context, Intent intent) {
try {
Thread.sleep(4000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
//send dummy values
Amarino.sendDataToArduino(context, DEVICE_ADDRESS, 'd', 1);
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(context, DEVICE_ADDRESS, 'd', 1);
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(context, DEVICE_ADDRESS, 'd', 1);
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(context, DEVICE_ADDRESS, 'd', 1);
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
//authorize connection
Amarino.sendDataToArduino(context, DEVICE_ADDRESS, 'A', 1);
}
}
What the code above does is authorize the connection. Basically, it sends character codes to the micrcontroller. The microcontroller receives these character codes and has a corresponding function registered to each character code. You'll see more of this later in the Arduino code. First, it sends 'd' four times, and I don't necessarily remember why I did that. 'd' has no function registered on the Arduino, and I used it as a dummy message I guess.
Anyway, 'A' is the character code I used in order to authorize the connection. The Android device sends that character code once a connection has been made, and responds accordingly. But what happens when messages are sent from the Arduino to the Android device? You guessed it, we'll need to use a Broadcast Receiver to catch the messages:
<receiver android:name="MessageReceived">
<intent-filter>
<action android:name="amarino.intent.action.RECEIVED" />
</intent-filter>
</receiver>
Let's take a look at MessageReceived.java:
package grabb.corp.ikey2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import at.abraxas.amarino.Amarino;
import at.abraxas.amarino.AmarinoIntent;
public class MessageReceived extends BroadcastReceiver{
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
public void onReceive(Context context, Intent intent) {
Intent it = new Intent("grabb.corp.ikey2.updated");
String data = null;
final String address = intent.getStringExtra(AmarinoIntent.EXTRA_DEVICE_ADDRESS);
final int dataType = intent.getIntExtra(AmarinoIntent.EXTRA_DATA_TYPE, -1);
if (dataType == AmarinoIntent.STRING_EXTRA) {
data = intent.getStringExtra(AmarinoIntent.EXTRA_DATA);
if (data != null) {
if(data.equals("1")) {
IKey.connected = true;
Intent i = new Intent(context, IKey.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
if(data.equals("3")) { //connection from other devices is allowed
IKeySettings.ALLOWED = 1;
}
if(data.equals("4")) { //connection from other devices is NOT allowed
IKeySettings.ALLOWED = 0;
}
else { //if no other options, must be returning pass code
IKeySettings.PASSCODE = data;
if(IKeySettings.PASSCODE.length() == 5 && IKeySettings.ALLOWED != 5) {
context.sendBroadcast(it);
}
}
}
}
}
}
This class handles all of the messages possible to be received from the Arduino to the Master App. The Arduino sends "1" if the connection is authorized, "3" if the Non-Master Apps are currently allowed to connect, "4" if they are not, and any other message is considered to be the current pass-code being sent. This is fine, since we are in complete control of all the messages being sent by the Arduino.
In this case, we just received "1." What the code for "1" does is start another Activity (Android's GUI). This GUI is called IKey.class:
package grabb.corp.ikey2;
import grabb.corp.ikey2.ConnectionReceived;
import android.os.Bundle;
import android.os.PowerManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IntentFilter;
import android.view.Menu;
import at.abraxas.amarino.Amarino;
import at.abraxas.amarino.AmarinoIntent;
public class IKey extends Activity {
public static PowerManager pm;
private PowerManager.WakeLock wl;
Context receiveContext;
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
Activity IKEYActivity = this;
public static boolean connected = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ikey);
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = IKey.pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "iKey Connection Acquired");
wl.acquire();
receiveContext = this;
try {
Thread.sleep(5000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
Amarino.sendDataToArduino(receiveContext, DEVICE_ADDRESS, 'a', 1);
Amarino.sendDataToArduino(receiveContext, DEVICE_ADDRESS, 'a', 1);
Amarino.sendDataToArduino(receiveContext, DEVICE_ADDRESS, 'a', 1);
Amarino.sendDataToArduino(receiveContext, DEVICE_ADDRESS, 'a', 1);
Amarino.sendDataToArduino(receiveContext, DEVICE_ADDRESS, 'a', 1);
dialog.cancel();
wl.release();
IKEYActivity.finish();
break;
case DialogInterface.BUTTON_NEGATIVE:
dialog.cancel();
wl.release();
IKEYActivity.finish();
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Would you like to unlock your car?").setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_ikey, menu);
return true;
}
}
All this Activity does is display a message box asking the user if they would like to unlock their vehicle. If yes, the character code 'a' is sent to the Arduino, which signals the Arduino to unlock the vehicle. If no, nothing happens. The Activity is closed at the end either way.
Finally, we have the Activity used to change settings, which is what appears when the user would go and find the App in their list of Apps and click on it. Here is IKeySettings.java:
package grabb.corp.ikey2;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import at.abraxas.amarino.Amarino;
public class IKeySettings extends Activity {
public static int ALLOWED = 5;
public static String PASSCODE = "12346";
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
private BroadcastReceiver dataUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
IKeySettings.this.dataUpdated(intent);
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter iff2 = new IntentFilter();
iff2.addAction("grabb.corp.ikey2.updated");
this.registerReceiver(this.dataUpdated,iff2);
if(IKey.connected == true) {
setContentView(R.layout.activity_ikeysettings);
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, DEVICE_ADDRESS, 'D', 1); //getPinCode
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
final EditText passCode = (EditText) findViewById(R.id.editText1);
final RadioButton noRadio = (RadioButton) findViewById(R.id.radioButton2);
final RadioButton yesRadio = (RadioButton) findViewById(R.id.radioButton1);
final Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(noRadio.isChecked()) {
Amarino.sendDataToArduino(IKeySettings.this, DEVICE_ADDRESS, 'E', 0); //set not allowed
ALLOWED = 0;
}
else if(yesRadio.isChecked()) {
Amarino.sendDataToArduino(IKeySettings.this, DEVICE_ADDRESS, 'E', 1); //set allowed
ALLOWED = 1;
}
String passCodeText = passCode.getText().toString();
int[] intArray = new int[passCodeText.length()];
for (int i = 0; i < passCodeText.length(); i++) {
intArray[i] = Character.digit(passCodeText.charAt(i), 10);
}
Amarino.sendDataToArduino(IKeySettings.this, DEVICE_ADDRESS, 'F', intArray); //set allowed
PASSCODE = "";
PASSCODE += intArray[0];
PASSCODE += intArray[1];
PASSCODE += intArray[2];
PASSCODE += intArray[3];
PASSCODE += intArray[4];
try {
Thread.sleep(5000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
AlertDialog alertDialog = new AlertDialog.Builder(IKeySettings.this).create();
alertDialog.setTitle("Settings Saved!");
alertDialog.setCancelable(false);
alertDialog.setMessage("Your new settings have been saved successfully!");
alertDialog.setButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
alertDialog.show();
passCode.setText(PASSCODE);
if(ALLOWED == 0) {
noRadio.setChecked(true);
}
else if(ALLOWED == 1) {
yesRadio.setChecked(true);
}
}
});
}
else {
AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle("No Connection!");
alertDialog.setCancelable(false);
alertDialog.setMessage("In order to alter settings, this Android device must be connected to IKey!");
alertDialog.setButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
IKeySettings.this.finish();
}
});
alertDialog.show();
}
}
void dataUpdated(Intent i) {
final EditText passCode = (EditText) findViewById(R.id.editText1);
passCode.setText(PASSCODE);
final RadioButton noRadio = (RadioButton) findViewById(R.id.radioButton2);
final RadioButton yesRadio = (RadioButton) findViewById(R.id.radioButton1);
if(ALLOWED == 0) {
noRadio.setChecked(true);
}
else if(ALLOWED == 1) {
yesRadio.setChecked(true);
}
}
}
This code is a tiny bit cleaner than the rest. When it is started, it sends requests to the Arduino for the settings stored in the EEPROM memory ('C' signals request for allowed/disallowed setting, 'D' signals for pass code). Once these are updated, the current settings are displayed. The user can then change the settings, and send them to the Arduino. Arduino received these messages along with the updated information, and sends back a success value. Whether or not the settings were saved successfully is displayed in a messagebox. Here is a screenshot of the simple GUI:
Here is the full AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="grabb.corp.ikey2"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="8" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="grabb.corp.ikey2.IKey"
android:label="@string/app_name"
android:theme="@style/Theme.Transparent">
</activity>
<activity
android:name="grabb.corp.ikey2.IKeySettings"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="BootupReceiver">
<intent-filter>
<action
android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
<receiver android:name="ConnectionReceived">
<intent-filter>
<action
android:name="amarino.intent.action.CONNECTED" />
</intent-filter>
</receiver>
<receiver android:name="ConnectionFailed">
<intent-filter>
<action
android:name="amarino.intent.action.CONNECTION_FAILED" />
<action
android:name="amarino.intent.action.DISCONNECTED" />
</intent-filter>
</receiver>
<receiver android:name="MessageReceived">
<intent-filter>
<action
android:name="amarino.intent.action.RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
The Non-Master App My code for the Non-Master App is much more simple and clean. Here is a picture of the GUI:
The Non-Master App basically displays a login form. The user enters the Device Address and pass code to connect to the Arduino, and waits for authorization. If the connection is authorized, the Unlock/Lock buttons are enabled,
the Device Address is remembered, and the user may unlock/lock the vehicle. If the connection is not authorized for any reason, a messagebox tells this to the user. There are only two java files for this App: IKeyNonMaster.java and messageReceived.java. messageReceived.java uses a Broadcast Receiver the same way the Master App does.
When the user tries to authorize a connection, the Android device simply requests the stored settings from the Arduino. Once the settings are received, the Android App compared them with the info entered in by the user. If everything matches, authorization is successful.
Here is the code for IKeyNonMaster.java:
package grabb.corp.ikey;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import at.abraxas.amarino.Amarino;
public class IKeyNonMaster extends Activity {
public static String deviceAddr = null;
public static String pin = "11111";
public static String receivedPin = "98";
public static int receivedAllowed = 5;
private BroadcastReceiver connectionReceived = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
IKeyNonMaster.this.receivedConnection(intent);
}
};
private BroadcastReceiver dataUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
IKeyNonMaster.this.dataUpdated(intent);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ikey_non_master);
final EditText deviceAddrText = (EditText) findViewById(R.id.editText1);
final EditText pinText = (EditText) findViewById(R.id.editText2);
final Button connectBtn = (Button) findViewById(R.id.button1);
final Button unlockBtn = (Button) findViewById(R.id.unlockBtn);
final Button lockBtn = (Button) findViewById(R.id.lockBtn);
SharedPreferences settings = getSharedPreferences("PrevDevice", 0);
String prevDevice = settings.getString("prevDevice", "");
deviceAddrText.setText(prevDevice);
unlockBtn.setEnabled(false);
lockBtn.setEnabled(false);
IntentFilter iff = new IntentFilter();
iff.addAction("amarino.intent.action.CONNECTED");
// Put whatever message you want to receive as the action
this.registerReceiver(this.connectionReceived,iff);
IntentFilter iff2 = new IntentFilter();
iff2.addAction("grabb.corp.ikey.updated");
// Put whatever message you want to receive as the action
this.registerReceiver(this.dataUpdated,iff2);
final Context thisContext = this;
connectBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
deviceAddr = deviceAddrText.getText().toString();
pin = pinText.getText().toString();
if(connectBtn.getText().toString().equals("Connect")) {
if(deviceAddr.length() == 17) {
if(deviceAddr.charAt(2) == ':' && deviceAddr.charAt(5) == ':' && deviceAddr.charAt(8) == ':' && deviceAddr.charAt(11) == ':' && deviceAddr.charAt(2) == ':') {
connectBtn.setEnabled(false);
deviceAddrText.setEnabled(false);
pinText.setEnabled(false);
connectBtn.setText("Connecting...");
Amarino.connect(thisContext, deviceAddr);
}
}
}
else if(connectBtn.getText().toString().equals("Disconnect")) {
Amarino.disconnect(thisContext, deviceAddr);
connectBtn.setText("Connect");
deviceAddrText.setEnabled(true);
pinText.setEnabled(true);
unlockBtn.setEnabled(false);
lockBtn.setEnabled(false);
}
}
});
unlockBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Amarino.sendDataToArduino(thisContext, deviceAddr, 'a', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'a', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'a', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'a', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'a', 1);
}
});
lockBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Amarino.sendDataToArduino(thisContext, deviceAddr, 'b', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'b', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'b', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'b', 1);
Amarino.sendDataToArduino(thisContext, deviceAddr, 'b', 1);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_ikey_non_master, menu);
return true;
}
private void receivedConnection(Intent i) {
try {
Thread.sleep(3000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
//validation
Amarino.sendDataToArduino(this, deviceAddr, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, deviceAddr, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, deviceAddr, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, deviceAddr, 'C', 1); //getAllowed
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
Amarino.sendDataToArduino(this, deviceAddr, 'D', 1); //getPinCode
try {
Thread.sleep(500);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
private void dataUpdated(Intent i) {
final EditText deviceAddrText = (EditText) findViewById(R.id.editText1);
final EditText pinText = (EditText) findViewById(R.id.editText2);
final Button connectBtn = (Button) findViewById(R.id.button1);
final Button unlockBtn = (Button) findViewById(R.id.unlockBtn);
final Button lockBtn = (Button) findViewById(R.id.lockBtn);
connectBtn.setText("Disconnect");
connectBtn.setEnabled(true);
if(receivedAllowed == 1 && pin.equals(receivedPin)) {
//authorized, continue with connection
SharedPreferences settings = getSharedPreferences("PrevDevice", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("prevDevice", deviceAddr);
editor.commit();
unlockBtn.setEnabled(true);
lockBtn.setEnabled(true);
}
else {
//unauthorized
AlertDialog alertDialog1 = new AlertDialog.Builder(IKeyNonMaster.this).create();
alertDialog1.setTitle("Unauthorized!");
alertDialog1.setCancelable(false);
alertDialog1.setMessage("Either you entered the wrong pin, or non-master devices are not allowed to connect to this device.");
alertDialog1.setButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
alertDialog1.show();
connectBtn.setText("Connect");
Amarino.disconnect(this, deviceAddr);
deviceAddrText.setEnabled(true);
pinText.setEnabled(true);
}
}
}
Here is the code for messageReceived.java:
package grabb.corp.ikey;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import at.abraxas.amarino.AmarinoIntent;
public class messageReceived extends BroadcastReceiver{
final String DEVICE_ADDRESS = "00:13:EF:00:06:79";
public void onReceive(Context context, Intent intent) {
Intent i = new Intent("grabb.corp.ikey.updated");
String data = null;
final String address = intent.getStringExtra(AmarinoIntent.EXTRA_DEVICE_ADDRESS);
final int dataType = intent.getIntExtra(AmarinoIntent.EXTRA_DATA_TYPE, -1);
if (dataType == AmarinoIntent.STRING_EXTRA) {
data = intent.getStringExtra(AmarinoIntent.EXTRA_DATA);
if (data != null) {
if(data.equals("1")) {
//authorized
}
if(data.equals("3")) { //connection from other devices is allowed
IKeyNonMaster.receivedAllowed = 1;
}
if(data.equals("4")) { //connection from other devices is NOT allowed
IKeyNonMaster.receivedAllowed = 0;
}
else { //if no other options, must be returning pass code
IKeyNonMaster.receivedPin = data;
if(IKeyNonMaster.receivedPin.length() == 5 && IKeyNonMaster.receivedAllowed != 5) {
context.sendBroadcast(i);
}
}
}
}
}
}
And finally, here is the AndroidManifest.xml file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="grabb.corp.ikey"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="8" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" android:allowTaskReparenting="false">
<activity
android:name="grabb.corp.ikey.IKeyNonMaster"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="messageReceived">
<intent-filter>
<action
android:name="amarino.intent.action.RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
The Arduino The Arduino is much simpler. Upon being turned it, it sends a couple of messages to the Bluetooth shield for initialization, and then listens for messages from Android Apps. The Android Apps send character codes for each message it would like to send, and the Arduino responds accordingly. Here is the code:
#include <MeetAndroid.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>
MeetAndroid meetAndroid(6,7, 57600);
// CHANGE THE PIN NUMBER ACCORDING TO THE PIN THAT THE
//VIBRATOR IS ON
int vibratorPin = 13;
int lockPin = 8;
void setup() {
//This shit needs to be changed depending on the configuration
//of the bluetooth module. It's the baud rate.
Serial.begin(57600);
//two functions that we receive from android:
//start alarm, and stop alarm
meetAndroid.registerFunction(startAlarm, 'a');
meetAndroid.registerFunction(stopAlarm, 'b');
meetAndroid.registerFunction(authorizeMaster, 'A');
meetAndroid.registerFunction(authorizeNonMaster, 'B');
meetAndroid.registerFunction(getAllowed, 'C');
meetAndroid.registerFunction(getPass, 'D');
meetAndroid.registerFunction(setAllowed, 'E');
meetAndroid.registerFunction(setPass, 'F');
pinMode(vibratorPin, OUTPUT);
pinMode(lockPin, OUTPUT);
meetAndroid.send_plain("\r\n+STWMOD=0\r\n"); //set the bluetooth work in slave mode
meetAndroid.send_plain("\r\n+STNA=SeeedBTSlave\r\n"); //set the bluetooth name as "SeeedBTSlave"
meetAndroid.send_plain("\r\n+STOAUT=1\r\n"); // Permit Paired device to connect me
meetAndroid.send_plain("\r\n+STAUTO=0\r\n"); // Auto-connection should be forbidden here
delay(2000);
meetAndroid.send_plain("\r\n+INQ=1\r\n"); //make the slave bluetooth inquirable
}
void loop() {
meetAndroid.receive(); //receive Android messages
}
// startAlarm: receives int from Android and verifies
// that the android device is indeed communicating
// with the arduino. Once confirmed, the function
// gives the vibratorPin 5 volts
void startAlarm(byte flag, byte numOfValues) {
digitalWrite(vibratorPin, HIGH);
delay(1000);
digitalWrite(vibratorPin, LOW);
}
// stopAlarm: receives int from Android and verifies
// that the android device is indeed communicating
// with the arduino. Once confirmed, the function
// turns the vibratorPin off
void stopAlarm(byte flag, byte numOfValues) {
digitalWrite(lockPin, HIGH);
delay(1000);
digitalWrite(lockPin, LOW);
}
void authorizeMaster(byte flag, byte numOfValues) {
int allow = 1;
String allowed;
allowed += allow;
char charBuf[2];
allowed.toCharArray(charBuf, 2);
meetAndroid.send(charBuf);
}
void authorizeNonMaster(byte flag, byte numOfValues) {
int allowed = EEPROM.read(1);
if(allowed == 1) {
int data[numOfValues];
int currentVal;
int readVal;
int success = 1;
int fail = 0;
boolean codeMatches = true;
meetAndroid.getIntValues(data);
for (int i=0; i<numOfValues;i++)
{
readVal = i + 2;
currentVal = EEPROM.read(readVal);
if(data[i] != currentVal) {
codeMatches = false;
}
}
if(codeMatches == true) {
meetAndroid.send(success);
}
if(codeMatches == false) {
meetAndroid.send(fail);
}
}
else {
meetAndroid.send(0);
}
}
void getAllowed(byte flag, byte numOfValues) {
int allowed = EEPROM.read(1);
int allowedSuccess = 3;
int allowedFailure = 4;
if(allowed == 0) {
meetAndroid.send(allowedFailure);
}
else if(allowed == 1) {
meetAndroid.send(allowedSuccess);
}
}
void getPass(byte flag, byte numOfValues) {
String pass;
pass += EEPROM.read(2);
pass += EEPROM.read(3);
pass += EEPROM.read(4);
pass += EEPROM.read(5);
pass += EEPROM.read(6);
char charBuf[6];
pass.toCharArray(charBuf, 6);
meetAndroid.send(charBuf);
}
void setAllowed(byte flag, byte numOfValues) {
int value = meetAndroid.getInt();
if(value == 1) {
EEPROM.write(1, 1);
}
if(value == 0) {
EEPROM.write(1, 0);
}
}
void setPass(byte flag, byte numOfValues) {
int data[numOfValues];
meetAndroid.getIntValues(data);
int EEPROMaddr;
for (int i=0; i<numOfValues;i++)
{
EEPROMaddr = i+2;
EEPROM.write(EEPROMaddr, data[i]);
}
}
A ton of this is actually unnecessary. For example, the authorizeNonMaster() function above is never used. I wrote this code before the Non-Master App, and when I got to writing the Non-Master App I realized it would be much easier to just request the information from the Arduino and compare values on the Android side instead of the Arduino side. But yeah, anyway, the unlock and locking functions simply give voltage to a pin for 1 second. These pins were supposed to be hooked up to the electric locking/unlocking systems of a vehicle, but all they did was turn red and green LEDs on and off for the demonstration I did.
Also, you might notice that the functions for unlocking and locking are named "startAlarm" and "stopAlarm." This is because last year, when a buddy of mine was doing
his senior project, he used the same Bluetooth Android/Arduino connection concept to create an alarm system that vibrated a bracelet. It was intended for deaf people, and the Alarm system was ran from an Android App. I did all the programming for that project (he's a really good buddy of mine, and I was interested in it), so I copy+pasted some of the code from that project for this one. Later on, I might post that project too, but it was much more buggy than this one.
I posted all of this in a big rush, so if you have any questions or feel like I left anything out, let me know! I'll be re-reading all of this a bit later because for some reason whenever I post on forums like this I make five year old grammar mistakes.
Thank you! This code is messy as hell, but as far as I'm concerned: connecting, changing the settings, authorizing Apps, lighting up the LEDs, and everything else I needed for the demonstration worked perfectly!
Credit: Countless Google Searches helped me out with this project. I used a modified MeetAndroid library for my Arduino code, which can be found here:
https://github.com/eliotk/meetandroid-softserial The modified version let me communicate with the Bluetooth Shield (it's all the send_plain business at the beginning). The rest of the credit is to random Google Searches, the Android Developer website, the Amarino Toolkit, etc.