diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
new file mode 100644
index 0000000..23e7dfb
Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser
new file mode 100644
index 0000000..91b7e84
Binary files /dev/null and b/.idea/caches/gradle_models.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..681f41a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 7c4fca0..8144c3c 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,7 +1,6 @@
-
@@ -12,11 +11,6 @@
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 773d366..a020387 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,17 +1,22 @@
+
+
+
+
+
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..bc2751c
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5bae4e2..65c58fe 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,26 +5,47 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index c8421df..0188d1c 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,8 +2,9 @@
-
+
+
\ No newline at end of file
diff --git a/.idea/modules/Application/iconsole-android.Application.iml b/.idea/modules/Application/iconsole-android.Application.iml
new file mode 100644
index 0000000..921e407
--- /dev/null
+++ b/.idea/modules/Application/iconsole-android.Application.iml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/android_antlib_4-14/iconsole-android.android_antlib_4-14.iml b/.idea/modules/android_antlib_4-14/iconsole-android.android_antlib_4-14.iml
new file mode 100644
index 0000000..41848b4
--- /dev/null
+++ b/.idea/modules/android_antlib_4-14/iconsole-android.android_antlib_4-14.iml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Application/Application.iml b/Application/Application.iml
deleted file mode 100644
index bb90e68..0000000
--- a/Application/Application.iml
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Application/build.gradle b/Application/build.gradle
index 232ebea..dd745bc 100644
--- a/Application/build.gradle
+++ b/Application/build.gradle
@@ -6,10 +6,11 @@ buildscript {
url 'https://maven.google.com/'
name 'Google'
}
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.3'
+ classpath 'com.android.tools.build:gradle:4.1.3'
}
}
@@ -24,13 +25,15 @@ repositories {
}
dependencies {
- implementation "com.android.support:gridlayout-v7:27.1.1"
- implementation "com.android.support:cardview-v7:27.1.1"
- implementation "com.android.support:appcompat-v7:27.1.1"
- implementation 'com.android.support:support-v13:27.1.1'
- implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ implementation "com.android.support:gridlayout-v7:28.0.0"
+ implementation "com.android.support:cardview-v7:28.0.0"
+ implementation "com.android.support:appcompat-v7:28.0.0"
+ implementation 'com.android.support:support-v13:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:2.0.4'
+ implementation project(':android_antlib_4-14')
}
+
// The sample build uses multiple directories to
// keep boilerplate and common code separate from
// the main sample code.
diff --git a/Application/src/main/AndroidManifest.xml b/Application/src/main/AndroidManifest.xml
index 01cfa2b..33e4dda 100644
--- a/Application/src/main/AndroidManifest.xml
+++ b/Application/src/main/AndroidManifest.xml
@@ -34,15 +34,18 @@
>
-
-
+
+
+
+
+
mFrag;
+
+ MyInnerHandler(BluetoothChatFragment aFragment) {
+ mFrag = new WeakReference(aFragment);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ BluetoothChatFragment theFrag = mFrag.get();
+ if (theFrag == null) {
+ return;
+ }
+ FragmentActivity activity = theFrag.getActivity();
+
+ switch (msg.what) {
+ case Constants.MESSAGE_STATE_CHANGE:
+ switch (msg.arg1) {
+ case BluetoothChatService.STATE_CONNECTED:
+ theFrag.setStatus(theFrag.getString(R.string.title_connected_to, theFrag.mConnectedDeviceName));
+ //mConversationArrayAdapter.clear();
+ theFrag.mStartButton.setEnabled(true);
+ theFrag.mStopButton.setEnabled(true);
+ theFrag.mDisconnectButton.setEnabled(true);
+ theFrag.mLevel.setEnabled(true);
+ theFrag.mLevel.setValue(1);
+ break;
+ case BluetoothChatService.STATE_CONNECTING:
+ theFrag.setStatus(R.string.title_connecting);
+ theFrag.mStartButton.setEnabled(false);
+ theFrag.mStopButton.setEnabled(false);
+ theFrag.mDisconnectButton.setEnabled(false);
+ theFrag.mLevel.setEnabled(false);
+ break;
+ case BluetoothChatService.STATE_LISTEN:
+ case BluetoothChatService.STATE_NONE:
+ theFrag.setStatus(R.string.title_not_connected);
+ theFrag.mStartButton.setEnabled(false);
+ theFrag.mStopButton.setEnabled(false);
+ theFrag.mDisconnectButton.setEnabled(false);
+ theFrag.mLevel.setEnabled(false);
+ break;
+ }
+ break;
+ case Constants.MESSAGE_DATA:
+ if (!(msg.obj instanceof IConsole.Data))
+ return;
+ IConsole.Data data = (IConsole.Data) msg.obj;
+ theFrag.mChannelService.setSpeed(data.mSpeed10 / 10.0);
+ theFrag.mChannelService.setPower(data.mPower10 / 10);
+ theFrag.mChannelService.setCadence(data.mRPM);
+
+ theFrag.mSpeedText.setText(String.format("% 3.1f", data.mSpeed10 / 10.0));
+ theFrag.mPowerText.setText(String.format("% 3.1f", data.mPower10 / 10.0));
+ theFrag.mRPMText.setText(String.format("%d", data.mRPM));
+ theFrag.mDistanceText.setText(String.format("% 3.1f", data.mDistance10 / 10.0));
+ theFrag.mCaloriesText.setText(String.format("% 3d", data.mCalories));
+ theFrag.mHFText.setText(String.format("%d", data.mHF));
+ theFrag.mTimeText.setText(String.format("%s", data.getTimeStr()));
+ //mLevel.setValue(data.mLevel);
+ break;
+ case Constants.MESSAGE_WRITE:
+ //byte[] writeBuf = (byte[]) msg.obj;
+ // construct a string from the buffer
+ //String writeMessage = new String(writeBuf);
+ //mConversationArrayAdapter.add("Me: " + writeMessage);
+ break;
+ case Constants.MESSAGE_READ:
+ //byte[] readBuf = (byte[]) msg.obj;
+ // construct a string from the valid bytes in the buffer
+ //String readMessage = new String(readBuf, 0, msg.arg1);
+ //mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);
+ break;
+ case Constants.MESSAGE_DEVICE_NAME:
+ // save the connected device's name
+ theFrag.mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
+ if (null != activity) {
+ Toast.makeText(activity, "Connected to "
+ + theFrag.mConnectedDeviceName, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ case Constants.MESSAGE_TOAST:
+ if (null != activity) {
+ Toast.makeText(activity, msg.getData().getString(Constants.TOAST),
+ Toast.LENGTH_SHORT).show();
+ }
+ break;
+ }
+ }
+ }
+
+ private final Handler mHandler = new MyInnerHandler(this);
private static final String TAG = "BluetoothChatFragment";
@@ -75,7 +169,7 @@ public class BluetoothChatFragment extends Fragment {
/**
* Array adapter for the conversation thread
- private ArrayAdapter mConversationArrayAdapter;
+ private ArrayAdapter mConversationArrayAdapter;
*/
/**
@@ -88,6 +182,53 @@ public class BluetoothChatFragment extends Fragment {
*/
private BluetoothChatService mChatService = null;
private boolean mIsBound;
+ private ChannelService.ChannelServiceComm mChannelService;
+ /**
+ * The Handler that gets information back from the BluetoothChatService
+ */
+ private boolean mChannelServiceBound = false;
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // This is called when the connection with the service has been
+ // established, giving us the service object we can use to
+ // interact with the service. Because we have bound to a explicit
+ // service that we know is running in our own process, we can
+ // cast its IBinder to a concrete class and directly access it.
+ mChatService = ((BluetoothChatService.BluetoothChatServiceI) service).getService();
+ ((BluetoothChatService.BluetoothChatServiceI) service).setHandler(mHandler);
+ Log.d(TAG, "onServiceConnected()");
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ // This is called when the connection with the service has been
+ // unexpectedly disconnected -- that is, its process crashed.
+ // Because it is running in our same process, we should never
+ // see this happen.
+ mChatService = null;
+
+ }
+ };
+ private ServiceConnection mChannelServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder serviceBinder) {
+ Log.v(TAG, "mChannelServiceConnection.onServiceConnected...");
+
+ mChannelService = (ChannelService.ChannelServiceComm) serviceBinder;
+
+
+ Log.v(TAG, "...mChannelServiceConnection.onServiceConnected");
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ Log.v(TAG, "mChannelServiceConnection.onServiceDisconnected...");
+
+ // Clearing and disabling when disconnecting from ChannelService
+ mChannelService = null;
+
+ Log.v(TAG, "...mChannelServiceConnection.onServiceDisconnected");
+ }
+ };
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -104,7 +245,6 @@ public class BluetoothChatFragment extends Fragment {
}
}
-
@Override
public void onStart() {
super.onStart();
@@ -117,6 +257,8 @@ public class BluetoothChatFragment extends Fragment {
} else if (mChatService == null) {
setupChat();
}
+ if (!mChannelServiceBound) doBindChannelService();
+
}
@Override
@@ -126,6 +268,9 @@ public class BluetoothChatFragment extends Fragment {
}
Log.d(TAG, "onDestroy()");
doUnbindService();
+ doUnbindChannelService();
+ mChannelServiceConnection = null;
+
super.onDestroy();
}
@@ -172,28 +317,6 @@ public class BluetoothChatFragment extends Fragment {
mTimeText = (TextView) view.findViewById(R.id.Time);
}
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- // This is called when the connection with the service has been
- // established, giving us the service object we can use to
- // interact with the service. Because we have bound to a explicit
- // service that we know is running in our own process, we can
- // cast its IBinder to a concrete class and directly access it.
- mChatService = ((BluetoothChatService.BluetoothChatServiceI)service).getService();
- ((BluetoothChatService.BluetoothChatServiceI)service).setHandler(mHandler);
- Log.d(TAG, "onServiceConnected()");
- }
-
- public void onServiceDisconnected(ComponentName className) {
- // This is called when the connection with the service has been
- // unexpectedly disconnected -- that is, its process crashed.
- // Because it is running in our same process, we should never
- // see this happen.
- mChatService = null;
-
- }
- };
-
void doBindService() {
Log.d(TAG, "doBindService()");
@@ -201,7 +324,7 @@ public class BluetoothChatFragment extends Fragment {
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
- getActivity().bindService(new Intent(getActivity(), BluetoothChatService.class), mConnection , Context.BIND_AUTO_CREATE);
+ getActivity().bindService(new Intent(getActivity(), BluetoothChatService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
@@ -213,6 +336,33 @@ public class BluetoothChatFragment extends Fragment {
}
}
+ private void doBindChannelService() {
+ Log.v(TAG, "doBindChannelService...");
+
+ // Binds to ChannelService. ChannelService binds and manages connection between the
+ // app and the ANT Radio Service
+ mChannelServiceBound = getActivity().bindService(new Intent(getActivity(), ChannelService.class), mChannelServiceConnection, Context.BIND_AUTO_CREATE);
+
+ if (!mChannelServiceBound) //If the bind returns false, run the unbind method to update the GUI
+ doUnbindChannelService();
+
+ Log.i(TAG, " Channel Service binding = " + mChannelServiceBound);
+
+ Log.v(TAG, "...doBindChannelService");
+ }
+
+ private void doUnbindChannelService() {
+ Log.v(TAG, "doUnbindChannelService...");
+
+ if (mChannelServiceBound) {
+ getActivity().unbindService(mChannelServiceConnection);
+
+ mChannelServiceBound = false;
+ }
+
+ Log.v(TAG, "...doUnbindChannelService");
+ }
+
/**
* Set up the UI and background operations for chat.
*/
@@ -287,7 +437,7 @@ public class BluetoothChatFragment extends Fragment {
if (mChatService == null)
return;
- // Check that we're actually connected before trying anything
+ // Check that we're actually connected before trying anything
if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
Toast.makeText(getActivity(), R.string.not_connected, Toast.LENGTH_SHORT).show();
return;
@@ -330,86 +480,6 @@ public class BluetoothChatFragment extends Fragment {
actionBar.setSubtitle(subTitle);
}
- /**
- * The Handler that gets information back from the BluetoothChatService
- */
- private final Handler mHandler = new Handler() {
- @SuppressLint("DefaultLocale")
- @Override
- public void handleMessage(Message msg) {
- FragmentActivity activity = getActivity();
- switch (msg.what) {
- case Constants.MESSAGE_STATE_CHANGE:
- switch (msg.arg1) {
- case BluetoothChatService.STATE_CONNECTED:
- setStatus(getString(R.string.title_connected_to, mConnectedDeviceName));
- //mConversationArrayAdapter.clear();
- mStartButton.setEnabled(true);
- mStopButton.setEnabled(true);
- mDisconnectButton.setEnabled(true);
- mLevel.setEnabled(true);
- mLevel.setValue(1);
- break;
- case BluetoothChatService.STATE_CONNECTING:
- setStatus(R.string.title_connecting);
- mStartButton.setEnabled(false);
- mStopButton.setEnabled(false);
- mDisconnectButton.setEnabled(false);
- mLevel.setEnabled(false);
- break;
- case BluetoothChatService.STATE_LISTEN:
- case BluetoothChatService.STATE_NONE:
- setStatus(R.string.title_not_connected);
- mStartButton.setEnabled(false);
- mStopButton.setEnabled(false);
- mDisconnectButton.setEnabled(false);
- mLevel.setEnabled(false);
- break;
- }
- break;
- case Constants.MESSAGE_DATA:
- if (!(msg.obj instanceof IConsole.Data))
- return;
- IConsole.Data data = (IConsole.Data) msg.obj;
- mSpeedText.setText(String.format("% 3.1f", data.mSpeed10 / 10.0));
- mPowerText.setText(String.format("% 3.1f", data.mPower10 / 10.0));
- mRPMText.setText(String.format("%d", data.mRPM));
- mDistanceText.setText(String.format("% 3.1f", data.mDistance10 / 10.0));
- mCaloriesText.setText(String.format("% 3d", data.mCalories));
- mHFText.setText(String.format("%d", data.mHF));
- mTimeText.setText(String.format("%s",data.getTimeStr()));
- //mLevel.setValue(data.mLevel);
- break;
- case Constants.MESSAGE_WRITE:
- //byte[] writeBuf = (byte[]) msg.obj;
- // construct a string from the buffer
- //String writeMessage = new String(writeBuf);
- //mConversationArrayAdapter.add("Me: " + writeMessage);
- break;
- case Constants.MESSAGE_READ:
- //byte[] readBuf = (byte[]) msg.obj;
- // construct a string from the valid bytes in the buffer
- //String readMessage = new String(readBuf, 0, msg.arg1);
- //mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);
- break;
- case Constants.MESSAGE_DEVICE_NAME:
- // save the connected device's name
- mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
- if (null != activity) {
- Toast.makeText(activity, "Connected to "
- + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
- }
- break;
- case Constants.MESSAGE_TOAST:
- if (null != activity) {
- Toast.makeText(activity, msg.getData().getString(Constants.TOAST),
- Toast.LENGTH_SHORT).show();
- }
- break;
- }
- }
- };
-
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE_SECURE:
diff --git a/Application/src/main/java/org/surfsite/iconsole/BluetoothChatService.java b/Application/src/main/java/org/surfsite/iconsole/BluetoothChatService.java
index 8bd3b96..0303cd3 100644
--- a/Application/src/main/java/org/surfsite/iconsole/BluetoothChatService.java
+++ b/Application/src/main/java/org/surfsite/iconsole/BluetoothChatService.java
@@ -23,14 +23,12 @@ import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
-import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
-
import android.util.Log;
import java.io.IOException;
@@ -45,32 +43,32 @@ import java.util.UUID;
* thread for performing data transmissions when connected.
*/
public class BluetoothChatService extends Service {
- // Debugging
- private static final String TAG = "BluetoothChatService";
- // Name for the SDP record when creating server socket
- private static final String NAME_SECURE = "BluetoothChatSecure";
- private static final String NAME_INSECURE = "BluetoothChatInsecure";
-
- // Unique UUID for this application
- private static final UUID SERIAL_PORT_CLASS = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
- // Member fields
- private final BluetoothAdapter mAdapter;
- private Handler mHandler;
-
- private ConnectThread mConnectThread;
- private ConnectedThread mConnectedThread;
- private int mState;
- private int mNewState;
-
// Constants that indicate the current connection state
public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device
+ // Debugging
+ private static final String TAG = "BluetoothChatService";
+ // Name for the SDP record when creating server socket
+ private static final String NAME_SECURE = "BluetoothChatSecure";
+ private static final String NAME_INSECURE = "BluetoothChatInsecure";
+ // Unique UUID for this application
+ private static final UUID SERIAL_PORT_CLASS = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
+ // Member fields
+ private final BluetoothAdapter mAdapter;
+ // This is the object that receives interactions from clients. See
+ // RemoteService for a more complete example.
+ private final IBinder mBinder = new BluetoothChatServiceI();
+ private Handler mHandler;
+ private ConnectThread mConnectThread;
+ private ConnectedThread mConnectedThread;
+ private int mState;
+ private int mNewState;
private NotificationManager mNM;
-
private int NOTIFICATION = R.string.local_service_started;
+
public BluetoothChatService() {
super();
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -78,21 +76,11 @@ public class BluetoothChatService extends Service {
mNewState = mState;
}
-
- public class BluetoothChatServiceI extends Binder {
- BluetoothChatService getService() {
- return BluetoothChatService.this;
- }
- void setHandler(Handler handler) {
- mHandler = handler;
- }
- }
-
@Override
public void onCreate() {
super.onCreate();
- mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
+ mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
@@ -144,11 +132,6 @@ public class BluetoothChatService extends Service {
return mBinder;
}
-
- // This is the object that receives interactions from clients. See
- // RemoteService for a more complete example.
- private final IBinder mBinder = new BluetoothChatServiceI();
-
/**
* Update UI title according to the current state of the chat connection
*/
@@ -158,6 +141,7 @@ public class BluetoothChatService extends Service {
mNewState = mState;
// Give the new state to the Handler so the UI Activity can update
+ if (mHandler != null)
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
}
@@ -186,6 +170,7 @@ public class BluetoothChatService extends Service {
super.onDestroy();
}
+
/**
* Show a notification while this service is running.
*/
@@ -288,7 +273,6 @@ public class BluetoothChatService extends Service {
updateUserInterfaceTitle();
}
-
/**
* Indicate that the connection attempt failed and notify the UI Activity.
*/
@@ -327,12 +311,22 @@ public class BluetoothChatService extends Service {
BluetoothChatService.this.startBT();
}
+ public class BluetoothChatServiceI extends Binder {
+ BluetoothChatService getService() {
+ return BluetoothChatService.this;
+ }
+
+ void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+ }
+
/**
* This thread runs while attempting to make an outgoing connection
* with a device. It runs straight through; the connection either
* succeeds or fails.
*/
- class ConnectThread extends Thread {
+ class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
@@ -481,9 +475,17 @@ public class BluetoothChatService extends Service {
}
- public boolean setLevel(int level) { return mmIConsole.setLevel(level); }
- public boolean startIConsole() { return mmIConsole.start(); }
- public boolean stopIConsole() { return mmIConsole.stop(); }
+ public boolean setLevel(int level) {
+ return mmIConsole.setLevel(level);
+ }
+
+ public boolean startIConsole() {
+ return mmIConsole.start();
+ }
+
+ public boolean stopIConsole() {
+ return mmIConsole.stop();
+ }
public void cancel() {
mmIConsole.stop();
diff --git a/Application/src/main/java/org/surfsite/iconsole/ChannelService.java b/Application/src/main/java/org/surfsite/iconsole/ChannelService.java
new file mode 100644
index 0000000..26ba4cc
--- /dev/null
+++ b/Application/src/main/java/org/surfsite/iconsole/ChannelService.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2012 Dynastream Innovations Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.surfsite.iconsole;
+
+import com.dsi.ant.AntService;
+import com.dsi.ant.channel.AntChannel;
+import com.dsi.ant.channel.AntChannelProvider;
+import com.dsi.ant.channel.ChannelNotAvailableException;
+import com.dsi.ant.channel.PredefinedNetwork;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+
+public class ChannelService extends Service {
+ private static final String TAG = "ChannelService";
+
+ private boolean mAntRadioServiceBound;
+ private AntService mAntRadioService = null;
+ private AntChannelProvider mAntChannelProvider = null;
+ private boolean mAllowAddChannel = false;
+ PowerChannelController powerChannelController = null;
+ SpeedChannelController speedChannelController = null;
+
+ private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // Must pass in the received IBinder object to correctly construct an AntService object
+ mAntRadioService = new AntService(service);
+
+ try {
+ // Getting a channel provider in order to acquire channels
+ mAntChannelProvider = mAntRadioService.getChannelProvider();
+
+ // Initial check for number of channels available
+ boolean mChannelAvailable = mAntChannelProvider.getNumChannelsAvailable() > 0;
+ // Initial check for if legacy interface is in use. If the
+ // legacy interface is in use, applications can free the ANT
+ // radio by attempting to acquire a channel.
+ boolean legacyInterfaceInUse = mAntChannelProvider.isLegacyInterfaceInUse();
+
+ // If there are channels OR legacy interface in use, allow adding channels
+ if (mChannelAvailable || legacyInterfaceInUse) {
+ mAllowAddChannel = true;
+ } else {
+ // If no channels available AND legacy interface is not in use, disallow adding channels
+ mAllowAddChannel = false;
+ }
+
+
+ } catch (RemoteException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ die("Binder Died");
+
+ mAntChannelProvider = null;
+ mAntRadioService = null;
+
+ mAllowAddChannel = false;
+ }
+
+ };
+
+ /**
+ * The interface used to communicate with the ChannelService
+ */
+ public class ChannelServiceComm extends Binder {
+
+ void setSpeed(double speed) {
+ if (null != speedChannelController) {
+ speedChannelController.speed = speed;
+ }
+ }
+
+ void setPower(int power) {
+ if (null != powerChannelController) {
+ powerChannelController.power = power;
+ }
+ }
+
+ void setCadence(int cadence) {
+ if (null != powerChannelController) {
+ powerChannelController.cadence = cadence;
+ }
+ }
+
+ /**
+ * Closes all channels currently added.
+ */
+ void clearAllChannels() {
+ closeAllChannels();
+ }
+ }
+
+ public void openAllChannels() throws ChannelNotAvailableException {
+ powerChannelController = new PowerChannelController(acquireChannel());
+ speedChannelController = new SpeedChannelController(acquireChannel());
+ }
+
+ private void closeAllChannels() {
+ if (powerChannelController != null)
+ powerChannelController.close();
+ if (speedChannelController != null)
+ speedChannelController.close();
+ powerChannelController = null;
+ speedChannelController = null;
+ }
+
+ AntChannel acquireChannel() throws ChannelNotAvailableException {
+ AntChannel mAntChannel = null;
+ if (null != mAntChannelProvider) {
+ try {
+ /*
+ * If applications require a channel with specific capabilities
+ * (event buffering, background scanning etc.), a Capabilities
+ * object should be created and then the specific capabilities
+ * required set to true. Applications can specify both required
+ * and desired Capabilities with both being passed in
+ * acquireChannel(context, PredefinedNetwork,
+ * requiredCapabilities, desiredCapabilities).
+ */
+ mAntChannel = mAntChannelProvider.acquireChannel(this, PredefinedNetwork.ANT_PLUS_1);
+ /*
+ NetworkKey mNK = new NetworkKey(new byte[] { (byte)0xb9, (byte)0xa5, (byte)0x21, (byte)0xfb,
+ (byte)0xbd, (byte)0x72, (byte)0xc3, (byte)0x45 });
+ Log.v(TAG, mNK.toString());
+ mAntChannel = mAntChannelProvider.acquireChannelOnPrivateNetwork(this, mNK);
+ */
+ } catch (RemoteException e) {
+ die("ACP Remote Ex");
+ }
+ }
+ return mAntChannel;
+ }
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return new ChannelServiceComm();
+ }
+
+ /**
+ * Receives AntChannelProvider state changes being sent from ANT Radio Service
+ */
+ private final BroadcastReceiver mChannelProviderStateChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED.equals(intent.getAction())) {
+ boolean update = false;
+ // Retrieving the data contained in the intent
+ int numChannels = intent.getIntExtra(AntChannelProvider.NUM_CHANNELS_AVAILABLE, 0);
+ boolean legacyInterfaceInUse = intent.getBooleanExtra(AntChannelProvider.LEGACY_INTERFACE_IN_USE, false);
+
+ if (mAllowAddChannel) {
+ // Was a acquire channel allowed
+ // If no channels available AND legacy interface is not in use, disallow acquiring of channels
+ if (0 == numChannels && !legacyInterfaceInUse) {
+ mAllowAddChannel = false;
+ update = true;
+ closeAllChannels();
+ }
+ } else {
+ // Acquire channels not allowed
+ // If there are channels OR legacy interface in use, allow acquiring of channels
+ if (numChannels > 0 || legacyInterfaceInUse) {
+ mAllowAddChannel = true;
+ update = true;
+ try {
+ openAllChannels();
+ } catch (ChannelNotAvailableException exception) {
+ Log.e(TAG, "Channel not available!!");
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private void doBindAntRadioService() {
+ if (BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
+
+ // Start listing for channel available intents
+ registerReceiver(mChannelProviderStateChangedReceiver, new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED));
+
+ // Creating the intent and calling context.bindService() is handled by
+ // the static bindService() method in AntService
+ mAntRadioServiceBound = AntService.bindService(this, mAntRadioServiceConnection);
+ }
+
+ private void doUnbindAntRadioService() {
+ if (BuildConfig.DEBUG) Log.v(TAG, "doUnbindAntRadioService");
+
+ // Stop listing for channel available intents
+ try {
+ unregisterReceiver(mChannelProviderStateChangedReceiver);
+ } catch (IllegalArgumentException exception) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Attempting to unregister a never registered Channel Provider State Changed receiver.");
+ }
+
+ if (mAntRadioServiceBound) {
+ try {
+ unbindService(mAntRadioServiceConnection);
+ } catch (IllegalArgumentException e) {
+ // Not bound, that's what we want anyway
+ }
+
+ mAntRadioServiceBound = false;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mAntRadioServiceBound = false;
+
+ doBindAntRadioService();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ closeAllChannels();
+
+ doUnbindAntRadioService();
+ mAntChannelProvider = null;
+
+ super.onDestroy();
+ }
+
+ static void die(String error) {
+ Log.e(TAG, "DIE: " + error);
+ }
+
+}
diff --git a/Application/src/main/java/org/surfsite/iconsole/MainActivity.java b/Application/src/main/java/org/surfsite/iconsole/MainActivity.java
index 6e26949..b917357 100644
--- a/Application/src/main/java/org/surfsite/iconsole/MainActivity.java
+++ b/Application/src/main/java/org/surfsite/iconsole/MainActivity.java
@@ -30,8 +30,10 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
+import android.widget.Button;
import android.widget.ViewAnimator;
+import org.surfsite.iconsole.ChannelService.ChannelServiceComm;
/**
@@ -70,8 +72,16 @@ public class MainActivity extends FragmentActivity {
@Override
protected void onDestroy() {
- stopService(new Intent(this, BluetoothChatService.class));
+
+ if(isFinishing())
+ {
+ stopService(new Intent(this, BluetoothChatService.class));
+ stopService(new Intent(this, ChannelService.class));
+ }
super.onDestroy();
}
+
+
+
}
diff --git a/Application/src/main/java/org/surfsite/iconsole/PowerChannelController.java b/Application/src/main/java/org/surfsite/iconsole/PowerChannelController.java
new file mode 100644
index 0000000..081c641
--- /dev/null
+++ b/Application/src/main/java/org/surfsite/iconsole/PowerChannelController.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2012 Dynastream Innovations Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.surfsite.iconsole;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.dsi.ant.channel.AntChannel;
+import com.dsi.ant.channel.AntCommandFailedException;
+import com.dsi.ant.channel.IAntChannelEventHandler;
+import com.dsi.ant.message.ChannelId;
+import com.dsi.ant.message.ChannelType;
+import com.dsi.ant.message.EventCode;
+import com.dsi.ant.message.fromant.AcknowledgedDataMessage;
+import com.dsi.ant.message.fromant.ChannelEventMessage;
+import com.dsi.ant.message.fromant.MessageFromAntType;
+import com.dsi.ant.message.ipc.AntMessageParcel;
+
+import java.util.Random;
+
+public class PowerChannelController {
+ public static final int POWER_SENSOR_ID = 0x9e3d4b66;
+ // The device type and transmission type to be part of the channel ID message
+ private static final int CHANNEL_POWER_DEVICE_TYPE = 0x0B;
+ private static final int CHANNEL_POWER_TRANSMISSION_TYPE = 5;
+ // The period and frequency values the channel will be configured to
+ private static final int CHANNEL_POWER_PERIOD = 8182; // 1 Hz
+ private static final int CHANNEL_POWER_FREQUENCY = 57;
+ private static final String TAG = PowerChannelController.class.getSimpleName();
+ private static Random randGen = new Random();
+ int power = 0;
+ int cadence = 0;
+ private AntChannel mAntChannel;
+ private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
+ private boolean mIsOpen;
+
+ public PowerChannelController(AntChannel antChannel) {
+ mAntChannel = antChannel;
+ openChannel();
+ }
+
+ boolean openChannel() {
+ if (null != mAntChannel) {
+ if (mIsOpen) {
+ Log.w(TAG, "Channel was already open");
+ } else {
+ // Channel ID message contains device number, type and transmission type. In
+ // order for master (TX) channels and slave (RX) channels to connect, they
+ // must have the same channel ID, or wildcard (0) is used.
+ ChannelId channelId = new ChannelId(POWER_SENSOR_ID & 0xFFFF,
+ CHANNEL_POWER_DEVICE_TYPE, CHANNEL_POWER_TRANSMISSION_TYPE);
+
+ try {
+ // Setting the channel event handler so that we can receive messages from ANT
+ mAntChannel.setChannelEventHandler(mChannelEventCallback);
+
+ // Performs channel assignment by assigning the type to the channel. Additional
+ // features (such as, background scanning and frequency agility) can be enabled
+ // by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
+ mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
+
+ /*
+ * Configures the channel ID, messaging period and rf frequency after assigning,
+ * then opening the channel.
+ *
+ * For any additional ANT features such as proximity search or background scanning, refer to
+ * the ANT Protocol Doc found at:
+ * http://www.thisisant.com/resources/ant-message-protocol-and-usage/
+ */
+ mAntChannel.setChannelId(channelId);
+ mAntChannel.setPeriod(CHANNEL_POWER_PERIOD);
+ mAntChannel.setRfFrequency(CHANNEL_POWER_FREQUENCY);
+ mAntChannel.open();
+ mIsOpen = true;
+
+ Log.d(TAG, "Opened channel with device number: " + POWER_SENSOR_ID);
+ } catch (RemoteException e) {
+ channelError(e);
+ } catch (AntCommandFailedException e) {
+ // This will release, and therefore unassign if required
+ channelError("Open failed", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "No channel available");
+ }
+
+ return mIsOpen;
+ }
+
+
+ void channelError(RemoteException e) {
+ String logString = "Remote service communication failed.";
+
+ Log.e(TAG, logString);
+
+ }
+
+ void channelError(String error, AntCommandFailedException e) {
+ StringBuilder logString;
+
+ if (e.getResponseMessage() != null) {
+ String initiatingMessageId = "0x" + Integer.toHexString(
+ e.getResponseMessage().getInitiatingMessageId());
+ String rawResponseCode = "0x" + Integer.toHexString(
+ e.getResponseMessage().getRawResponseCode());
+
+ logString = new StringBuilder(error)
+ .append(". Command ")
+ .append(initiatingMessageId)
+ .append(" failed with code ")
+ .append(rawResponseCode);
+ } else {
+ String attemptedMessageId = "0x" + Integer.toHexString(
+ e.getAttemptedMessageType().getMessageId());
+ String failureReason = e.getFailureReason().toString();
+
+ logString = new StringBuilder(error)
+ .append(". Command ")
+ .append(attemptedMessageId)
+ .append(" failed with reason ")
+ .append(failureReason);
+ }
+
+ Log.e(TAG, logString.toString());
+
+ mAntChannel.release();
+ }
+
+ public void close() {
+ // TODO kill all our resources
+ if (null != mAntChannel) {
+ mIsOpen = false;
+
+ // Releasing the channel to make it available for others.
+ // After releasing, the AntChannel instance cannot be reused.
+ mAntChannel.release();
+ mAntChannel = null;
+ }
+
+ Log.e(TAG, "Channel Closed");
+ }
+
+ /**
+ * Implements the Channel Event Handler Interface so that messages can be
+ * received and channel death events can be handled.
+ */
+ public class ChannelEventCallback implements IAntChannelEventHandler {
+
+ int cnt = 0;
+ int eventCount = 0;
+ int cumulativePower = 0;
+
+ @Override
+ public void onChannelDeath() {
+ // Display channel death message when channel dies
+ Log.e(TAG, "Channel Death");
+ }
+
+ @Override
+ public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
+ Log.d(TAG, "Rx: " + antParcel);
+ Log.d(TAG, "Message Type: " + messageType);
+ byte[] payload = new byte[8];
+
+ // Switching on message type to handle different types of messages
+ switch (messageType) {
+ // If data message, construct from parcel and update channel data
+ case BROADCAST_DATA:
+ // Rx Data
+ //updateData(new BroadcastDataMessage(antParcel).getPayload());
+ break;
+ case ACKNOWLEDGED_DATA:
+ // Rx Data
+ //updateData(new AcknowledgedDataMessage(antParcel).getPayload());
+ payload = new AcknowledgedDataMessage(antParcel).getPayload();
+ Log.d(TAG, "AcknowledgedDataMessage: " + payload);
+
+ if ((payload[0] == 0) && (payload[1] == 1) && (payload[2] == (byte)0xAA)) {
+ payload[0] = (byte) 0x01;
+ payload[1] = (byte) 0xAC;
+ payload[2] = (byte) 0xFF;
+ payload[3] = (byte) 0xFF;
+ payload[4] = (byte) 0xFF;
+ payload[5] = (byte) 0xFF;
+ payload[6] = (byte) 0x00;
+ payload[7] = (byte) 0x00;
+ try {
+ // Setting the data to be broadcast on the next channel period
+ mAntChannel.setBroadcastData(payload);
+ } catch (RemoteException e) {
+ channelError(e);
+ }
+ }
+ break;
+ case CHANNEL_EVENT:
+ // Constructing channel event message from parcel
+ ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
+ EventCode code = eventMessage.getEventCode();
+ Log.d(TAG, "Event Code: " + code);
+
+ // Switching on event code to handle the different types of channel events
+ switch (code) {
+ case TX:
+ cnt += 1;
+
+ if (cnt % 61 == 15) {
+ payload[0] = (byte) 0x50;
+ payload[1] = (byte) 0xFF;
+ payload[2] = (byte) 0xFF;
+ payload[3] = (byte) 0x01;
+ payload[4] = (byte) 0xFF;
+ payload[5] = (byte) 0x00;
+ payload[6] = (byte) 0x01;
+ payload[7] = (byte) 0x00;
+ } else if (cnt % 61 == 30) {
+ payload[0] = (byte) 0x51;
+ payload[1] = (byte) 0xFF;
+ payload[2] = (byte) 0xFF;
+ payload[3] = (byte) 0x01;
+ payload[4] = (byte) ((POWER_SENSOR_ID) & 0xFF);
+ payload[5] = (byte) ((POWER_SENSOR_ID >> 8) & 0xFF);
+ payload[6] = (byte) ((POWER_SENSOR_ID >> 16) & 0xFF);
+ payload[7] = (byte) ((POWER_SENSOR_ID >> 24) & 0xFF);
+ } else {
+ eventCount = (eventCount + 1) & 0xFF;
+ cumulativePower = (cumulativePower + power) & 0xFFFF;
+ payload[0] = (byte) 0x10;
+ payload[1] = (byte) eventCount;
+ payload[2] = (byte) 0xFF;
+ payload[3] = (byte) cadence;
+ payload[4] = (byte) ((cumulativePower) & 0xFF);
+ payload[5] = (byte) ((cumulativePower >> 8) & 0xFF);
+ payload[6] = (byte) ((power) & 0xFF);
+ payload[7] = (byte) ((power >> 8) & 0xFF);
+ }
+
+ if (mIsOpen) {
+ try {
+ // Setting the data to be broadcast on the next channel period
+ mAntChannel.setBroadcastData(payload);
+ } catch (RemoteException e) {
+ channelError(e);
+ }
+ }
+ break;
+ case CHANNEL_COLLISION:
+ cnt += 1;
+ break;
+ case RX_SEARCH_TIMEOUT:
+ // TODO May want to keep searching
+ Log.e(TAG, "No Device Found");
+ break;
+ case CHANNEL_CLOSED:
+ case RX_FAIL:
+ case RX_FAIL_GO_TO_SEARCH:
+ case TRANSFER_RX_FAILED:
+ case TRANSFER_TX_COMPLETED:
+ case TRANSFER_TX_FAILED:
+ case TRANSFER_TX_START:
+ case UNKNOWN:
+ // TODO More complex communication will need to handle these events
+ break;
+ }
+ break;
+ case ANT_VERSION:
+ case BURST_TRANSFER_DATA:
+ case CAPABILITIES:
+ case CHANNEL_ID:
+ case CHANNEL_RESPONSE:
+ case CHANNEL_STATUS:
+ case SERIAL_NUMBER:
+ case OTHER:
+ // TODO More complex communication will need to handle these message types
+ break;
+ }
+ }
+ }
+}
diff --git a/Application/src/main/java/org/surfsite/iconsole/SpeedChannelController.java b/Application/src/main/java/org/surfsite/iconsole/SpeedChannelController.java
new file mode 100644
index 0000000..7a83b33
--- /dev/null
+++ b/Application/src/main/java/org/surfsite/iconsole/SpeedChannelController.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2012 Dynastream Innovations Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.surfsite.iconsole;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.dsi.ant.channel.AntChannel;
+import com.dsi.ant.channel.AntCommandFailedException;
+import com.dsi.ant.channel.IAntChannelEventHandler;
+import com.dsi.ant.message.ChannelId;
+import com.dsi.ant.message.ChannelType;
+import com.dsi.ant.message.EventCode;
+import com.dsi.ant.message.fromant.ChannelEventMessage;
+import com.dsi.ant.message.fromant.MessageFromAntType;
+import com.dsi.ant.message.ipc.AntMessageParcel;
+
+import java.util.Random;
+
+public class SpeedChannelController {
+ // The device type and transmission type to be part of the channel ID message
+ private static final int CHANNEL_SPEED_DEVICE_TYPE = 0x7B;
+ private static final int CHANNEL_SPEED_TRANSMISSION_TYPE = 1;
+
+ // The period and frequency values the channel will be configured to
+ private static final int CHANNEL_SPEED_PERIOD = 8118; // 1 Hz
+ private static final int CHANNEL_SPEED_FREQUENCY = 57;
+
+ private static final String TAG = SpeedChannelController.class.getSimpleName();
+ public static final int SPEED_SENSOR_ID = 0x9e3d4b65;
+
+ private static Random randGen = new Random();
+
+ private AntChannel mAntChannel;
+
+ private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
+
+
+ private boolean mIsOpen;
+ double speed = 0.0;
+
+ public SpeedChannelController(AntChannel antChannel) {
+ mAntChannel = antChannel;
+ openChannel();
+ }
+
+ boolean openChannel() {
+ if (null != mAntChannel) {
+ if (mIsOpen) {
+ Log.w(TAG, "Channel was already open");
+ } else {
+ // Channel ID message contains device number, type and transmission type. In
+ // order for master (TX) channels and slave (RX) channels to connect, they
+ // must have the same channel ID, or wildcard (0) is used.
+ ChannelId channelId = new ChannelId(SPEED_SENSOR_ID & 0xFFFF,
+ CHANNEL_SPEED_DEVICE_TYPE, CHANNEL_SPEED_TRANSMISSION_TYPE);
+
+ try {
+ // Setting the channel event handler so that we can receive messages from ANT
+ mAntChannel.setChannelEventHandler(mChannelEventCallback);
+
+ // Performs channel assignment by assigning the type to the channel. Additional
+ // features (such as, background scanning and frequency agility) can be enabled
+ // by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
+ mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
+
+ /*
+ * Configures the channel ID, messaging period and rf frequency after assigning,
+ * then opening the channel.
+ *
+ * For any additional ANT features such as proximity search or background scanning, refer to
+ * the ANT Protocol Doc found at:
+ * http://www.thisisant.com/resources/ant-message-protocol-and-usage/
+ */
+ mAntChannel.setChannelId(channelId);
+ mAntChannel.setPeriod(CHANNEL_SPEED_PERIOD);
+ mAntChannel.setRfFrequency(CHANNEL_SPEED_FREQUENCY);
+ mAntChannel.open();
+ mIsOpen = true;
+
+ Log.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
+ } catch (RemoteException e) {
+ channelError(e);
+ } catch (AntCommandFailedException e) {
+ // This will release, and therefore unassign if required
+ channelError("Open failed", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "No channel available");
+ }
+
+ return mIsOpen;
+ }
+
+ void channelError(RemoteException e) {
+ String logString = "Remote service communication failed.";
+
+ Log.e(TAG, logString);
+ }
+
+ void channelError(String error, AntCommandFailedException e) {
+ StringBuilder logString;
+
+ if (e.getResponseMessage() != null) {
+ String initiatingMessageId = "0x" + Integer.toHexString(
+ e.getResponseMessage().getInitiatingMessageId());
+ String rawResponseCode = "0x" + Integer.toHexString(
+ e.getResponseMessage().getRawResponseCode());
+
+ logString = new StringBuilder(error)
+ .append(". Command ")
+ .append(initiatingMessageId)
+ .append(" failed with code ")
+ .append(rawResponseCode);
+ } else {
+ String attemptedMessageId = "0x" + Integer.toHexString(
+ e.getAttemptedMessageType().getMessageId());
+ String failureReason = e.getFailureReason().toString();
+
+ logString = new StringBuilder(error)
+ .append(". Command ")
+ .append(attemptedMessageId)
+ .append(" failed with reason ")
+ .append(failureReason);
+ }
+
+ Log.e(TAG, logString.toString());
+
+ mAntChannel.release();
+
+ Log.e(TAG, "ANT Command Failed");
+ }
+
+ public void close() {
+ // TODO kill all our resources
+ if (null != mAntChannel) {
+ mIsOpen = false;
+
+ // Releasing the channel to make it available for others.
+ // After releasing, the AntChannel instance cannot be reused.
+ mAntChannel.release();
+ mAntChannel = null;
+ }
+
+ Log.e(TAG, "Channel Closed");
+ }
+
+ /**
+ * Implements the Channel Event Handler Interface so that messages can be
+ * received and channel death events can be handled.
+ */
+ public class ChannelEventCallback implements IAntChannelEventHandler {
+ int revCounts = 0;
+ int ucMessageCount = 0;
+ byte ucPageChange = 0;
+ byte ucExtMesgType = 1;
+ long lastTime = 0;
+ double way;
+ int rev;
+ double remWay;
+ double wheel = 0.1;
+
+ @Override
+ public void onChannelDeath() {
+ // Display channel death message when channel dies
+ Log.e(TAG, "Channel Death");
+ }
+
+ @Override
+ public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
+ Log.d(TAG, "Rx: " + antParcel);
+ Log.d(TAG, "Message Type: " + messageType);
+
+ // Switching on message type to handle different types of messages
+ switch (messageType) {
+ // If data message, construct from parcel and update channel data
+ case BROADCAST_DATA:
+ // Rx Data
+ //updateData(new BroadcastDataMessage(antParcel).getPayload());
+ break;
+ case ACKNOWLEDGED_DATA:
+ // Rx Data
+ //updateData(new AcknowledgedDataMessage(antParcel).getPayload());
+ break;
+ case CHANNEL_EVENT:
+ // Constructing channel event message from parcel
+ ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
+ EventCode code = eventMessage.getEventCode();
+ Log.d(TAG, "Event Code: " + code);
+
+ // Switching on event code to handle the different types of channel events
+ switch (code) {
+ case TX:
+ long unixTime = System.currentTimeMillis() / 1000L;
+
+ if (lastTime != 0) {
+ way = speed * (unixTime - lastTime) / 3.6 + remWay;
+ rev = (int)(way / wheel + 0.5);
+ remWay = way - rev * wheel;
+ revCounts += rev;
+ }
+ lastTime = unixTime;
+
+ ucPageChange += 0x20;
+ ucPageChange &= 0xF0;
+ ucMessageCount += 1;
+ byte[] payload = new byte[8];
+
+ if (ucMessageCount >= 65) {
+ if (ucExtMesgType >= 4)
+ ucExtMesgType = 1;
+
+ if (ucExtMesgType == 1) {
+ int halfunixTime = (int) (unixTime / 2L);
+ payload[0] = (byte) ((byte) 0x01 | (byte) (ucPageChange & (byte) 0x80));
+ payload[1] = (byte) (halfunixTime & 0xFF);
+ payload[2] = (byte) ((halfunixTime >> 8) & 0xFF);
+ payload[3] = (byte) ((halfunixTime >> 16) & 0xFF);
+ }
+ else if (ucExtMesgType == 2) {
+ payload[0] = (byte) ((byte) 0x02 | (byte) (ucPageChange & (byte) 0x80));
+ payload[1] = (byte) 0xFF;
+ payload[2] = (byte) ((SPEED_SENSOR_ID >> 16) & 0xFF);
+ payload[3] = (byte) ((SPEED_SENSOR_ID >> 24) & 0xFF);
+ }
+ else if (ucExtMesgType == 3) {
+ payload[0] = (byte) ((byte) 0x03 | (byte) (ucPageChange & (byte) 0x80));
+ payload[1] = (byte) 0x01;
+ payload[2] = (byte) 0x01;
+ payload[3] = (byte) 0x01;
+ }
+ if (ucMessageCount >= 68) {
+ ucMessageCount = 0;
+ ucExtMesgType += 1;
+ }
+ } else {
+ payload[0] = (byte) (ucPageChange & 0x80);
+ payload[1] = (byte) 0xFF;
+ payload[2] = (byte) 0xFF;
+ payload[3] = (byte) 0xFF;
+ }
+
+ int unixTime1024 = (int) (unixTime * 1024);
+ payload[4] = (byte) (unixTime1024 & 0xFF);
+ payload[5] = (byte) ((unixTime1024 >> 8) & 0xFF);
+ payload[6] = (byte) (revCounts & 0xFF);
+ payload[7] = (byte) ((revCounts >> 8) & 0xFF);
+
+ if (mIsOpen) {
+ try {
+ // Setting the data to be broadcast on the next channel period
+ mAntChannel.setBroadcastData(payload);
+ } catch (RemoteException e) {
+ channelError(e);
+ }
+ }
+ break;
+ case CHANNEL_COLLISION:
+ ucPageChange += 0x20;
+ ucPageChange &= 0xF0;
+ ucMessageCount += 1;
+ break;
+ case RX_SEARCH_TIMEOUT:
+ // TODO May want to keep searching
+ Log.e(TAG, "No Device Found");
+ break;
+ case CHANNEL_CLOSED:
+ case RX_FAIL:
+ case RX_FAIL_GO_TO_SEARCH:
+ case TRANSFER_RX_FAILED:
+ case TRANSFER_TX_COMPLETED:
+ case TRANSFER_TX_FAILED:
+ case TRANSFER_TX_START:
+ case UNKNOWN:
+ // TODO More complex communication will need to handle these events
+ break;
+ }
+ break;
+ case ANT_VERSION:
+ case BURST_TRANSFER_DATA:
+ case CAPABILITIES:
+ case CHANNEL_ID:
+ case CHANNEL_RESPONSE:
+ case CHANNEL_STATUS:
+ case SERIAL_NUMBER:
+ case OTHER:
+ // TODO More complex communication will need to handle these message types
+ break;
+ }
+ }
+ }
+}
diff --git a/Application/src/main/res/layout/fragment_bluetooth_chat.xml b/Application/src/main/res/layout/fragment_bluetooth_chat.xml
index 95338db..f47b525 100644
--- a/Application/src/main/res/layout/fragment_bluetooth_chat.xml
+++ b/Application/src/main/res/layout/fragment_bluetooth_chat.xml
@@ -101,7 +101,7 @@
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
- android:text="Power"
+ android:text="Power corr"
app:layout_constraintBottom_toTopOf="@+id/Power"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guidelineleft"
diff --git a/Application/src/main/res/values/strings.xml b/Application/src/main/res/values/strings.xml
index 4b9e87c..8b6a118 100644
--- a/Application/src/main/res/values/strings.xml
+++ b/Application/src/main/res/values/strings.xml
@@ -46,7 +46,7 @@
0.0
Level
OpeniConsole Bluetooth active
- OpeniConsole
+ OpeniConsole - TEST
Connect to iConsole
diff --git a/README.md b/README.md
index 324c670..13f4a3c 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,6 @@ Contribute via pull requests. If you want to do major contributions, drop me a m
- workout recording to FIT/TCX/GPX
- workout profiles
- Google street view cycling
-- sensor data broadcasting via ANT
+- sensor data broadcasting via ANT (works, message me for a howto or look [here](https://github.com/haraldh/iconsole-android/issues/4))
See also my [python prototype](https://github.com/haraldh/iconsole) for the bluetooth protocol reverse engineering and sensor broadcasting.
diff --git a/android_antlib_4-14/build.gradle b/android_antlib_4-14/build.gradle
new file mode 100644
index 0000000..04de0a9
--- /dev/null
+++ b/android_antlib_4-14/build.gradle
@@ -0,0 +1,2 @@
+configurations.maybeCreate("default")
+artifacts.add("default", file('android_antlib_4-14-0.jar'))
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ad6a928..cf780a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.3'
+ classpath 'com.android.tools.build:gradle:4.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index adde34a..e4259d6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Jul 06 15:08:55 CEST 2018
+#Mon Apr 12 12:52:47 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
diff --git a/iconsole-android.iml b/iconsole-android.iml
index 24c2e1d..74be301 100644
--- a/iconsole-android.iml
+++ b/iconsole-android.iml
@@ -1,5 +1,5 @@
-
+
@@ -8,11 +8,12 @@
-
+
+
-
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 68c02fb..7f179be 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1 @@
-
-
-
-include 'Application'
+include 'Application', ':android_antlib_4-14'