Upgrade ANT support libraries

Rework AntSensorManager (and subclasses) to use asynchronous ANT interfaces
Encapsulate message byte arrays with per-message classes
Add tests
This commit is contained in:
Matthew Simmons
2011-02-21 19:49:43 -05:00
parent c1f649fc83
commit 10cd26af5e
22 changed files with 1603 additions and 736 deletions
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google 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
@@ -18,10 +18,14 @@ package com.google.android.apps.mytracks.services.sensors;
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.sensors.ant.AntChannelIdMessage;
import com.google.android.apps.mytracks.services.sensors.ant.AntChannelResponseMessage;
import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
@@ -49,62 +53,70 @@ public class AntDirectSensorManager extends AntSensorManager {
public AntDirectSensorManager(Context context) {
super(context);
deviceNumberHRM = WILDCARD;
// First read the the device id that we will be pairing with.
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
deviceNumberHRM =
deviceNumberHRM =
(short) prefs.getInt(context.getString(R.string.ant_heart_rate_sensor_id_key), 0);
}
Log.i(TAG, "Pairing with heart rate monitor: " + deviceNumberHRM);
Log.i(TAG, "Will pair with heart rate monitor: " + deviceNumberHRM);
}
@Override
public void handleMessage(byte[] antMessage) {
int channel =
antMessage[AntMesg.MESG_DATA_OFFSET] & AntDefine.CHANNEL_NUMBER_MASK;
protected boolean handleMessage(byte messageId, byte[] messageData) {
if (super.handleMessage(messageId, messageData)) {
return true;
}
int channel = messageData[0] & AntDefine.CHANNEL_NUMBER_MASK;
switch (channel) {
case HRM_CHANNEL:
antDecodeHRM(antMessage);
antDecodeHRM(messageId, messageData);
break;
default:
Log.d(TAG, "Unhandled message: " + channel);
}
return true;
}
/**
* Decode an ant heart rate monitor message.
* @param antMessage The byte array received from the heart rate monitor.
* @param messageData The byte array received from the heart rate monitor.
*/
private void antDecodeHRM(byte[] antMessage) {
// message ID == broadcast
switch (antMessage[AntMesg.MESG_ID_OFFSET]) {
private void antDecodeHRM(int messageId, byte[] messageData) {
switch (messageId) {
case AntMesg.MESG_BROADCAST_DATA_ID:
handleBroadcastData(antMessage);
handleBroadcastData(messageData);
break;
case AntMesg.MESG_RESPONSE_EVENT_ID:
handleMessageResponse(antMessage);
handleMessageResponse(messageData);
break;
case AntMesg.MESG_CHANNEL_ID_ID:
handleChannelId(antMessage);
handleChannelId(messageData);
break;
default:
Log.e(TAG, "Unexpected message id: " + antMessage[3]);
Log.e(TAG, "Unexpected message id: " + messageId);
}
}
private void handleBroadcastData(byte[] antMessage) {
if (deviceNumberHRM == WILDCARD) {
getAntReceiver().ANTRequestMessage(HRM_CHANNEL,
AntMesg.MESG_CHANNEL_ID_ID);
try {
getAntReceiver().ANTRequestMessage(HRM_CHANNEL,
AntMesg.MESG_CHANNEL_ID_ID);
} catch (AntInterfaceException e) {
Log.e(TAG, "ANT error handling broadcast data", e);
}
Log.d(TAG, "Requesting channel id id.");
}
setSensorState(Sensor.SensorState.CONNECTED);
int bpm = (int) antMessage[10] & 0xFF;
int bpm = (int) antMessage[8] & 0xFF;
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(bpm)
.setState(Sensor.SensorState.SENDING);
@@ -115,11 +127,9 @@ public class AntDirectSensorManager extends AntSensorManager {
.build();
}
void handleChannelId(byte[] antMessage) {
// Store the device id.
deviceNumberHRM =
(short) (((int) antMessage[3] & 0xFF |
((int) (antMessage[4] & 0xFF) << 8)) & 0xFFFF);
void handleChannelId(byte[] rawMessage) {
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
deviceNumberHRM = message.getDeviceNumber();
Log.i(TAG, "Found device id: " + deviceNumberHRM);
SharedPreferences prefs = context.getSharedPreferences(
@@ -129,26 +139,31 @@ public class AntDirectSensorManager extends AntSensorManager {
editor.commit();
}
private void handleMessageResponse(byte[] antMessage) {
if (antMessage[3] == AntMesg.MESG_EVENT_ID &&
antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel((byte) 0);
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
private void handleMessageResponse(byte[] rawMessage) {
AntChannelResponseMessage message = new AntChannelResponseMessage(rawMessage);
switch (message.getMessageId()) {
case AntMesg.MESG_EVENT_ID:
if (message.getMessageCode() == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(TAG, "Search timed out. Unassigning channel.");
try {
getAntReceiver().ANTUnassignChannel((byte) 0);
} catch (AntInterfaceException e) {
Log.e(TAG, "ANT error unassigning channel", e);
}
setSensorState(Sensor.SensorState.DISCONNECTED);
}
break;
case AntMesg.MESG_UNASSIGN_CHANNEL_ID:
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
break;
}
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
super.setupChannel();
antChannelSetup(NETWORK_NUMBER,
@Override protected void setupAntSensorChannels() {
setupAntSensorChannel(NETWORK_NUMBER,
HRM_CHANNEL,
deviceNumberHRM,
HEART_RATE_DEVICE_TYPE,
@@ -156,7 +171,6 @@ public class AntDirectSensorManager extends AntSensorManager {
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
setSensorState(Sensor.SensorState.CONNECTING);
}
public short getDeviceNumberHRM() {
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google 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
@@ -22,6 +22,8 @@ import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
@@ -104,8 +106,12 @@ public class AntSRMSensorManager extends AntSensorManager {
private void handleBroadcastData(byte[] antMessage) {
if (deviceId == WILDCARD) {
getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID);
Log.d(MyTracksConstants.TAG, "Requesting channel id id.");
try {
Log.d(MyTracksConstants.TAG, "Requesting channel id id.");
getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID);
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "Failed to request channel id id", e);
}
}
setSensorState(Sensor.SensorState.CONNECTED);
switch(antMessage[MESSAGE_TYPE_INDEX]) {
@@ -138,7 +144,11 @@ public class AntSRMSensorManager extends AntSensorManager {
&& antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(MyTracksConstants.TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel(channel);
try {
getAntReceiver().ANTUnassignChannel(channel);
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "Failed to unassign ANT channel", e);
}
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
@@ -207,12 +217,10 @@ public class AntSRMSensorManager extends AntSensorManager {
sensorData = builder.build();
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
antChannelSetup(NETWORK_NUMBER,
protected void setupAntSensorChannels() {
lastMessageId = 0;
setupAntSensorChannel(NETWORK_NUMBER,
channel,
deviceId,
DEVICE_TYPE,
@@ -220,7 +228,5 @@ public class AntSRMSensorManager extends AntSensorManager {
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
lastMessageId = 0;
setSensorState(Sensor.SensorState.CONNECTING);
}
}
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google 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
@@ -16,37 +16,56 @@
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.ant.AntStartupMessage;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* An abstract class for ANT+ based sensors.
* This is the common superclass for ANT-based sensors. It handles tasks which
* apply to the ANT framework as a whole, such as framework initialization and
* destruction. Subclasses are expected to handle the initialization and
* management of individual sensors.
*
* Implementation:
*
* The initialization process is somewhat complicated. This is due in part to
* the asynchronous nature of ANT Radio Service initialization, and in part due
* to an apparent bug in that service. The following is an overview of the
* initialization process.
*
* Initialization begins in {@link #setupChannel}, which is invoked by the
* {@link SensorManager} when track recording begins. {@link #setupChannel}
* asks the ANT Radio Service (via {@link AntInterface}) to start, using a
* {@link AntInterface.ServiceListener} to indicate when the service has
* connected. {@link #serviceConnected} claims and enables the Radio Service,
* and then resets it to a known state for our use. Completion of reset is
* indicated by receipt of a startup message (see {@link AntStartupMessage}).
* Once we've received that message, the ANT service is ready for use, and we
* can start sensor-specific initialization using
* {@link #setupAntSensorChannels}. The initialization of each sensor will
* usually result in a call to {@link #setupAntSensorChannel}.
*
* @author Sandor Dornbush
*/
public abstract class AntSensorManager extends SensorManager {
// ANT Service
public static final int EVENT_ANT_ENABLED = 1;
public static final int EVENT_ANT_DISABLED = 2;
// Pairing
protected static final short WILDCARD = 0;
private AntInterface antReceiver;
private boolean status;
// Flag to know if the ANT App was interrupted
// TODO: This code path is not used but probably should be.
private boolean antInterrupted;
@@ -57,40 +76,29 @@ public abstract class AntSensorManager extends SensorManager {
protected SensorDataSet sensorData;
protected Context context;
private static final boolean DEBUGGING = false;
/**
* Receives all of the ANT intents and dispatches to the proper handler.
*/
private final BroadcastReceiver receiver = new BroadcastReceiver() {
/** Receives and logs all status ANT intents. */
private final BroadcastReceiver statusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String antAction = intent.getAction();
Log.i(MyTracksConstants.TAG, "enter status onReceive" + antAction);
}
};
Log.i(MyTracksConstants.TAG, "enter onReceive" + antAction);
if (antAction.equals(AntInterfaceIntent.ANT_ENABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_ENABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_ENABLED, 0));
}
if (antAction.equals(AntInterfaceIntent.ANT_DISABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_DISABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_DISABLED, 0));
}
/** Receives all data ANT intents. */
private final BroadcastReceiver dataReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String antAction = intent.getAction();
Log.i(MyTracksConstants.TAG, "enter data onReceive" + antAction);
if (antAction.equals(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION)) {
byte[] antMessage =
intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
byte[] antMessage = intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
if (DEBUGGING) {
StringBuilder sb =
new StringBuilder("ANT_RX_MESSAGE_ACTION Rx:");
for (int i = 0; i < antMessage.length; i++)
sb.append(String.format("[%X]]", (antMessage[i] & 0xFF)));
Log.d(MyTracksConstants.TAG, sb.toString());
Log.d(MyTracksConstants.TAG, "Received RX message " + messageToString(antMessage));
}
handleMessage(antMessage);
@@ -99,60 +107,39 @@ public abstract class AntSensorManager extends SensorManager {
};
/**
* Handler for all the ANT related events. The events will be handled only
* when we are in the ANT application main menu
**/
private Handler handler = new Handler() {
* ANT uses this listener to tell us when it has bound to the ANT Radio
* Service. We can't start sending ANT commands until we've been notified
* (via this listener) that the Radio Service has connected.
*/
private AntInterface.ServiceListener antServiceListener = new AntInterface.ServiceListener() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_ANT_ENABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_ENABLED");
break;
case EVENT_ANT_DISABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_DISABLED");
break;
}
public void onServiceConnected() {
serviceConnected();
}
@Override
public void onServiceDisconnected() {
Log.d(MyTracksConstants.TAG, "ANT interface reports disconnection");
}
};
public AntSensorManager(Context context) {
this.context = context;
// Need to enable the ANT if it is not enabled earlier
antReceiver = AntInterface.getInstance(context, null);
if (antReceiver == null) {
return;
}
status = antReceiver.isEnabled();
if (!status) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
status = antReceiver.enable();
if (!status) {
Log.e(MyTracksConstants.TAG, "Can not enable ANT interface");
} else {
Log.i(MyTracksConstants.TAG, "Powering on Radio");
}
} else {
Log.i(MyTracksConstants.TAG, "Radio already enabled");
}
}
}
@Override
public void setupChannel() {
Log.i(MyTracksConstants.TAG, "Registering for ant intents.");
// Register for ANT intent broadcasts.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(receiver, intentFilter);
public void onDestroy() {
Log.i(MyTracksConstants.TAG, "destroying AntSensorManager");
context.unregisterReceiver(statusReceiver);
context.unregisterReceiver(dataReceiver);
try {
antReceiver.releaseInterface();
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to release ANT interface", e);
}
antReceiver.destroy();
}
@Override
@@ -165,64 +152,184 @@ public abstract class AntSensorManager extends SensorManager {
return true;
}
public abstract void handleMessage(byte[] antMessage);
@Override
public void onDestroy() {
Log.i(MyTracksConstants.TAG, "Unregistering for ant intents.");
context.unregisterReceiver(receiver);
}
/**
* Set up an ANT+ channel.
*
* @return True on success.
*/
protected boolean antChannelSetup(byte networkNumber, byte channelNumber,
short deviceNumber, byte deviceType, byte txType, short channelPeriod,
byte radioFreq, byte proxSearch) {
// Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 =
// ANTFS)
if (!antReceiver.ANTAssignChannel(channelNumber,
AntDefine.PARAMETER_RX_NOT_TX, networkNumber)) {
return false;
}
if (!antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType,
txType)) {
return false;
}
if (!antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod)) {
return false;
}
if (!antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq)) {
return false;
}
// Disable high priority search
if (!antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0)) {
return false;
}
// Set search timeout to 30 seconds (low priority search))
if (!antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12)) {
return false;
}
if (deviceNumber == WILDCARD) {
// Configure proximity search, if using wild card search
if (!antReceiver.ANTSetProximitySearch(channelNumber, proxSearch)) {
return false;
}
}
return !antReceiver.ANTOpenChannel(channelNumber);
}
public AntInterface getAntReceiver() {
return antReceiver;
}
/**
* This is the interface used by the {@link SensorManager} to tell this
* class when to start. It handles initialization of the ANT framework,
* eventually resulting in sensor-specific initialization via
* {@link #setupAntSensorChannels}.
*/
@Override
protected final void setupChannel() {
// We handle this unpleasantly because the UI should've checked for ANT
// support before it even instantiated this class.
if (!AntInterface.hasAntSupport(context)) {
throw new IllegalStateException("device does not have ANT support");
}
// We register for ANT intents early because we want to have a record of
// the status intents in the log as we start up.
registerForAntIntents();
antReceiver = AntInterface.getInstance(context, antServiceListener);
if (antReceiver == null) {
Log.e(MyTracksConstants.TAG, "Failed to get ANT Receiver");
return;
}
setSensorState(Sensor.SensorState.CONNECTING);
}
/**
* This method is invoked via the ServiceListener when we're connected to
* the ANT service. If we're just starting up, this is our first opportunity
* to initiate any ANT commands.
*/
private synchronized void serviceConnected() {
Log.d(MyTracksConstants.TAG, "ANT service connected");
try {
if (!antReceiver.claimInterface()) {
Log.e(MyTracksConstants.TAG, "failed to claim ANT interface");
return;
}
if (!antReceiver.isEnabled()) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
Log.i(MyTracksConstants.TAG, "Powering on Radio");
antReceiver.enable();
}
} else {
Log.i(MyTracksConstants.TAG, "Radio already enabled");
}
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to enable ANT", e);
}
try {
// We expect this call to throw an exception due to a bug in the ANT
// Radio Service. It won't actually fail, though, as we'll get the
// startup message (see {@link AntStartupMessage}) one normally expects
// after a reset. Channel initialization can proceed once we receive
// that message.
antReceiver.ANTResetSystem();
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to reset ANT (expected exception)", e);
}
}
/**
* Process a raw ANT message.
* @param antMessage the ANT message, including the size and message ID bytes
* @deprecated Use {@link #handleMessage(int, byte[])} instead.
*/
protected void handleMessage(byte[] antMessage) {
int len = antMessage[0];
if (len != antMessage.length - 2 || antMessage.length <= 2) {
Log.e(MyTracksConstants.TAG, "Invalid message: " + messageToString(antMessage));
return;
}
byte messageId = antMessage[1];
// Arrays#copyOfRange doesn't exist??
byte[] messageData = new byte[antMessage.length - 2];
System.arraycopy(antMessage, 2, messageData, 0, antMessage.length - 2);
handleMessage(messageId, messageData);
}
/**
* Process a raw ANT message.
* @param messageId the message ID. See the ANT Message Protocol and Usage
* guide, section 9.3.
* @param messageData the ANT message, without the size and message ID bytes.
* @return true if this method has taken responsibility for the passed
* message; false otherwise.
*/
protected boolean handleMessage(byte messageId, byte[] messageData) {
if (messageId == AntMesg.MESG_STARTUP_MESG_ID) {
Log.d(MyTracksConstants.TAG, String.format(
"Received startup message (reason %02x); initializing channel",
new AntStartupMessage(messageData).getMessage()));
setupAntSensorChannels();
return true;
}
return false;
}
/**
* Subclasses define this method to perform sensor-specific initialization.
* When this method is called, the ANT framework has been enabled, and is
* ready for use.
*/
protected abstract void setupAntSensorChannels();
/**
* Used by subclasses to set up an ANT channel for a single sensor. A given
* subclass may invoke this method multiple times if the subclass is
* responsible for more than one sensor.
*
* @return true on success
*/
protected boolean setupAntSensorChannel(byte networkNumber, byte channelNumber,
short deviceNumber, byte deviceType, byte txType, short channelPeriod,
byte radioFreq, byte proxSearch) {
try {
// Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 =
// ANTFS)
antReceiver.ANTAssignChannel(channelNumber, AntDefine.PARAMETER_RX_NOT_TX, networkNumber);
antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType, txType);
antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod);
antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq);
// Disable high priority search
antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0);
// Set search timeout to 30 seconds (low priority search))
antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12);
if (deviceNumber == WILDCARD) {
// Configure proximity search, if using wild card search
antReceiver.ANTSetProximitySearch(channelNumber, proxSearch);
}
antReceiver.ANTOpenChannel(channelNumber);
return true;
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to setup ANT channel", e);
return false;
}
}
private void registerForAntIntents() {
Log.i(MyTracksConstants.TAG, "Registering for ant intents.");
// Register for ANT intent broadcasts.
IntentFilter statusIntentFilter = new IntentFilter();
statusIntentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_INTERFACE_CLAIMED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_RESET_ACTION);
context.registerReceiver(statusReceiver, statusIntentFilter);
IntentFilter dataIntentFilter = new IntentFilter();
dataIntentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(dataReceiver, dataIntentFilter);
}
private String messageToString(byte[] message) {
StringBuilder out = new StringBuilder();
for (byte b : message) {
out.append(String.format("%s%02x", (out.length() == 0 ? "" : " "), b));
}
return out.toString();
}
}
@@ -41,7 +41,7 @@ public abstract class SensorManager {
public static final int RETRY_PERIOD = 30000;
private Sensor.SensorState sensorState = Sensor.SensorState.NONE;
private long sensorStateTimestamp = 0;
/**
@@ -89,7 +89,11 @@ public abstract class SensorManager {
timer.schedule(checkSensorManager, RETRY_PERIOD, RETRY_PERIOD);
}
public abstract void setupChannel();
/**
* This method is used to set up any necessary connections to underlying
* sensor hardware.
*/
protected abstract void setupChannel();
public void shutdown() {
timer.cancel();
@@ -0,0 +1,56 @@
/*
* Copyright 2011 Google 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 com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Channel ID message.
* (ANT Message ID 0x51, Protocol & Usage guide v4.2 section 9.5.7.2)
*
* @author Matthew Simmons
*/
public class AntChannelIdMessage extends AntMessage {
private byte channelNumber;
private short deviceNumber;
private byte deviceTypeId;
private byte transmissionType;
public AntChannelIdMessage(byte[] messageData) {
channelNumber = messageData[0];
deviceNumber = decodeShort(messageData[1], messageData[2]);
deviceTypeId = messageData[3];
transmissionType = messageData[4];
}
/** Returns the channel number */
public byte getChannelNumber() {
return channelNumber;
}
/** Returns the device number */
public short getDeviceNumber() {
return deviceNumber;
}
/** Returns the device type */
public byte getDeviceTypeId() {
return deviceTypeId;
}
/** Returns the transmission type */
public byte getTransmissionType() {
return transmissionType;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2011 Google 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 com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Channel Response / Event message.
* (ANT Message ID 0x40, Protocol & Usage guide v4.2 section 9.5.6.1)
*
* @author Matthew Simmons
*/
public class AntChannelResponseMessage extends AntMessage {
private byte channelNumber;
private byte messageId;
private byte messageCode;
public AntChannelResponseMessage(byte[] messageData) {
channelNumber = messageData[0];
messageId = messageData[1];
messageCode = messageData[2];
}
/** Returns the channel number */
public byte getChannelNumber() {
return channelNumber;
}
/** Returns the ID of the message being responded to */
public byte getMessageId() {
return messageId;
}
/** Returns the code for a specific response or event */
public byte getMessageCode() {
return messageCode;
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2011 Google 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 com.google.android.apps.mytracks.services.sensors.ant;
/**
* This is a common superclass for ANT message subclasses.
*
* @author Matthew Simmons
*/
public class AntMessage {
protected AntMessage() {}
/** Build a short value from its constituent bytes */
protected static short decodeShort(byte b0, byte b1) {
short value = b0;
value |= ((short) b1) << 8;
return value;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2011 Google 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 com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Startup message.
* (ANT Message ID 0x6f, Protocol & Usage guide v4.2 section 9.5.3.1)
*
* @author Matthew Simmons
*/
public class AntStartupMessage extends AntMessage {
private byte message;
public AntStartupMessage(byte[] messageData) {
message = messageData[0];
}
/** Returns the cause of the startup */
public byte getMessage() {
return message;
}
}