forked from upstream-mirrors/OpenTracks
Merging batteryLevel branch.
This commit is contained in:
+90
-12
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
|
* Copyright 2011 Dominik Ršttsches <d.roettsches@gmail.com>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
@@ -15,11 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.services.sensors;
|
package com.google.android.apps.mytracks.services.sensors;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of a Sensor MessageParser for Zephyr.
|
* An implementation of a Sensor MessageParser for Zephyr.
|
||||||
*
|
*
|
||||||
@@ -31,6 +34,54 @@ public class ZephyrMessageParser implements MessageParser {
|
|||||||
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
|
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
|
||||||
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
|
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
|
||||||
|
|
||||||
|
private StrideReadings strideReadings;
|
||||||
|
|
||||||
|
public class StrideReadings {
|
||||||
|
class StrideReading {
|
||||||
|
public int timeMs;
|
||||||
|
public int numStrides;
|
||||||
|
|
||||||
|
StrideReading(int newTimeMs, int newNumStrides) {
|
||||||
|
timeMs = newTimeMs;
|
||||||
|
numStrides = newNumStrides;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedList<StrideReading> strideReadingsHistory;
|
||||||
|
private static final int NUM_READINGS_FOR_AVERAGE = 10;
|
||||||
|
private static final int MIN_READINGS_FOR_AVERAGE = 5;
|
||||||
|
public static final int CADENCE_NOT_AVAILABLE = -1;
|
||||||
|
|
||||||
|
public StrideReadings() {
|
||||||
|
strideReadingsHistory = new LinkedList<StrideReading>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateStrideReading(int timeInMs, int numStrides) {
|
||||||
|
// HRM/HxM docs say, transmission frequency is 1 Hz,
|
||||||
|
// let's keep last NUM_READINGS_FOR_AVERAGE readings.
|
||||||
|
// TODO: Calibrate this using a reliable footpod / cadence sensor,
|
||||||
|
// otherwise perhaps use heartbeat timestamp for calculation.
|
||||||
|
strideReadingsHistory.addFirst(new StrideReading(timeInMs, numStrides));
|
||||||
|
while(strideReadingsHistory.size() > NUM_READINGS_FOR_AVERAGE) {
|
||||||
|
strideReadingsHistory.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCadence() {
|
||||||
|
if(strideReadingsHistory.size() < MIN_READINGS_FOR_AVERAGE) {
|
||||||
|
// Bail out if we cannot really get a meaningful average yet.
|
||||||
|
return CADENCE_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
// compute assuming 1 stride reading/second
|
||||||
|
int timeSinceOldestReadingSecs = strideReadingsHistory.size() - 1;
|
||||||
|
int stridesThen = strideReadingsHistory.getLast().numStrides;
|
||||||
|
int stridesNow = strideReadingsHistory.getFirst().numStrides;
|
||||||
|
// Contrary to documentation stride value seems to roll over every 127 strides.
|
||||||
|
return Math.round( (float)((stridesNow - stridesThen) % 127) /
|
||||||
|
timeSinceOldestReadingSecs * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
|
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -38,22 +89,49 @@ public class ZephyrMessageParser implements MessageParser {
|
|||||||
sb.append(String.format("%02X", buffer[i]));
|
sb.append(String.format("%02X", buffer[i]));
|
||||||
}
|
}
|
||||||
Log.w(Constants.TAG, "Got zephyr data: " + sb);
|
Log.w(Constants.TAG, "Got zephyr data: " + sb);
|
||||||
// Heart Rate
|
|
||||||
|
// Device Firmware ID, Firmware Version, Hardware ID, Hardware Version
|
||||||
|
// 0x1A00316550003162 produces erroneous values for Cadence and needs
|
||||||
|
// a workaround based on the stride counter.
|
||||||
|
String hardwareFirmwareId = sb.substring(6, 22);
|
||||||
|
boolean needsWorkaround = hardwareFirmwareId.equals("1A00316550003162");
|
||||||
|
Log.w(Constants.TAG, "FW & HW Ids & Version " + hardwareFirmwareId + " needs workaround: " + needsWorkaround);
|
||||||
|
|
||||||
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
|
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
|
||||||
.setValue(buffer[12] & 0xFF)
|
.setValue(buffer[12] & 0xFF)
|
||||||
|
.setState(Sensor.SensorState.SENDING);
|
||||||
|
|
||||||
|
Sensor.SensorData.Builder batteryLevel = Sensor.SensorData.newBuilder()
|
||||||
|
.setValue(buffer[11])
|
||||||
|
.setState(Sensor.SensorState.SENDING);
|
||||||
|
|
||||||
|
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
|
||||||
|
|
||||||
|
if(!needsWorkaround) {
|
||||||
|
cadence = cadence
|
||||||
|
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
|
||||||
.setState(Sensor.SensorState.SENDING);
|
.setState(Sensor.SensorState.SENDING);
|
||||||
// Changes Nico Laum (Power and Cadence)
|
} else {
|
||||||
Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder()
|
if(strideReadings == null) {
|
||||||
.setValue(buffer[11])
|
strideReadings = new StrideReadings();
|
||||||
.setState(Sensor.SensorState.SENDING);
|
}
|
||||||
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder()
|
|
||||||
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
|
strideReadings.updateStrideReading(
|
||||||
.setState(Sensor.SensorState.SENDING);
|
SensorUtils.unsignedShortToIntLittleEndian(buffer, 14),
|
||||||
|
buffer[54] & 0xFF);
|
||||||
|
|
||||||
|
if(strideReadings.getCadence() != StrideReadings.CADENCE_NOT_AVAILABLE) {
|
||||||
|
cadence = cadence.setValue(strideReadings.getCadence())
|
||||||
|
.setState(Sensor.SensorState.SENDING);
|
||||||
|
} else {
|
||||||
|
cadence = cadence.setValue(0).setState(Sensor.SensorState.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Sensor.SensorDataSet sds =
|
Sensor.SensorDataSet sds =
|
||||||
Sensor.SensorDataSet.newBuilder()
|
Sensor.SensorDataSet.newBuilder()
|
||||||
.setCreationTime(System.currentTimeMillis())
|
.setCreationTime(System.currentTimeMillis())
|
||||||
.setPower(power)
|
.setBatteryLevel(batteryLevel)
|
||||||
.setHeartRate(heartrate)
|
.setHeartRate(heartrate)
|
||||||
.setCadence(cadence)
|
.setCadence(cadence)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ message SensorDataSet {
|
|||||||
optional SensorData heart_rate = 2;
|
optional SensorData heart_rate = 2;
|
||||||
optional SensorData cadence = 3;
|
optional SensorData cadence = 3;
|
||||||
optional SensorData power = 4;
|
optional SensorData power = 4;
|
||||||
|
optional SensorData battery_level = 5;
|
||||||
}
|
}
|
||||||
@@ -398,10 +398,18 @@ public final class Sensor {
|
|||||||
public boolean hasPower() { return hasPower; }
|
public boolean hasPower() { return hasPower; }
|
||||||
public com.google.android.apps.mytracks.content.Sensor.SensorData getPower() { return power_; }
|
public com.google.android.apps.mytracks.content.Sensor.SensorData getPower() { return power_; }
|
||||||
|
|
||||||
|
// optional .com.google.android.apps.mytracks.content.SensorData battery_level = 5;
|
||||||
|
public static final int BATTERY_LEVEL_FIELD_NUMBER = 5;
|
||||||
|
private boolean hasBatteryLevel;
|
||||||
|
private com.google.android.apps.mytracks.content.Sensor.SensorData batteryLevel_;
|
||||||
|
public boolean hasBatteryLevel() { return hasBatteryLevel; }
|
||||||
|
public com.google.android.apps.mytracks.content.Sensor.SensorData getBatteryLevel() { return batteryLevel_; }
|
||||||
|
|
||||||
private void initFields() {
|
private void initFields() {
|
||||||
heartRate_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
heartRate_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
||||||
cadence_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
cadence_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
||||||
power_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
power_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
||||||
|
batteryLevel_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
||||||
}
|
}
|
||||||
public final boolean isInitialized() {
|
public final boolean isInitialized() {
|
||||||
if (hasHeartRate()) {
|
if (hasHeartRate()) {
|
||||||
@@ -413,6 +421,9 @@ public final class Sensor {
|
|||||||
if (hasPower()) {
|
if (hasPower()) {
|
||||||
if (!getPower().isInitialized()) return false;
|
if (!getPower().isInitialized()) return false;
|
||||||
}
|
}
|
||||||
|
if (hasBatteryLevel()) {
|
||||||
|
if (!getBatteryLevel().isInitialized()) return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +442,9 @@ public final class Sensor {
|
|||||||
if (hasPower()) {
|
if (hasPower()) {
|
||||||
output.writeMessage(4, getPower());
|
output.writeMessage(4, getPower());
|
||||||
}
|
}
|
||||||
|
if (hasBatteryLevel()) {
|
||||||
|
output.writeMessage(5, getBatteryLevel());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int memoizedSerializedSize = -1;
|
private int memoizedSerializedSize = -1;
|
||||||
@@ -455,6 +469,10 @@ public final class Sensor {
|
|||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeMessageSize(4, getPower());
|
.computeMessageSize(4, getPower());
|
||||||
}
|
}
|
||||||
|
if (hasBatteryLevel()) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeMessageSize(5, getBatteryLevel());
|
||||||
|
}
|
||||||
memoizedSerializedSize = size;
|
memoizedSerializedSize = size;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
@@ -611,6 +629,9 @@ public final class Sensor {
|
|||||||
if (other.hasPower()) {
|
if (other.hasPower()) {
|
||||||
mergePower(other.getPower());
|
mergePower(other.getPower());
|
||||||
}
|
}
|
||||||
|
if (other.hasBatteryLevel()) {
|
||||||
|
mergeBatteryLevel(other.getBatteryLevel());
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +681,15 @@ public final class Sensor {
|
|||||||
setPower(subBuilder.buildPartial());
|
setPower(subBuilder.buildPartial());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 42: {
|
||||||
|
com.google.android.apps.mytracks.content.Sensor.SensorData.Builder subBuilder = com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder();
|
||||||
|
if (hasBatteryLevel()) {
|
||||||
|
subBuilder.mergeFrom(getBatteryLevel());
|
||||||
|
}
|
||||||
|
input.readMessage(subBuilder, extensionRegistry);
|
||||||
|
setBatteryLevel(subBuilder.buildPartial());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -794,6 +824,43 @@ public final class Sensor {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optional .com.google.android.apps.mytracks.content.SensorData battery_level = 5;
|
||||||
|
public boolean hasBatteryLevel() {
|
||||||
|
return result.hasBatteryLevel();
|
||||||
|
}
|
||||||
|
public com.google.android.apps.mytracks.content.Sensor.SensorData getBatteryLevel() {
|
||||||
|
return result.getBatteryLevel();
|
||||||
|
}
|
||||||
|
public Builder setBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
result.hasBatteryLevel = true;
|
||||||
|
result.batteryLevel_ = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Builder setBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData.Builder builderForValue) {
|
||||||
|
result.hasBatteryLevel = true;
|
||||||
|
result.batteryLevel_ = builderForValue.build();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Builder mergeBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
|
||||||
|
if (result.hasBatteryLevel() &&
|
||||||
|
result.batteryLevel_ != com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) {
|
||||||
|
result.batteryLevel_ =
|
||||||
|
com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder(result.batteryLevel_).mergeFrom(value).buildPartial();
|
||||||
|
} else {
|
||||||
|
result.batteryLevel_ = value;
|
||||||
|
}
|
||||||
|
result.hasBatteryLevel = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Builder clearBatteryLevel() {
|
||||||
|
result.hasBatteryLevel = false;
|
||||||
|
result.batteryLevel_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
// @@protoc_insertion_point(builder_scope:com.google.android.apps.mytracks.content.SensorDataSet)
|
// @@protoc_insertion_point(builder_scope:com.google.android.apps.mytracks.content.SensorDataSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user