mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-02 03:16:07 +02:00
275 lines
10 KiB
Java
275 lines
10 KiB
Java
package nodomain.freeyourgadget.gadgetbridge.service.devices.cycling_sensor.support;
|
|
|
|
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityKind.TYPE_CYCLING;
|
|
|
|
import android.bluetooth.BluetoothGatt;
|
|
import android.bluetooth.BluetoothGattCharacteristic;
|
|
import android.content.Intent;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.UUID;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.cycling_sensor.db.CyclingSampleProvider;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.CyclingSample;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.NotifyAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
|
|
|
public class CyclingSensorSupport extends CyclingSensorBaseSupport {
|
|
static class CyclingSpeedCadenceMeasurement {
|
|
private static final int FLAG_REVOLUTION_DATA_PRESENT = 1 << 0;
|
|
private static final int FLAG_CADENCE_DATA_PRESENT = 1 << 1;
|
|
public boolean revolutionDataPresent = false;
|
|
public boolean cadenceDataPresent = false;
|
|
public int revolutionCount;
|
|
public int lastRevolutionTimeTicks;
|
|
private int crankRevolutionCount;
|
|
private int lastCrankRevolutionTimeTicks;
|
|
|
|
public static CyclingSpeedCadenceMeasurement fromPayload(byte[] payload) throws RuntimeException {
|
|
if(payload.length < 7){
|
|
throw new RuntimeException("wrong payload length");
|
|
}
|
|
|
|
ByteBuffer buffer = ByteBuffer
|
|
.wrap(payload)
|
|
.order(ByteOrder.LITTLE_ENDIAN);
|
|
|
|
byte flags = buffer.get();
|
|
|
|
boolean revolutionDataPresent = (flags | FLAG_REVOLUTION_DATA_PRESENT) == FLAG_REVOLUTION_DATA_PRESENT;
|
|
boolean cadenceDataPresent = (flags | FLAG_CADENCE_DATA_PRESENT) == FLAG_CADENCE_DATA_PRESENT;
|
|
CyclingSpeedCadenceMeasurement result = new CyclingSpeedCadenceMeasurement();
|
|
|
|
if(revolutionDataPresent){
|
|
result.revolutionDataPresent = true;
|
|
result.revolutionCount = buffer.getInt() & 0xFFFFFFFF; // remove sign
|
|
result.lastRevolutionTimeTicks = buffer.getShort() & 0xFFFF;
|
|
}
|
|
|
|
if(cadenceDataPresent){
|
|
result.cadenceDataPresent = true;
|
|
result.crankRevolutionCount = buffer.getInt();
|
|
result.lastCrankRevolutionTimeTicks = buffer.getShort();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
public String toString() {
|
|
return String.format("Measurement revolutions: %d, time ticks %d", revolutionCount, lastRevolutionTimeTicks);
|
|
}
|
|
}
|
|
|
|
public final static UUID UUID_CYCLING_SENSOR_SERVICE =
|
|
UUID.fromString("00001816-0000-1000-8000-00805f9b34fb");
|
|
public final static UUID UUID_CYCLING_SENSOR_CSC_MEASUREMENT =
|
|
UUID.fromString("00002a5b-0000-1000-8000-00805f9b34fb");
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(CyclingSensorSupport.class);
|
|
|
|
private long persistenceInterval;
|
|
private long nextPersistenceTimestamp = 0;
|
|
|
|
private float wheelCircumference;
|
|
|
|
private Device databaseDevice;
|
|
private User databaseUser;
|
|
|
|
private CyclingSpeedCadenceMeasurement lastReportedMeasurement = null;
|
|
private long lastMeasurementTime = 0;
|
|
|
|
private BluetoothGattCharacteristic batteryCharacteristic = null;
|
|
|
|
public CyclingSensorSupport() {
|
|
super(logger);
|
|
|
|
addSupportedService(UUID_CYCLING_SENSOR_SERVICE);
|
|
addSupportedService(BatteryInfoProfile.SERVICE_UUID);
|
|
}
|
|
|
|
@Override
|
|
public void onSendConfiguration(String config) {
|
|
switch (config){
|
|
case DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL:
|
|
loadConfiguration();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void loadConfiguration(){
|
|
Prefs deviceSpecificPrefs = new Prefs(
|
|
GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress())
|
|
);
|
|
persistenceInterval = deviceSpecificPrefs.getInt(DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL, 60) * 1000;
|
|
nextPersistenceTimestamp = 0;
|
|
|
|
float wheelDiameter = deviceSpecificPrefs.getFloat(DeviceSettingsPreferenceConst.PREF_CYCLING_SENSOR_WHEEL_DIAMETER, 29);
|
|
wheelCircumference = (float)(wheelDiameter * 2.54 * Math.PI) / 100;
|
|
}
|
|
|
|
@Override
|
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
|
|
|
BluetoothGattCharacteristic measurementCharacteristic =
|
|
getCharacteristic(UUID_CYCLING_SENSOR_CSC_MEASUREMENT);
|
|
|
|
builder.add(new NotifyAction(measurementCharacteristic, true));
|
|
|
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
|
batteryCharacteristic = getCharacteristic(BatteryInfoProfile.UUID_CHARACTERISTIC_BATTERY_LEVEL);
|
|
|
|
if(batteryCharacteristic != null){
|
|
builder.add(new ReadAction(batteryCharacteristic));
|
|
}
|
|
|
|
loadConfiguration();
|
|
|
|
gbDevice.setFirmwareVersion("1.0.0");
|
|
|
|
try(DBHandler handler = GBApplication.acquireDB()){
|
|
DaoSession session = handler.getDaoSession();
|
|
|
|
databaseDevice = DBHelper.getDevice(getDevice(), session);
|
|
databaseUser = DBHelper.getUser(session);
|
|
}catch (Exception ex){
|
|
ex.printStackTrace();
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
private void handleMeasurementCharacteristic(BluetoothGattCharacteristic characteristic){
|
|
byte[] value = characteristic.getValue();
|
|
if(value == null || value.length < 7){
|
|
logger.error("Measurement characteristic value length smaller than 7");
|
|
return;
|
|
}
|
|
|
|
CyclingSpeedCadenceMeasurement measurement = null;
|
|
|
|
try {
|
|
measurement = CyclingSpeedCadenceMeasurement.fromPayload(value);
|
|
}catch (RuntimeException e){
|
|
// do nothing, measurement stays null
|
|
}
|
|
|
|
if(measurement == null){
|
|
return;
|
|
}
|
|
|
|
if(!measurement.revolutionDataPresent){
|
|
return;
|
|
}
|
|
|
|
handleCyclingSpeedMeasurement(measurement);
|
|
}
|
|
|
|
private void handleCyclingSpeedMeasurement(CyclingSpeedCadenceMeasurement currentMeasurement) {
|
|
logger.debug("Measurement " + currentMeasurement);
|
|
|
|
long now = System.currentTimeMillis();
|
|
|
|
Float speed = null;
|
|
|
|
long lastMeasurementDelta = (now - lastMeasurementTime);
|
|
|
|
if(lastMeasurementDelta <= 30_000){
|
|
int ticksPassed = currentMeasurement.lastRevolutionTimeTicks - lastReportedMeasurement.lastRevolutionTimeTicks;
|
|
// every second is subdivided in 1024 ticks
|
|
int millisDelta = (int)(ticksPassed * (1000f / 1024f));
|
|
|
|
if(millisDelta > 0) {
|
|
int revolutionsDelta = currentMeasurement.revolutionCount - lastReportedMeasurement.revolutionCount;
|
|
|
|
float revolutionsPerSecond = revolutionsDelta * (1000f / millisDelta);
|
|
|
|
speed = revolutionsPerSecond * wheelCircumference;
|
|
}
|
|
}
|
|
|
|
lastReportedMeasurement = currentMeasurement;
|
|
lastMeasurementTime = now;
|
|
|
|
if(now < nextPersistenceTimestamp){
|
|
// too early
|
|
return;
|
|
}
|
|
|
|
nextPersistenceTimestamp = now + persistenceInterval;
|
|
|
|
CyclingSample sample = new CyclingSample();
|
|
|
|
if (currentMeasurement.revolutionDataPresent) {
|
|
sample.setRevolutionCount(currentMeasurement.revolutionCount);
|
|
sample.setSpeed(speed);
|
|
sample.setDistance(currentMeasurement.revolutionCount * wheelCircumference);
|
|
}
|
|
|
|
sample.setTimestamp(now);
|
|
sample.setDevice(databaseDevice);
|
|
sample.setUser(databaseUser);
|
|
|
|
Intent liveIntent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES);
|
|
liveIntent.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
|
|
LocalBroadcastManager.getInstance(getContext())
|
|
.sendBroadcast(liveIntent);
|
|
|
|
try(DBHandler handler = GBApplication.acquireDB()) {
|
|
DaoSession session = handler.getDaoSession();
|
|
|
|
CyclingSampleProvider sampleProvider =
|
|
new CyclingSampleProvider(getDevice(), session);
|
|
|
|
sampleProvider.addSample(sample);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
byte[] value = characteristic.getValue();
|
|
|
|
if(characteristic.equals(batteryCharacteristic) && value != null && value.length == 1){
|
|
GBDeviceEventBatteryInfo info = new GBDeviceEventBatteryInfo();
|
|
info.level = characteristic.getValue()[0];
|
|
handleGBDeviceEvent(info);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
|
if(characteristic.getUuid().equals(UUID_CYCLING_SENSOR_CSC_MEASUREMENT)){
|
|
handleMeasurementCharacteristic(characteristic);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|