-
Notifications
You must be signed in to change notification settings - Fork 265
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added ability to read direct sensor data to Android library
- Loading branch information
Showing
4 changed files
with
150 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<classpath> | ||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> | ||
<classpathentry kind="src" path="src"/> | ||
<classpathentry kind="src" path="gen"/> | ||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> | ||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> | ||
<classpathentry kind="output" path="bin/classes"/> | ||
</classpath> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,11 @@ | |
* for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" | ||
* | ||
* Released under GNU GPL (General Public License) v3.0 | ||
* Copyright (C) 2013 Peter Bartz | ||
* Copyright (C) 2013 Peter Bartz [http://ptrbrtz.net] | ||
* Copyright (C) 2011-2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin | ||
* Written by Peter Bartz ([email protected]) | ||
* | ||
* Infos, updates, bug reports and feedback: | ||
* Infos, updates, bug reports, contributions and feedback: | ||
* https://github.com/ptrbrtz/razor-9dof-ahrs | ||
******************************************************************************************/ | ||
|
||
|
@@ -34,8 +34,8 @@ | |
* <p> | ||
* Bluetooth seems to be even more picky on Android than it is anyway. Be sure to have a look at the | ||
* section about Android Bluetooth in the tutorial at | ||
* <a href="http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs"> | ||
* http://dev.qu.tu-berlin.de/projects/sf-razor-9dof-ahrs</a>! | ||
* <a href="https://github.com/ptrbrtz/razor-9dof-ahrs"> | ||
* https://github.com/ptrbrtz/razor-9dof-ahrs</a>! | ||
* <p> | ||
* The app using this class has to | ||
* <ul> | ||
|
@@ -57,67 +57,108 @@ public class RazorAHRS { | |
private static final boolean DEBUG = false; | ||
private static final String SYNCH_TOKEN = "#SYNCH"; | ||
private static final String NEW_LINE = "\r\n"; | ||
|
||
// Timeout to init Razor AHRS after a Bluetooth connection has been established | ||
public static final int INIT_TIMEOUT_MS = 5000; | ||
|
||
// IDs passed to internal message handler | ||
private static final int MSG_ID__YPR_DATA = 0; | ||
private static final int MSG_ID__IO_EXCEPTION_AND_DISCONNECT = 1; | ||
private static final int MSG_ID__CONNECT_OK = 2; | ||
private static final int MSG_ID__CONNECT_FAIL = 3; | ||
private static final int MSG_ID__CONNECT_ATTEMPT = 4; | ||
|
||
private static final int MSG_ID__AMG_DATA = 5; | ||
|
||
private static final UUID UUID_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); | ||
|
||
|
||
/** | ||
* Razor output modes. | ||
* Use <code>YAW_PITCH_ROLL_ANGLES</code> to receive yaw, pitch and roll in degrees. <br> | ||
* Use <code>RAW_SENSOR_DATA</code> or <code>CALIBRATED_SENSOR_DATA</code> to read raw or | ||
* calibrated xyz sensor data of the accelerometer, magnetometer and the gyroscope. | ||
*/ | ||
public enum RazorOutputMode { | ||
YAW_PITCH_ROLL_ANGLES, | ||
RAW_SENSOR_DATA, | ||
CALIBRATED_SENSOR_DATA | ||
} | ||
private RazorOutputMode razorOutputMode; | ||
|
||
private enum ConnectionState { | ||
DISCONNECTED, | ||
CONNECTING, | ||
CONNECTED, | ||
USER_DISCONNECT_REQUEST | ||
} | ||
volatile private ConnectionState connectionState = ConnectionState.DISCONNECTED; | ||
|
||
volatile private BluetoothSocket btSocket; | ||
volatile private BluetoothDevice btDevice; | ||
volatile private InputStream inStream; | ||
volatile private OutputStream outStream; | ||
|
||
private RazorListener razorListener; | ||
private boolean callbacksEnabled = true; | ||
|
||
BluetoothThread btThread; | ||
|
||
private int numConnectAttempts; | ||
|
||
// Object pools | ||
ObjectPool<float[]> float3Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() { | ||
private ObjectPool<float[]> float3Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() { | ||
@Override | ||
public float[] newObject() { | ||
return new float[3]; | ||
} | ||
}); | ||
private ObjectPool<float[]> float9Pool = new ObjectPool<float[]>(new ObjectPool.ObjectFactory<float[]>() { | ||
@Override | ||
public float[] newObject() { | ||
return new float[9]; | ||
} | ||
}); | ||
|
||
/** | ||
* Constructor. | ||
* Must be called from the thread where you want receive the RazorListener callbacks! So if you | ||
* want to manipulate Android UI from the callbacks you have to call this from your main/UI | ||
* want to manipulate Android UI from the callbacks you have to call this from your main/UI | ||
* thread. | ||
* | ||
* | ||
* @param btDevice {@link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor | ||
* AHRS to connect to. | ||
* @param razorListener {@link RazorListener} that will be notified of Razor AHRS events. | ||
* @throws RuntimeException thrown if one of the parameters is null. | ||
*/ | ||
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener) | ||
throws RuntimeException { | ||
this(btDevice, razorListener, RazorOutputMode.YAW_PITCH_ROLL_ANGLES); | ||
} | ||
|
||
/** | ||
* Constructor. | ||
* Must be called from the thread where you want receive the RazorListener callbacks! So if you | ||
* want to manipulate Android UI from the callbacks you have to call this from your main/UI | ||
* thread. | ||
* | ||
* @param btDevice {@link android.bluetooth.BluetoothDevice BluetoothDevice} holding the Razor | ||
* AHRS to connect to. | ||
* @param razorListener {@link RazorListener} that will be notified of Razor AHRS events. | ||
* @param razorOutputMode {@link RazorOutputMode} that you desire. | ||
* @throws RuntimeException thrown if one of the parameters is null. | ||
*/ | ||
public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener, RazorOutputMode razorOutputMode) | ||
throws RuntimeException { | ||
if (btDevice == null) | ||
throw new RuntimeException("BluetoothDevice can not be null."); | ||
this.btDevice = btDevice; | ||
|
||
if (razorListener == null) | ||
throw new RuntimeException("RazorListener can not be null."); | ||
this.razorListener = razorListener; | ||
|
||
if (razorOutputMode == null) | ||
throw new RuntimeException("RazorMode can not be null."); | ||
this.razorOutputMode = razorOutputMode; | ||
} | ||
|
||
/** | ||
|
@@ -126,15 +167,15 @@ public RazorAHRS(BluetoothDevice btDevice, RazorListener razorListener) | |
public boolean getCallbacksEnabled() { | ||
return callbacksEnabled; | ||
} | ||
|
||
/** | ||
* Enables/disables listener callbacks. | ||
* @param enabled | ||
*/ | ||
public void setCallbacksEnabled(boolean enabled) { | ||
callbacksEnabled = enabled; | ||
} | ||
|
||
/** | ||
* Connect and start reading. Both is done asynchronously. {@link RazorListener#onConnectOk()} | ||
* or {@link RazorListener#onConnectFail(IOException)} callbacks will be invoked. | ||
|
@@ -151,7 +192,7 @@ public void asyncConnect(int numConnectAttempts) { | |
btThread.join(); | ||
} catch (InterruptedException e) { } | ||
} | ||
|
||
// Bluetooth thread not running any more, we're definitely in DISCONNECTED state now | ||
|
||
// Create new thread to connect to Razor AHRS and read input | ||
|
@@ -172,14 +213,14 @@ public void asyncDisconnect() { | |
// Don't go to USER_DISCONNECT_REQUEST state if we are disconnected already | ||
if (connectionState == ConnectionState.DISCONNECTED) | ||
return; | ||
|
||
// This is a wanted disconnect, so we force (blocking) I/O to break | ||
connectionState = ConnectionState.USER_DISCONNECT_REQUEST; | ||
closeSocketAndStreams(); | ||
} | ||
if (DEBUG) Log.d(TAG, "asyncDisconnect() END"); | ||
} | ||
|
||
/** | ||
* Writes out a string using ASCII encoding. Assumes we're connected. Does not handle | ||
* exceptions itself. | ||
|
@@ -201,7 +242,7 @@ private void closeSocketAndStreams() { | |
if (outStream != null) | ||
write("#o0"); | ||
} catch (IOException e) { } | ||
|
||
// Close Bluetooth socket => I/O operations immediately will throw exception | ||
try { | ||
if (btSocket != null) | ||
|
@@ -248,6 +289,22 @@ private byte readByte() throws IOException { | |
return (byte) in; | ||
} | ||
|
||
/** | ||
* Converts a buffer of bytes to an array of floats. This method does not do any error | ||
* checking on parameter array sizes. | ||
* @param byteBuf Byte buffer with length of at least <code>numFloats * 4</code>. | ||
* @param floatArr Float array with length of at least <code>numFloats</code>. | ||
* @param numFloats Number of floats to convert | ||
*/ | ||
private void byteBufferToFloatArray(byte[] byteBuf, float[] floatArr, int numFloats) { | ||
//int numFloats = byteBuf.length / 4; | ||
for (int i = 0; i < numFloats * 4; i += 4) { | ||
// Convert from little endian (Razor) to big endian (Java) and interpret as float | ||
floatArr[i/4] = Float.intBitsToFloat((byteBuf[i] & 0xff) + ((byteBuf[i+1] & 0xff) << 8) + | ||
((byteBuf[i+2] & 0xff) << 16) + ((byteBuf[i+3] & 0xff) << 24)); | ||
} | ||
} | ||
|
||
/** | ||
* Parse input stream for given token. | ||
* @param token Token to find | ||
|
@@ -319,7 +376,7 @@ private void initRazor() throws IOException { | |
write("#ob#o1#oe0#s" + configSynchID); | ||
while (!readToken(configSynchReply, readByte())) { } | ||
} | ||
|
||
/** | ||
* Opens Bluetooth connection to Razor AHRS. | ||
* @throws IOException | ||
|
@@ -331,7 +388,7 @@ private void connect() throws IOException { | |
if (DEBUG) Log.d(TAG, "btSocket is null in connect()"); | ||
throw new IOException("Could not create Bluetooth socket"); | ||
} | ||
|
||
// This could be used to create the RFCOMM socekt on older Android devices where | ||
//createRfcommSocketToServiceRecord is not present yet. | ||
/*try { | ||
|
@@ -340,13 +397,13 @@ private void connect() throws IOException { | |
} catch (Exception e) { | ||
throw new IOException("Could not create Bluetooth socket using reflection"); | ||
}*/ | ||
|
||
// Connect socket to Razor AHRS | ||
if (DEBUG) Log.d(TAG, "Canceling bt discovery"); | ||
BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); // Recommended | ||
if (DEBUG) Log.d(TAG, "Trying to connect() btSocket"); | ||
btSocket.connect(); | ||
|
||
// Get the input and output streams | ||
if (DEBUG) Log.d(TAG, "Trying to create streams"); | ||
inStream = btSocket.getInputStream(); | ||
|
@@ -368,7 +425,7 @@ public void run() { | |
if (DEBUG) Log.d(TAG, "btDevice is null in run()"); | ||
throw new IOException("Bluetooth device is null"); | ||
} | ||
|
||
// Make several attempts to connect | ||
int i = 1; | ||
while (true) { | ||
|
@@ -382,17 +439,17 @@ public void run() { | |
// Maximum number of attempts reached or cancel requested? | ||
if (i == numConnectAttempts || connectionState == ConnectionState.USER_DISCONNECT_REQUEST) | ||
throw e; | ||
|
||
// We couldn't connect on first try, manually starting Bluetooth discovery | ||
// often helps | ||
if (DEBUG) Log.d(TAG, "Starting BT discovery"); | ||
BluetoothAdapter.getDefaultAdapter().startDiscovery(); | ||
delay(5000); // 5 seconds - long enough? | ||
|
||
i++; | ||
} | ||
} | ||
|
||
// Set Razor output mode | ||
if (DEBUG) Log.d(TAG, "Trying to set Razor output mode"); | ||
initRazor(); | ||
|
@@ -408,26 +465,35 @@ public void run() { | |
|
||
// Tell listener we're ready | ||
sendToParentThread(MSG_ID__CONNECT_OK, null); | ||
|
||
// Keep reading inStream until an exception occurs | ||
if (DEBUG) Log.d(TAG, "Starting input loop"); | ||
while (true) { | ||
// Read byte from input stream | ||
inBuf[inBufPos++] = (byte) readByte(); | ||
|
||
if (inBufPos == 12) { // We received a full frame | ||
float[] ypr = float3Pool.get(); | ||
|
||
// Convert from little endian (Razor) to big endian (Java) and interpret as float | ||
ypr[0] = Float.intBitsToFloat((inBuf[0] & 0xff) + ((inBuf[1] & 0xff) << 8) + ((inBuf[2] & 0xff) << 16) + ((inBuf[3] & 0xff) << 24)); | ||
ypr[1] = Float.intBitsToFloat((inBuf[4] & 0xff) + ((inBuf[5] & 0xff) << 8) + ((inBuf[6] & 0xff) << 16) + ((inBuf[7] & 0xff) << 24)); | ||
ypr[2] = Float.intBitsToFloat((inBuf[8] & 0xff) + ((inBuf[9] & 0xff) << 8) + ((inBuf[10] & 0xff) << 16) + ((inBuf[11] & 0xff) << 24)); | ||
|
||
// Forward to parent thread handler | ||
sendToParentThread(MSG_ID__YPR_DATA, ypr); | ||
|
||
// Rewind input buffer position | ||
inBufPos = 0; | ||
|
||
if (razorOutputMode == RazorOutputMode.YAW_PITCH_ROLL_ANGLES) { | ||
if (inBufPos == 12) { // We received a full frame | ||
float[] ypr = float3Pool.get(); | ||
byteBufferToFloatArray(inBuf, ypr, 3); | ||
|
||
// Forward to parent thread handler | ||
sendToParentThread(MSG_ID__YPR_DATA, ypr); | ||
|
||
// Rewind input buffer position | ||
inBufPos = 0; | ||
} | ||
} else { // Raw or calibrated sensor data mode | ||
if (inBufPos == 36) { // We received a full frame | ||
float[] amg = float9Pool.get(); | ||
byteBufferToFloatArray(inBuf, amg, 9); | ||
|
||
// Forward to parent thread handler | ||
sendToParentThread(MSG_ID__AMG_DATA, amg); | ||
|
||
// Rewind input buffer position | ||
inBufPos = 0; | ||
} | ||
} | ||
} | ||
} catch (IOException e) { | ||
|
@@ -446,13 +512,13 @@ public void run() { | |
} else { | ||
// I/O error was caused on purpose, socket and streams are closed already | ||
} | ||
|
||
// I/O closed, thread done => we're disconnected now | ||
connectionState = ConnectionState.DISCONNECTED; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Sends a message to Handler assigned to parent thread. | ||
* | ||
|
@@ -463,7 +529,7 @@ private void sendToParentThread(int msgId, Object o) { | |
if (callbacksEnabled) | ||
parentThreadHandler.obtainMessage(msgId, o).sendToTarget(); | ||
} | ||
|
||
/** | ||
* Sends a message to Handler assigned to parent thread. | ||
* | ||
|
@@ -485,7 +551,7 @@ void delay(long ms) { | |
} catch (InterruptedException e) { } | ||
} | ||
} | ||
|
||
/** | ||
* Handler that forwards messages to the RazorListener callbacks. This handler runs in the | ||
* thread this RazorAHRS object was created in and receives data from the Bluetooth I/O thread. | ||
|
@@ -494,11 +560,17 @@ void delay(long ms) { | |
@Override | ||
public void handleMessage(Message msg) { | ||
switch (msg.what) { | ||
case MSG_ID__YPR_DATA: | ||
case MSG_ID__YPR_DATA: // Yaw, pitch and roll data | ||
float[] ypr = (float[]) msg.obj; | ||
razorListener.onAnglesUpdate(ypr[0], ypr[1], ypr[2]); | ||
float3Pool.put(ypr); | ||
break; | ||
case MSG_ID__AMG_DATA: // Accelerometer, magnetometer and gyroscope data | ||
float[] amg = (float[]) msg.obj; | ||
razorListener.onSensorsUpdate(amg[0], amg[1], amg[2], amg[3], amg[4], amg[5], | ||
amg[6], amg[7], amg[8]); | ||
float9Pool.put(amg); | ||
break; | ||
case MSG_ID__IO_EXCEPTION_AND_DISCONNECT: | ||
razorListener.onIOExceptionAndDisconnect((IOException) msg.obj); | ||
break; | ||
|
Oops, something went wrong.