diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 773d366..1c53797 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,6 +9,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5bae4e2..3b6674f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index c8421df..14a85c1 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -3,6 +3,7 @@
+
diff --git a/Application/Application.iml b/Application/Application.iml
index bb90e68..153210c 100644
--- a/Application/Application.iml
+++ b/Application/Application.iml
@@ -90,17 +90,27 @@
+
+
+
+
+
+
+
-
+
+
+
+
@@ -130,5 +140,6 @@
+
\ No newline at end of file
diff --git a/Application/build.gradle b/Application/build.gradle
index 232ebea..8f87031 100644
--- a/Application/build.gradle
+++ b/Application/build.gradle
@@ -29,8 +29,10 @@ dependencies {
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 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..2b01c64 100644
--- a/Application/src/main/AndroidManifest.xml
+++ b/Application/src/main/AndroidManifest.xml
@@ -43,6 +43,7 @@
+
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() {
+ powerChannelController.close();
+ 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..d678426
--- /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] == 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/android_antlib_4-14/android_antlib_4-14.iml b/android_antlib_4-14/android_antlib_4-14.iml
new file mode 100644
index 0000000..18306c8
--- /dev/null
+++ b/android_antlib_4-14/android_antlib_4-14.iml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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/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'