diff --git a/MyTracks/res/values-ca/arrays.xml b/MyTracks/res/values-ca/arrays.xml index 2bba332fa..693efc12b 100644 --- a/MyTracks/res/values-ca/arrays.xml +++ b/MyTracks/res/values-ca/arrays.xml @@ -284,11 +284,4 @@ limitations under the License. punt de referència - - Cap - ANT+ Sensors - SRM ANT+ Bridge - Zephyr Bluetooth - - diff --git a/MyTracks/res/values-ca/strings.xml b/MyTracks/res/values-ca/strings.xml index 6ac1e8525..5f7b54f2f 100644 --- a/MyTracks/res/values-ca/strings.xml +++ b/MyTracks/res/values-ca/strings.xml @@ -409,4 +409,6 @@ limitations under the License. S\'està cercant S\'està enviant + + Cap diff --git a/MyTracks/res/values-es-rES/arrays.xml b/MyTracks/res/values-es-rES/arrays.xml index 757121f6b..cb7c0b79a 100644 --- a/MyTracks/res/values-es-rES/arrays.xml +++ b/MyTracks/res/values-es-rES/arrays.xml @@ -143,11 +143,4 @@ limitations under the License. camino - - Ninguna - ANT+ Sensores - SRM ANT+ Puente - Zephyr Bluetooth - - diff --git a/MyTracks/res/values-es-rES/strings.xml b/MyTracks/res/values-es-rES/strings.xml index 2cb837ae1..169e3b78a 100644 --- a/MyTracks/res/values-es-rES/strings.xml +++ b/MyTracks/res/values-es-rES/strings.xml @@ -128,4 +128,8 @@ limitations under the License. $6 : Speed units (mph) : Pace units (per mile) --> + + Ninguna + ANT+ Sensores + SRM ANT+ Puente diff --git a/MyTracks/res/values-fr/arrays.xml b/MyTracks/res/values-fr/arrays.xml index 18740a364..ef0994644 100644 --- a/MyTracks/res/values-fr/arrays.xml +++ b/MyTracks/res/values-fr/arrays.xml @@ -291,11 +291,4 @@ limitations under the License. point de passage - - Aucun - Capteur ANT+ - Pont SRM ANT+ - Bluetooth Zephyr - - diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml index a1c00f1fc..49cc6516d 100644 --- a/MyTracks/res/values-fr/strings.xml +++ b/MyTracks/res/values-fr/strings.xml @@ -397,6 +397,13 @@ limitations under the License. Recherche Envoi + + Aucun + Capteur ANT+ + Pont SRM ANT+ + Bluetooth Polar + Bluetooth Zephyr + notifications lors de l\'enregistrement de parcours lecture des parcours enregistrés diff --git a/MyTracks/res/values-ja/arrays.xml b/MyTracks/res/values-ja/arrays.xml index 31e45abee..aeffc5d84 100644 --- a/MyTracks/res/values-ja/arrays.xml +++ b/MyTracks/res/values-ja/arrays.xml @@ -272,11 +272,4 @@ limitations under the License. ウェイポイント - - なし - ANT+ センサー - SRM ANT+ ブリッジ - Zephyr Bluetooth - - diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml index 1a6a34106..a23fb0bbf 100644 --- a/MyTracks/res/values-ja/strings.xml +++ b/MyTracks/res/values-ja/strings.xml @@ -463,6 +463,11 @@ limitations under the License. 検索中 送信中 + + なし + ANT+ センサー + SRM ANT+ ブリッジ + トラック記録通知受信 トラック読み込み diff --git a/MyTracks/res/values-ru/arrays.xml b/MyTracks/res/values-ru/arrays.xml index feb32549f..e8cf89568 100755 --- a/MyTracks/res/values-ru/arrays.xml +++ b/MyTracks/res/values-ru/arrays.xml @@ -272,11 +272,4 @@ limitations under the License. путевая точка - - Нет - ANT+ Sensors - SRM ANT+ Bridge - Zephyr Bluetooth - - diff --git a/MyTracks/res/values-ru/strings.xml b/MyTracks/res/values-ru/strings.xml index 0598737ce..76361ce98 100644 --- a/MyTracks/res/values-ru/strings.xml +++ b/MyTracks/res/values-ru/strings.xml @@ -490,6 +490,11 @@ limitations under the License. Поиск Отправка + + Нет + ANT+ Датчик + SRM ANT+ Bridge + получать уведомления о записи трека чтение записанных данных трека diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml index dd6c37220..f85c10136 100644 --- a/MyTracks/res/values/arrays.xml +++ b/MyTracks/res/values/arrays.xml @@ -277,11 +277,4 @@ limitations under the License. way point - - None - ANT+ Sensors - SRM ANT+ Bridge - Zephyr Bluetooth - - diff --git a/MyTracks/res/values/arrays_do_not_translate.xml b/MyTracks/res/values/arrays_do_not_translate.xml index 780ee8b9e..25f173930 100644 --- a/MyTracks/res/values/arrays_do_not_translate.xml +++ b/MyTracks/res/values/arrays_do_not_translate.xml @@ -98,10 +98,19 @@ limitations under the License. TCX + + @string/no_sensor_option + @string/ant_sensor_option + @string/ant_bridge_sensor_option + @string/polar_bt_sensor_option + @string/zephyr_bt_sensor_option + + @string/none @string/ant_sensor_type @string/srm_ant_bridge_sensor_type + @string/polar_sensor_type @string/zephyr_sensor_type diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml index f6252d1db..6e25e4817 100644 --- a/MyTracks/res/values/do_not_translate.xml +++ b/MyTracks/res/values/do_not_translate.xml @@ -33,6 +33,7 @@ limitations under the License. NONE ANT SRM_ANT_BRIDGE + POLAR ZEPHYR diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index 5a5e33fba..019b7fe00 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -501,4 +501,12 @@ limitations under the License. Be notified when a track starts or stops being recorded. Be able to read track data, such as points and waypoints. Be able to write track data, such as starting recording or adding waypoints. + + + NONE + ANT+ Sensors + SRM ANT+ Bridge + Polar Bluetooth + Zephyr Bluetooth + diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java index e3b2084cd..45822ddb7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java @@ -232,7 +232,8 @@ public class SettingsActivity extends PreferenceActivity { private void updateSensorSettings(String sensorType) { boolean usesBluetooth = - getString(R.string.zephyr_sensor_type).equals(sensorType); + getString(R.string.zephyr_sensor_type).equals(sensorType) + || getString(R.string.polar_sensor_type).equals(sensorType); findPreference( getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth); findPreference( diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java index 3f95db95f..8fbbdc44b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java @@ -394,8 +394,9 @@ public class BluetoothConnectionManager { offset = 0; - // Send the obtained bytes to the UI Activity - handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) + // Send copy of the obtained bytes to the UI Activity. + // Avoids memory inconsistency issues. + handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()) .sendToTarget(); } catch (IOException e) { Log.e(Constants.TAG, "disconnected", e); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java new file mode 100644 index 000000000..d8876587d --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java @@ -0,0 +1,140 @@ +/* + * 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; + +import com.google.android.apps.mytracks.content.Sensor; + +/** + * An implementation of a Sensor MessageParser for Polar Wearlink Bluetooth HRM. + * + * Polar Bluetooth Wearlink packet example; + * Hdr Len Chk Seq Status HeartRate RRInterval_16-bits + * FE 08 F7 06 F1 48 03 64 + * where; + * Hdr always = 254 (0xFE), + * Chk = 255 - Len + * Seq range 0 to 15 + * Status = Upper nibble may be battery voltage + * bit 0 is Beat Detection flag. + * + * Additional packet examples; + * FE 08 F7 06 F1 48 03 64 + * FE 0A F5 06 F1 48 03 64 03 70 + * + * @author John R. Gerthoffer + */ +public class PolarMessageParser implements MessageParser { + + private int lastHeartRate = 0; + + /** + * Applies Polar packet validation rules to buffer. + * Polar packets are checked for following; + * offset 0 = header byte, 254 (0xFE). + * offset 1 = packet length byte, 8, 10, 12, 14. + * offset 2 = check byte, 255 - packet length. + * offset 3 = sequence byte, range from 0 to 15. + * + * @param an array of bytes to parse + * @param buffer offset to beginning of packet. + * @return whether buffer has a valid packet at offset i + */ + private boolean packetValid (byte[] buffer, int i) { + boolean headerValid = (buffer[i] & 0xFF) == 0xFE; + boolean checkbyteValid = (buffer[i + 2] & 0xFF) == (0xFF - (buffer[i + 1] & 0xFF)); + boolean sequenceValid = (buffer[i + 3] & 0xFF) < 16; + + return headerValid && checkbyteValid && sequenceValid; + } + + @Override + public Sensor.SensorDataSet parseBuffer(byte[] buffer) { + + int heartRate = 0; + boolean heartrateValid = false; + + // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends. + for (int i = 0; i < buffer.length - 8; i++) { + heartrateValid = packetValid(buffer,i); + if (heartrateValid) { + heartRate = buffer[i + 5] & 0xFF; + break; + } + } + + // If our buffer is corrupted, use decaying last good value. + if(!heartrateValid) { + heartRate = (int) (lastHeartRate * 0.8); + if(heartRate < 50) + heartRate = 0; + } + + lastHeartRate = heartRate; // Remember good value for next time. + + // Heart Rate + Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() + .setValue(heartRate) + .setState(Sensor.SensorState.SENDING); + + Sensor.SensorDataSet sds = Sensor.SensorDataSet.newBuilder() + .setCreationTime(System.currentTimeMillis()) + .setHeartRate(b) + .build(); + + return sds; + } + + /** + * Applies packet validation rules to buffer + * + * @param an array of bytes to parse + * @return whether buffer has a valid packet starting at index zero + */ + @Override + public boolean isValid(byte[] buffer) { + return packetValid(buffer,0); + } + + /** + * Polar uses variable packet sizes; 8, 10, 12, 14 and rarely 16. + * The most frequent are 8 and 10. + * We will wait for 16 bytes. + * This way, we are assured of getting one good one. + * + * @return the size of buffer needed to parse a good packet + */ + @Override + public int getFrameSize() { + return 16; + } + + /** + * Searches buffer for the beginning of a valid packet. + * + * @param an array of bytes to parse + * @return index to beginning of good packet, or -1 if none found. + */ + @Override + public int findNextAlignment(byte[] buffer) { + // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends. + for (int i = 0; i < buffer.length - 8; i++) { + if (packetValid(buffer,i)) { + return i; + } + } + return -1; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java new file mode 100644 index 000000000..d1d1f7441 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java @@ -0,0 +1,28 @@ +/* + * 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; + +import android.content.Context; + +/** + * PolarSensorManager - A sensor manager for Polar heart rate monitors. + */ + +public class PolarSensorManager extends BluetoothSensorManager { + public PolarSensorManager(Context context) { + super(context, new PolarMessageParser()); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java index 7c468be2c..a7836c3d0 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java @@ -59,6 +59,8 @@ public class SensorManagerFactory { return new AntSRMSensorManager(context); } else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) { return new ZephyrSensorManager(context); + } else if (sensor.equals(context.getString(R.string.polar_sensor_type))) { + return new PolarSensorManager(context); } else { Log.w(Constants.TAG, "Unable to find sensor type: " + sensor); return null; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java new file mode 100644 index 000000000..26ce13bef --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java @@ -0,0 +1,70 @@ +package com.google.android.apps.mytracks.services.sensors; + +import java.util.Arrays; + +import com.google.android.apps.mytracks.content.Sensor; + +import junit.framework.TestCase; + +public class PolarMessageParserTest extends TestCase { + + PolarMessageParser parser = new PolarMessageParser(); + // A complete and valid Polar HxM packet + // FE08F701D1001104FE08F702D1001104 + private final byte[] originalBuf = + {(byte) 0xFE, 0x08, (byte) 0xF7, 0x01, (byte) 0xD1, 0x00, 0x11, 0x04, (byte) 0xFE, 0x08, + (byte) 0xF7, 0x02, (byte) 0xD1, 0x00, 0x11, 0x04}; + private byte[] buf; + + public void setUp() { + buf = Arrays.copyOf(originalBuf, originalBuf.length); + } + + public void testIsValid() { + assertTrue(parser.isValid(buf)); + } + + public void testIsValid_invalidHeader() { + // Invalidate header. + buf[0] = 0x03; + assertFalse(parser.isValid(buf)); + } + + public void testIsValid_invalidCheckbyte() { + // Invalidate checkbyte. + buf[2] = 0x03; + assertFalse(parser.isValid(buf)); + } + + public void testIsValid_invalidSequence() { + // Invalidate sequence. + buf[3] = 0x11; + assertFalse(parser.isValid(buf)); + } + + public void testParseBuffer() { + buf[5] = 70; + Sensor.SensorDataSet sds = parser.parseBuffer(buf); + assertTrue(sds.hasHeartRate()); + assertTrue(sds.getHeartRate().getState() == Sensor.SensorState.SENDING); + assertEquals(70, sds.getHeartRate().getValue()); + } + + public void testFindNextAlignment_offset() { + // The first 4 bytes are garbage + buf = new byte[originalBuf.length + 4]; + buf[0] = 4; + buf[1] = 2; + buf[2] = 4; + buf[3] = 2; + + // Then the valid message. + System.arraycopy(originalBuf, 0, buf, 4, originalBuf.length); + assertEquals(4, parser.findNextAlignment(buf)); + } + + public void testFindNextAlignment_invalid() { + buf[0] = 0; + assertEquals(-1, parser.findNextAlignment(buf)); + } +}