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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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'