forked from upstream-mirrors/OpenTracks
Merge heads
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 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;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A preference for an ANT device pairing.
|
||||
* Currently this shows the ID and lets the user clear that ID for future pairing.
|
||||
* TODO: Support pairing from this preference.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class AntPreference extends Preference {
|
||||
|
||||
private final static int DEFAULT_PERSISTEDINT = 0;
|
||||
|
||||
public AntPreference(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public AntPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
int sensorId = getPersistedInt(AntPreference.DEFAULT_PERSISTEDINT);
|
||||
if (sensorId == 0) {
|
||||
setSummary(R.string.settings_sensor_ant_not_paired);
|
||||
} else {
|
||||
setSummary(getContext().getString(R.string.settings_sensor_ant_paired, sensorId));
|
||||
}
|
||||
|
||||
// Add actions to allow repairing.
|
||||
setOnPreferenceClickListener(
|
||||
new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
AntPreference.this.persistInt(0);
|
||||
setSummary(R.string.settings_sensor_ant_not_paired);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -96,8 +96,7 @@ public class BluetoothSensorManager extends SensorManager {
|
||||
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
|
||||
String deviceName = message.getData()
|
||||
.getString(BluetoothConnectionManager.KEY_DEVICE_NAME);
|
||||
Toast.makeText(context,
|
||||
context.getString(R.string.settings_sensor_bluetooth_connected, deviceName),
|
||||
Toast.makeText(context, context.getString(R.string.settings_sensor_connected, deviceName),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case BluetoothConnectionManager.MESSAGE_READ:
|
||||
|
||||
+2
-5
@@ -16,8 +16,7 @@
|
||||
|
||||
package com.google.android.apps.mytracks.services.sensors;
|
||||
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntDirectSensorManager;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntSrmBridgeSensorManager;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntSensorManager;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -98,9 +97,7 @@ public class SensorManagerFactory {
|
||||
context, R.string.sensor_type_key, sensorTypeValueNone);
|
||||
|
||||
if (sensorType.equals(context.getString(R.string.sensor_type_value_ant))) {
|
||||
return new AntDirectSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_srm_ant_bridge))) {
|
||||
return new AntSrmBridgeSensorManager(context);
|
||||
return new AntSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_zephyr))) {
|
||||
return new ZephyrSensorManager(context);
|
||||
} else if (sensorType.equals(context.getString(R.string.sensor_type_value_polar))) {
|
||||
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services.sensors.ant;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.dsi.ant.AntDefine;
|
||||
import com.dsi.ant.AntMesg;
|
||||
import com.dsi.ant.exception.AntInterfaceException;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A sensor manager to connect ANT+ sensors.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
public class AntDirectSensorManager extends AntSensorManager
|
||||
implements AntSensorDataCollector {
|
||||
|
||||
// allocating one channel for each sensor type
|
||||
private static final byte HEART_RATE_CHANNEL = 0;
|
||||
private static final byte CADENCE_CHANNEL = 1;
|
||||
private static final byte CADENCE_SPEED_CHANNEL = 2;
|
||||
|
||||
// ids for device number preferences
|
||||
private static final int sensorIdKeys[] = {
|
||||
R.string.ant_heart_rate_sensor_id_key,
|
||||
R.string.ant_cadence_sensor_id_key,
|
||||
R.string.ant_cadence_speed_sensor_id_key,
|
||||
};
|
||||
|
||||
private AntSensorBase sensors[] = null;
|
||||
|
||||
// current data to be sent for SensorDataSet
|
||||
private static final byte HEART_RATE_DATA_INDEX = 0;
|
||||
private static final byte CADENCE_DATA_INDEX = 1;
|
||||
private int currentSensorData[] = { -1, -1 };
|
||||
|
||||
private long lastDataSentMillis = 0;
|
||||
private byte connectingChannelsBitmap = 0;
|
||||
|
||||
public AntDirectSensorManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleMessage(byte messageId, byte[] messageData) {
|
||||
if (super.handleMessage(messageId, messageData)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int channel = messageData[0] & AntDefine.CHANNEL_NUMBER_MASK;
|
||||
if (sensors == null || channel >= sensors.length) {
|
||||
Log.d(TAG, "Unknown channel in message: " + channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
AntSensorBase sensor = sensors[channel];
|
||||
switch (messageId) {
|
||||
case AntMesg.MESG_BROADCAST_DATA_ID:
|
||||
if (sensor.getDeviceNumber() == WILDCARD) {
|
||||
resolveWildcardDeviceNumber((byte) channel);
|
||||
}
|
||||
sensor.handleBroadcastData(messageData, this);
|
||||
break;
|
||||
case AntMesg.MESG_RESPONSE_EVENT_ID:
|
||||
handleMessageResponse(messageData);
|
||||
break;
|
||||
case AntMesg.MESG_CHANNEL_ID_ID:
|
||||
sensor.setDeviceNumber(handleChannelId(messageData));
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unexpected ANT message id: " + messageId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private short handleChannelId(byte[] rawMessage) {
|
||||
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
|
||||
short deviceNumber = message.getDeviceNumber();
|
||||
byte channel = message.getChannelNumber();
|
||||
if (channel >= sensors.length) {
|
||||
Log.d(TAG, "Unknown channel in message: " + channel);
|
||||
return WILDCARD;
|
||||
}
|
||||
Log.i(TAG, "Found ANT device id: " + deviceNumber + " on channel: " + channel);
|
||||
|
||||
PreferencesUtils.setInt(context, sensorIdKeys[channel], deviceNumber);
|
||||
return deviceNumber;
|
||||
}
|
||||
|
||||
private void channelOut(byte channel)
|
||||
{
|
||||
if (channel >= sensors.length) {
|
||||
Log.d(TAG, "Unknown channel in message: " + channel);
|
||||
return;
|
||||
}
|
||||
connectingChannelsBitmap &= ~(1 << channel);
|
||||
Log.i(TAG, "ANT channel " + channel + " disconnected.");
|
||||
|
||||
if (sensors[channel].getDeviceNumber() != WILDCARD) {
|
||||
Log.i(TAG, "Retrying....");
|
||||
if (setupChannel(sensors[channel], channel)) {
|
||||
connectingChannelsBitmap |= 1 << channel;
|
||||
}
|
||||
}
|
||||
if (connectingChannelsBitmap == 0) {
|
||||
setSensorState(Sensor.SensorState.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMessageResponse(byte[] rawMessage) {
|
||||
AntChannelResponseMessage message = new AntChannelResponseMessage(rawMessage);
|
||||
byte channel = message.getChannelNumber();
|
||||
switch (message.getMessageId()) {
|
||||
case AntMesg.MESG_EVENT_ID:
|
||||
if (message.getMessageCode() == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
|
||||
// Search timeout
|
||||
Log.w(TAG, "ANT search timed out. Unassigning channel " + channel);
|
||||
try {
|
||||
getAntReceiver().ANTUnassignChannel(channel);
|
||||
} catch (AntInterfaceException e) {
|
||||
Log.e(TAG, "ANT error unassigning channel", e);
|
||||
channelOut(channel);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AntMesg.MESG_UNASSIGN_CHANNEL_ID:
|
||||
channelOut(channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveWildcardDeviceNumber(byte channel) {
|
||||
try {
|
||||
getAntReceiver().ANTRequestMessage(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 on channel: " + channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupAntSensorChannels() {
|
||||
short devIds[] = new short[sensorIdKeys.length];
|
||||
|
||||
for (int i = 0; i < sensorIdKeys.length; ++i) {
|
||||
devIds[i] = (short) PreferencesUtils.getInt(context, sensorIdKeys[i], WILDCARD);
|
||||
}
|
||||
|
||||
sensors = new AntSensorBase[] {
|
||||
new HeartRateSensor(devIds[HEART_RATE_CHANNEL]),
|
||||
new CadenceSensor(devIds[CADENCE_CHANNEL]),
|
||||
new CadenceSpeedSensor(devIds[CADENCE_SPEED_CHANNEL]),
|
||||
};
|
||||
|
||||
connectingChannelsBitmap = 0;
|
||||
for (int i = 0; i < sensors.length; ++i) {
|
||||
if (setupChannel(sensors[i], (byte) i)) {
|
||||
connectingChannelsBitmap |= 1 << i;
|
||||
}
|
||||
}
|
||||
if (connectingChannelsBitmap == 0) {
|
||||
setSensorState(Sensor.SensorState.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean setupChannel(AntSensorBase sensor, byte channel) {
|
||||
Log.i(TAG, "setup channel=" + channel + " deviceType=" + sensor.getDeviceType());
|
||||
|
||||
return setupAntSensorChannel(sensor.getNetworkNumber(),
|
||||
channel,
|
||||
sensor.getDeviceNumber(),
|
||||
sensor.getDeviceType(),
|
||||
(byte) 0x01,
|
||||
sensor.getChannelPeriod(),
|
||||
sensor.getFrequency(),
|
||||
(byte) 0);
|
||||
}
|
||||
|
||||
private void sendSensorData(byte index, int value) {
|
||||
if (index >= currentSensorData.length) {
|
||||
Log.w(TAG, "invalid index in sendSensorData:" + index);
|
||||
return;
|
||||
}
|
||||
currentSensorData[index] = value;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
// data comes in at ~4Hz rate from the sensors, so after >300 msec
|
||||
// fresh data is here from all the connected sensors
|
||||
if (now < lastDataSentMillis + 300) {
|
||||
return;
|
||||
}
|
||||
lastDataSentMillis = now;
|
||||
setSensorState(Sensor.SensorState.CONNECTED);
|
||||
|
||||
Sensor.SensorDataSet.Builder b = Sensor.SensorDataSet.newBuilder();
|
||||
if (currentSensorData[HEART_RATE_DATA_INDEX] >= 0) {
|
||||
b.setHeartRate(
|
||||
Sensor.SensorData.newBuilder()
|
||||
.setValue(currentSensorData[HEART_RATE_DATA_INDEX])
|
||||
.setState(Sensor.SensorState.SENDING));
|
||||
}
|
||||
|
||||
if (currentSensorData[CADENCE_DATA_INDEX] >= 0) {
|
||||
b.setCadence(
|
||||
Sensor.SensorData.newBuilder()
|
||||
.setValue(currentSensorData[CADENCE_DATA_INDEX])
|
||||
.setState(Sensor.SensorState.SENDING));
|
||||
}
|
||||
sensorData = b.setCreationTime(now).build();
|
||||
}
|
||||
|
||||
public void setCadence(int value) {
|
||||
sendSensorData(CADENCE_DATA_INDEX, value);
|
||||
}
|
||||
|
||||
public void setHeartRate(int value) {
|
||||
sendSensorData(HEART_RATE_DATA_INDEX, value);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
int value = b0 & 0xFF;
|
||||
value |= (b1 & 0xFF) << 8;
|
||||
return (short)value;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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;
|
||||
|
||||
/**
|
||||
* A enum which stores static data about ANT sensors.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public enum AntSensor {
|
||||
SENSOR_HEART_RATE (Constants.ANT_DEVICE_TYPE_HRM),
|
||||
SENSOR_CADENCE (Constants.ANT_DEVICE_TYPE_CADENCE),
|
||||
SENSOR_SPEED (Constants.ANT_DEVICE_TYPE_SPEED),
|
||||
SENSOR_POWER (Constants.ANT_DEVICE_TYPE_POWER);
|
||||
|
||||
private static class Constants {
|
||||
public static byte ANT_DEVICE_TYPE_POWER = 11;
|
||||
public static byte ANT_DEVICE_TYPE_HRM = 120;
|
||||
public static byte ANT_DEVICE_TYPE_CADENCE = 122;
|
||||
public static byte ANT_DEVICE_TYPE_SPEED = 123;
|
||||
};
|
||||
|
||||
private final byte deviceType;
|
||||
|
||||
private AntSensor(byte deviceType) {
|
||||
this.deviceType = deviceType;
|
||||
}
|
||||
|
||||
public byte getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Base class for ANT+ sensors.
|
||||
*
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
|
||||
public abstract class AntSensorBase {
|
||||
/*
|
||||
* These constants are defined by the ANT+ spec.
|
||||
*/
|
||||
public static final byte NETWORK_NUMBER = 1;
|
||||
public static final byte RF_FREQUENCY = 57;
|
||||
|
||||
private short deviceNumber;
|
||||
private final byte deviceType;
|
||||
private final short channelPeriod;
|
||||
|
||||
AntSensorBase(short deviceNumber, byte deviceType,
|
||||
String deviceTypeString, short channelPeriod) {
|
||||
this.deviceNumber = deviceNumber;
|
||||
this.deviceType = deviceType;
|
||||
this.channelPeriod = channelPeriod;
|
||||
|
||||
Log.i(TAG, "Will pair with " + deviceTypeString + " device: " + ((int) deviceNumber & 0xFFFF));
|
||||
}
|
||||
|
||||
public abstract void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c);
|
||||
|
||||
public void setDeviceNumber(short dn) {
|
||||
deviceNumber = dn;
|
||||
}
|
||||
|
||||
public short getDeviceNumber() {
|
||||
return deviceNumber;
|
||||
}
|
||||
|
||||
public byte getNetworkNumber() {
|
||||
return NETWORK_NUMBER;
|
||||
}
|
||||
|
||||
public byte getFrequency() {
|
||||
return RF_FREQUENCY;
|
||||
}
|
||||
|
||||
public byte getDeviceType() {
|
||||
return deviceType;
|
||||
}
|
||||
|
||||
public short getChannelPeriod() {
|
||||
return channelPeriod;
|
||||
}
|
||||
};
|
||||
+645
-311
File diff suppressed because it is too large
Load Diff
-242
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.dsi.ant.AntDefine;
|
||||
import com.dsi.ant.AntMesg;
|
||||
import com.dsi.ant.exception.AntInterfaceException;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.BuildConfig;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A sensor manager to the PC7 SRM ANT+ bridge.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Umran Abdulla
|
||||
*/
|
||||
public class AntSrmBridgeSensorManager extends AntSensorManager {
|
||||
|
||||
/*
|
||||
* These constants are defined by the ANT+ spec.
|
||||
*/
|
||||
public static final byte CHANNEL_NUMBER = 0;
|
||||
public static final byte NETWORK_NUMBER = 0;
|
||||
public static final byte DEVICE_TYPE = 12;
|
||||
public static final byte NETWORK_ID = 1;
|
||||
public static final short CHANNEL_PERIOD = 8192;
|
||||
public static final byte RF_FREQUENCY = 50;
|
||||
|
||||
private static final int INDEX_MESSAGE_TYPE = 1;
|
||||
private static final int INDEX_MESSAGE_ID = 2;
|
||||
private static final int INDEX_MESSAGE_POWER = 3;
|
||||
private static final int INDEX_MESSAGE_SPEED = 5;
|
||||
private static final int INDEX_MESSAGE_CADENCE = 7;
|
||||
private static final int INDEX_MESSAGE_BPM = 8;
|
||||
|
||||
private static final int MSG_INITIAL = 5;
|
||||
private static final int MSG_DATA = 6;
|
||||
|
||||
private short deviceNumber;
|
||||
|
||||
public AntSrmBridgeSensorManager(Context context) {
|
||||
super(context);
|
||||
|
||||
Log.i(TAG, "new ANT SRM Bridge Sensor Manager created");
|
||||
|
||||
deviceNumber = WILDCARD;
|
||||
|
||||
// First read the the device id that we will be pairing with.
|
||||
deviceNumber = (short) PreferencesUtils.getInt(
|
||||
context, R.string.ant_srm_bridge_sensor_id_key, WILDCARD);
|
||||
Log.i(TAG, "Will pair with device: " + deviceNumber);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean handleMessage(byte messageId, byte[] messageData) {
|
||||
if (super.handleMessage(messageId, messageData)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Received ANT msg: " + AntUtils.antMessageToString(messageId) + "(" + messageId + ")");
|
||||
}
|
||||
|
||||
int channel = messageData[0] & AntDefine.CHANNEL_NUMBER_MASK;
|
||||
switch (channel) {
|
||||
case CHANNEL_NUMBER:
|
||||
decodeChannelMsg(messageId, messageData);
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Unhandled message: " + channel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an ant device message.
|
||||
* @param messageData The byte array received from the device.
|
||||
*/
|
||||
private void decodeChannelMsg(int messageId, byte[] messageData) {
|
||||
switch (messageId) {
|
||||
case AntMesg.MESG_BROADCAST_DATA_ID:
|
||||
handleBroadcastData(messageData);
|
||||
break;
|
||||
case AntMesg.MESG_RESPONSE_EVENT_ID:
|
||||
handleMessageResponse(messageData);
|
||||
break;
|
||||
case AntMesg.MESG_CHANNEL_ID_ID:
|
||||
handleChannelId(messageData);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unexpected message id: " + messageId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBroadcastData(byte[] antMessage) {
|
||||
if (deviceNumber == WILDCARD) {
|
||||
try {
|
||||
getAntReceiver().ANTRequestMessage(CHANNEL_NUMBER, 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 messageType = antMessage[INDEX_MESSAGE_TYPE] & 0xFF;
|
||||
Log.d(TAG, "Received message-type=" + messageType);
|
||||
|
||||
switch (messageType) {
|
||||
case MSG_INITIAL:
|
||||
break;
|
||||
case MSG_DATA:
|
||||
parseDataMsg(antMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDataMsg(byte[] msg)
|
||||
{
|
||||
int messageId = msg[INDEX_MESSAGE_ID] & 0xFF;
|
||||
Log.d(TAG, "Received message-id=" + messageId);
|
||||
|
||||
int powerVal = (((msg[INDEX_MESSAGE_POWER] & 0xFF) << 8) |
|
||||
(msg[INDEX_MESSAGE_POWER+1] & 0xFF));
|
||||
@SuppressWarnings("unused")
|
||||
int speedVal = (((msg[INDEX_MESSAGE_SPEED] & 0xFF) << 8) |
|
||||
(msg[INDEX_MESSAGE_SPEED+1] & 0xFF));
|
||||
int cadenceVal = (msg[INDEX_MESSAGE_CADENCE] & 0xFF);
|
||||
int bpmVal = (msg[INDEX_MESSAGE_BPM] & 0xFF);
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Sensor.SensorData.Builder power =
|
||||
Sensor.SensorData.newBuilder()
|
||||
.setValue(powerVal)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
|
||||
/*
|
||||
* Although speed is available from the SRM Bridge, MyTracks doesn't use the value, and
|
||||
* computes speed from the GPS location data.
|
||||
*/
|
||||
// Sensor.SensorData.Builder speed = Sensor.SensorData.newBuilder().setValue(speedVal).setState(
|
||||
// Sensor.SensorState.SENDING);
|
||||
|
||||
Sensor.SensorData.Builder cadence =
|
||||
Sensor.SensorData.newBuilder()
|
||||
.setValue(cadenceVal)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
|
||||
Sensor.SensorData.Builder bpm =
|
||||
Sensor.SensorData.newBuilder()
|
||||
.setValue(bpmVal)
|
||||
.setState(Sensor.SensorState.SENDING);
|
||||
|
||||
sensorData =
|
||||
Sensor.SensorDataSet.newBuilder()
|
||||
.setCreationTime(time)
|
||||
.setPower(power)
|
||||
.setCadence(cadence)
|
||||
.setHeartRate(bpm)
|
||||
.build();
|
||||
}
|
||||
|
||||
void handleChannelId(byte[] rawMessage) {
|
||||
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
|
||||
deviceNumber = message.getDeviceNumber();
|
||||
Log.d(TAG, "Found device id: " + deviceNumber);
|
||||
PreferencesUtils.setInt(context, R.string.ant_srm_bridge_sensor_id_key, deviceNumber);
|
||||
}
|
||||
|
||||
private void handleMessageResponse(byte[] rawMessage) {
|
||||
AntChannelResponseMessage message =
|
||||
new AntChannelResponseMessage(rawMessage);
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Received ANT Response: " + AntUtils.antMessageToString(message.getMessageId()) +
|
||||
"(" + message.getMessageId() + ")" +
|
||||
", Code: " + AntUtils.antEventToStr(message.getMessageCode()) +
|
||||
"(" + message.getMessageCode() + ")");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AntMesg.MESG_UNASSIGN_CHANNEL_ID:
|
||||
setSensorState(Sensor.SensorState.DISCONNECTED);
|
||||
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void setupAntSensorChannels() {
|
||||
Log.i(TAG, "Setting up ANT sensor channels");
|
||||
setupAntSensorChannel(
|
||||
NETWORK_NUMBER,
|
||||
CHANNEL_NUMBER,
|
||||
deviceNumber,
|
||||
DEVICE_TYPE,
|
||||
(byte) 0x01,
|
||||
CHANNEL_PERIOD,
|
||||
RF_FREQUENCY,
|
||||
(byte) 0);
|
||||
}
|
||||
|
||||
public short getDeviceNumber() {
|
||||
return deviceNumber;
|
||||
}
|
||||
|
||||
void setDeviceNumber(short deviceNumber) {
|
||||
this.deviceNumber = deviceNumber;
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.dsi.ant.AntDefine;
|
||||
import com.dsi.ant.AntInterface;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility methods for ANT functionality.
|
||||
*
|
||||
* Prefer use of this class to importing DSI ANT classes into code outside of
|
||||
* the sensors package.
|
||||
*/
|
||||
public class AntUtils {
|
||||
private AntUtils() {}
|
||||
|
||||
/** Returns true if this device supports ANT sensors. */
|
||||
public static boolean hasAntSupport(Context context) {
|
||||
return AntInterface.hasAntSupport(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the names of in the messages with the given value
|
||||
*/
|
||||
public static String antMessageToString(byte msg) {
|
||||
return findConstByteInClass(AntDefine.class, msg, "MESG_.*_ID");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the names of in the events with the given value
|
||||
*/
|
||||
public static String antEventToStr(byte event) {
|
||||
return findConstByteInClass(AntDefine.class, event, ".*EVENT.*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of constant static byte field declarations in the class that have the given value
|
||||
* and whose name match the given pattern
|
||||
* @param cl class to search in
|
||||
* @param value value of constant static byte field declarations to match
|
||||
* @param regexPattern pattern to match against the name of the field
|
||||
* @return a set of the names of fields, expressed as a string
|
||||
*/
|
||||
private static String findConstByteInClass(Class<?> cl, byte value, String regexPattern)
|
||||
{
|
||||
Field[] fields = cl.getDeclaredFields();
|
||||
Set<String> fieldSet = new HashSet<String>();
|
||||
for (Field f : fields) {
|
||||
try {
|
||||
if (f.getType() == Byte.TYPE &&
|
||||
(f.getModifiers() & Modifier.STATIC) != 0 &&
|
||||
f.getName().matches(regexPattern) &&
|
||||
f.getByte(null) == value) {
|
||||
fieldSet.add(f.getName());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
} catch (IllegalAccessException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return fieldSet.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
|
||||
/**
|
||||
* Ant+ cadence sensor.
|
||||
*
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
|
||||
public class CadenceSensor extends AntSensorBase {
|
||||
/*
|
||||
* These constants are defined by the ANT+ bike speed and cadence sensor spec.
|
||||
*/
|
||||
public static final byte CADENCE_DEVICE_TYPE = 122;
|
||||
public static final short CADENCE_CHANNEL_PERIOD = 8102;
|
||||
|
||||
SensorEventCounter dataProcessor = new SensorEventCounter();
|
||||
|
||||
CadenceSensor(short devNum) {
|
||||
super(devNum, CADENCE_DEVICE_TYPE, "cadence sensor", CADENCE_CHANNEL_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an ANT+ cadence sensor message.
|
||||
* @param antMessage The byte array received from the cadence sensor.
|
||||
*/
|
||||
@Override
|
||||
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
|
||||
int sensorTime = ((int) antMessage[5] & 0xFF) + ((int) antMessage[6] & 0xFF) * 256;
|
||||
int crankRevs = ((int) antMessage[7] & 0xFF) + ((int) antMessage[8] & 0xFF) * 256;
|
||||
c.setCadence(dataProcessor.getEventsPerMinute(crankRevs, sensorTime));
|
||||
}
|
||||
};
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
|
||||
/**
|
||||
* Ant+ combined cadence and speed sensor.
|
||||
*
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
|
||||
public class CadenceSpeedSensor extends AntSensorBase {
|
||||
/*
|
||||
* These constants are defined by the ANT+ bike speed and cadence sensor spec.
|
||||
*/
|
||||
public static final byte CADENCE_SPEED_DEVICE_TYPE = 121;
|
||||
public static final short CADENCE_SPEED_CHANNEL_PERIOD = 8086;
|
||||
|
||||
SensorEventCounter dataProcessor = new SensorEventCounter();
|
||||
|
||||
CadenceSpeedSensor(short devNum) {
|
||||
super(devNum, CADENCE_SPEED_DEVICE_TYPE, "speed&cadence sensor", CADENCE_SPEED_CHANNEL_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an ANT+ cadence&speed sensor message.
|
||||
* @param antMessage The byte array received from the sensor.
|
||||
*/
|
||||
@Override
|
||||
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
|
||||
int sensorTime = ((int) antMessage[1] & 0xFF) + ((int) antMessage[2] & 0xFF) * 256;
|
||||
int crankRevs = ((int) antMessage[3] & 0xFF) + ((int) antMessage[4] & 0xFF) * 256;
|
||||
c.setCadence(dataProcessor.getEventsPerMinute(crankRevs, sensorTime));
|
||||
}
|
||||
};
|
||||
+13
-13
@@ -17,19 +17,19 @@
|
||||
package com.google.android.apps.mytracks.services.sensors.ant;
|
||||
|
||||
/**
|
||||
* Interface for collecting data from the sensors.
|
||||
* @author Laszlo Molnar
|
||||
* Ant channel configuration.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ChannelConfiguration {
|
||||
|
||||
public interface AntSensorDataCollector {
|
||||
|
||||
/**
|
||||
* Sets the current cadence to the value specified.
|
||||
*/
|
||||
void setCadence(int value);
|
||||
|
||||
/**
|
||||
* Sets the current heart rate to the value specified.
|
||||
*/
|
||||
void setHeartRate(int value);
|
||||
public short deviceNumber;
|
||||
public byte deviceType;
|
||||
public byte TransmissionType;
|
||||
public short period;
|
||||
public byte freq;
|
||||
public byte proxSearch;
|
||||
public boolean isInitializing = false;
|
||||
public boolean isDeinitializing = false;
|
||||
}
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Ant+ heart reate monitor sensor.
|
||||
*
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
|
||||
public class HeartRateSensor extends AntSensorBase {
|
||||
/*
|
||||
* These constants are defined by the ANT+ heart rate monitor spec.
|
||||
*/
|
||||
public static final byte HEART_RATE_DEVICE_TYPE = 120;
|
||||
public static final short HEART_RATE_CHANNEL_PERIOD = 8070;
|
||||
|
||||
HeartRateSensor(short devNum) {
|
||||
super(devNum, HEART_RATE_DEVICE_TYPE, "heart rate monitor", HEART_RATE_CHANNEL_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an ANT+ heart rate monitor message.
|
||||
* @param antMessage The byte array received from the heart rate monitor.
|
||||
*/
|
||||
@Override
|
||||
public void handleBroadcastData(byte[] antMessage, AntSensorDataCollector c) {
|
||||
int bpm = (int) antMessage[8] & 0xFF;
|
||||
Log.d(TAG, "now:" + System.currentTimeMillis() + " heart rate=" + bpm);
|
||||
c.setHeartRate(bpm);
|
||||
}
|
||||
};
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Processes an ANT+ sensor data (counter) + timestamp pair,
|
||||
* and returns the instantaneous value of the sensor
|
||||
*
|
||||
* @author Laszlo Molnar
|
||||
*/
|
||||
public class SensorEventCounter {
|
||||
|
||||
/**
|
||||
* HistoryElement stores a time stamped sensor counter
|
||||
*/
|
||||
private static class HistoryElement {
|
||||
final long systemTime;
|
||||
final int counter;
|
||||
final int sensorTime;
|
||||
|
||||
HistoryElement(long systemTime, int counter, int sensorTime) {
|
||||
this.systemTime = systemTime;
|
||||
this.counter = counter;
|
||||
this.sensorTime = sensorTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The latest counter value reported by the sensor
|
||||
*/
|
||||
private int counter;
|
||||
|
||||
/**
|
||||
* The calculated instantaneous sensor value to be displayed
|
||||
*/
|
||||
private int eventsPerMinute;
|
||||
|
||||
private static final int ONE_SECOND_MILLIS = 1000;
|
||||
private static final int HISTORY_LENGTH_MILLIS = ONE_SECOND_MILLIS * 5;
|
||||
private static final int HISTORY_MAX_LENGTH = 100;
|
||||
private static final int ONE_MINUTE_MILLIS = ONE_SECOND_MILLIS * 60;
|
||||
private static final int SENSOR_TIME_RESOLUTION = 1024; // in a second
|
||||
private static final int SENSOR_TIME_ONE_MINUTE = SENSOR_TIME_RESOLUTION * 60;
|
||||
|
||||
/**
|
||||
* History of previous sensor data - oldest first
|
||||
* only the latest HISTORY_LENGTH_MILLIS milliseconds of data is stored
|
||||
*/
|
||||
private LinkedList<HistoryElement> history;
|
||||
|
||||
SensorEventCounter() {
|
||||
counter = -1;
|
||||
eventsPerMinute = 0;
|
||||
history = new LinkedList<HistoryElement>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the instantaneous sensor value to be displayed
|
||||
*
|
||||
* @param newCounter sensor reported counter value
|
||||
* @param sensorTime sensor reported timestamp
|
||||
* @return the calculated value
|
||||
*/
|
||||
public int getEventsPerMinute(int newCounter, int sensorTime) {
|
||||
return getEventsPerMinute(newCounter, sensorTime, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// VisibleForTesting
|
||||
protected int getEventsPerMinute(int newCounter, int sensorTime, long now) {
|
||||
int counterChange = (newCounter - counter) & 0xFFFF;
|
||||
|
||||
Log.d(TAG, "now=" + now + " counter=" + newCounter + " sensortime=" + sensorTime);
|
||||
|
||||
if (counter < 0) {
|
||||
// store the initial counter value reported by the sensor
|
||||
// the timestamp is probably out of date, so the history is not updated
|
||||
counter = newCounter;
|
||||
return eventsPerMinute = 0;
|
||||
}
|
||||
counter = newCounter;
|
||||
|
||||
if (counterChange != 0) {
|
||||
// if new data has arrived from the sensor ...
|
||||
if (removeOldHistory(now)) {
|
||||
// ... and the history is not empty, then use the latest entry
|
||||
HistoryElement h = history.getLast();
|
||||
int sensorTimeChange = (sensorTime - h.sensorTime) & 0xFFFF;
|
||||
counterChange = (counter - h.counter) & 0xFFFF;
|
||||
eventsPerMinute = counterChange * SENSOR_TIME_ONE_MINUTE / sensorTimeChange;
|
||||
}
|
||||
// the previous removeOldHistory() call makes the length of the history capped
|
||||
history.addLast(new HistoryElement(now, counter, sensorTime));
|
||||
} else if (!history.isEmpty()) {
|
||||
// the sensor has resent an old (counter,timestamp) pair,
|
||||
// but the history is not empty
|
||||
|
||||
HistoryElement h = history.getLast();
|
||||
if (ONE_MINUTE_MILLIS < (now - h.systemTime) * eventsPerMinute) {
|
||||
// Too much time has passed since the last counter change.
|
||||
// This means that a smaller value than eventsPerMinute must be
|
||||
// returned. This value is extrapolated from the history of
|
||||
// HISTORY_LENGTH_MILLIS data.
|
||||
// Note, that eventsPerMinute is NOT updated unless the history
|
||||
// is empty or contains outdated entries. In that case it is zeroed.
|
||||
return getValueFromHistory(now);
|
||||
} // else the current eventsPerMinute is still valid, nothing to do here
|
||||
} else {
|
||||
// no new data from the sensor & the history is empty -> return 0
|
||||
eventsPerMinute = 0;
|
||||
}
|
||||
|
||||
Log.d(TAG, "getEventsPerMinute returns:" + eventsPerMinute);
|
||||
return eventsPerMinute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the instantaneous sensor value to be displayed
|
||||
* using the history when the sensor only resends the old data
|
||||
*/
|
||||
private int getValueFromHistory(long now) {
|
||||
if (!removeOldHistory(now)) {
|
||||
// there is nothing in the history, return 0
|
||||
return eventsPerMinute = 0;
|
||||
}
|
||||
HistoryElement f = history.getFirst();
|
||||
HistoryElement l = history.getLast();
|
||||
int sensorTimeChange = (l.sensorTime - f.sensorTime) & 0xFFFF;
|
||||
int counterChange = (counter - f.counter) & 0xFFFF;
|
||||
|
||||
// difference between now and systemTime of the oldest history entry
|
||||
// for better precision sensor timestamps are considered between
|
||||
// the first and the last history entry (could be overkill)
|
||||
int systemTimeChange = (int) (now - l.systemTime
|
||||
+ (sensorTimeChange * ONE_SECOND_MILLIS) / SENSOR_TIME_RESOLUTION);
|
||||
|
||||
// eventsPerMinute is not overwritten by this calculated value
|
||||
// because it is still needed when a new sensor event arrives
|
||||
int v = (counterChange * ONE_MINUTE_MILLIS) / systemTimeChange;
|
||||
Log.d(TAG, "getEventsPerMinute returns (2):" + v);
|
||||
|
||||
// do not return larger number than eventsPerMinute, because the reason
|
||||
// this function got called is that more time has passed after the last
|
||||
// sensor counter value change than the current eventsPerMinute
|
||||
// would be valid
|
||||
return v < eventsPerMinute ? v : eventsPerMinute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes old data from the history.
|
||||
*
|
||||
* @param now the current system time
|
||||
* @return true if the remaining history is not empty
|
||||
*/
|
||||
private boolean removeOldHistory(long now) {
|
||||
HistoryElement h;
|
||||
while ((h = history.peek()) != null) {
|
||||
// if the first element of the list is in our desired time range then return
|
||||
if (now - h.systemTime <= HISTORY_LENGTH_MILLIS && history.size() < HISTORY_MAX_LENGTH) {
|
||||
return true;
|
||||
}
|
||||
// otherwise remove the too old element, and look at the next (newer) one
|
||||
history.removeFirst();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+40
-15
@@ -16,8 +16,10 @@
|
||||
|
||||
package com.google.android.apps.mytracks.settings;
|
||||
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
|
||||
import com.dsi.ant.AntInterface;
|
||||
import com.google.android.apps.mytracks.services.sensors.ant.AntSensorManager;
|
||||
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
@@ -47,13 +49,14 @@ public class SensorSettingsActivity extends AbstractSettingsActivity {
|
||||
super.onCreate(bundle);
|
||||
addPreferencesFromResource(R.xml.sensor_settings);
|
||||
|
||||
boolean hasAntSupport = AntInterface.hasAntSupport(this);
|
||||
ListPreference sensorTypeListPreference = (ListPreference) findPreference(
|
||||
getString(R.string.sensor_type_key));
|
||||
List<String> sensorTypeEntries = Arrays.asList(getResources().getStringArray(
|
||||
AntUtils.hasAntSupport(this) ? R.array.sensor_type_all_options
|
||||
hasAntSupport ? R.array.sensor_type_all_options
|
||||
: R.array.sensor_type_bluetooth_options));
|
||||
List<String> sensorTypeEntryValues = Arrays.asList(getResources().getStringArray(
|
||||
AntUtils.hasAntSupport(this) ? R.array.sensor_type_all_values
|
||||
hasAntSupport ? R.array.sensor_type_all_values
|
||||
: R.array.sensor_type_bluetooth_values));
|
||||
sensorTypeListPreference.setEntries(sensorTypeEntries.toArray(
|
||||
new CharSequence[sensorTypeEntries.size()]));
|
||||
@@ -78,7 +81,7 @@ public class SensorSettingsActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
if (!AntUtils.hasAntSupport(this)) {
|
||||
if (!hasAntSupport) {
|
||||
PreferenceScreen rootPreferenceScreen = (PreferenceScreen) findPreference(
|
||||
getString(R.string.settings_sensor_root_key));
|
||||
rootPreferenceScreen.removePreference(
|
||||
@@ -97,17 +100,39 @@ public class SensorSettingsActivity extends AbstractSettingsActivity {
|
||||
|| getString(R.string.sensor_type_value_zephyr).equals(sensorType);
|
||||
findPreference(getString(R.string.settings_sensor_bluetooth_key)).setEnabled(isBluetooth);
|
||||
|
||||
Preference antHeartRateSensorId = findPreference(
|
||||
getString(R.string.ant_heart_rate_sensor_id_key));
|
||||
if (antHeartRateSensorId != null) {
|
||||
antHeartRateSensorId.setEnabled(getString(R.string.sensor_type_value_ant).equals(sensorType));
|
||||
}
|
||||
|
||||
Preference antSrmBridgetSensorId = findPreference(
|
||||
getString(R.string.ant_srm_bridge_sensor_id_key));
|
||||
if (antSrmBridgetSensorId != null) {
|
||||
antSrmBridgetSensorId.setEnabled(
|
||||
getString(R.string.sensor_type_value_srm_ant_bridge).equals(sensorType));
|
||||
boolean isAnt = getString(R.string.sensor_type_value_ant).equals(sensorType);
|
||||
updateAntSensor(R.string.settings_sensor_ant_reset_heart_rate_monitor_key,
|
||||
R.string.ant_heart_rate_monitor_id_key, isAnt);
|
||||
updateAntSensor(R.string.settings_sensor_ant_reset_speed_distance_monitor_key,
|
||||
R.string.ant_speed_distance_monitor_id_key, isAnt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an ant sensor.
|
||||
*
|
||||
* @param preferenceKey the preference key
|
||||
* @param valueKey the value key
|
||||
* @param enabled true if enabled
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updateAntSensor(int preferenceKey, final int valueKey, boolean enabled) {
|
||||
Preference preference = findPreference(getString(preferenceKey));
|
||||
if (preference != null) {
|
||||
preference.setEnabled(enabled);
|
||||
int deviceId = PreferencesUtils.getInt(this, valueKey, AntSensorManager.WILDCARD);
|
||||
if (deviceId == AntSensorManager.WILDCARD) {
|
||||
preference.setSummary(R.string.settings_sensor_not_connected);
|
||||
} else {
|
||||
preference.setSummary(getString(R.string.settings_sensor_ant_paired, deviceId));
|
||||
}
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
PreferencesUtils.setInt(SensorSettingsActivity.this, valueKey, AntSensorManager.WILDCARD);
|
||||
pref.setSummary(R.string.settings_sensor_not_connected);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user