mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-05 01:37:03 +01:00
Mi Band 8: Send gps to watch (wip)
This commit is contained in:
parent
0c27772bb5
commit
d3eb69fcf7
@ -333,8 +333,10 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
//
|
//
|
||||||
// Calendar
|
// Calendar
|
||||||
//
|
//
|
||||||
settings.add(R.xml.devicesettings_header_calendar);
|
if (supportsCalendarEvents()) {
|
||||||
settings.add(R.xml.devicesettings_sync_calendar);
|
settings.add(R.xml.devicesettings_header_calendar);
|
||||||
|
settings.add(R.xml.devicesettings_sync_calendar);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Other
|
// Other
|
||||||
|
@ -274,8 +274,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSetGpsLocation(final Location location) {
|
public void onSetGpsLocation(final Location location) {
|
||||||
// TODO onSetGpsLocation
|
healthService.onSetGpsLocation(location);
|
||||||
super.onSetGpsLocation(location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
@ -27,11 +30,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
@ -42,9 +43,10 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
@ -77,6 +79,9 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
private static final int CMD_CONFIG_STANDING_REMINDER_SET = 13;
|
private static final int CMD_CONFIG_STANDING_REMINDER_SET = 13;
|
||||||
private static final int CMD_CONFIG_STRESS_GET = 14;
|
private static final int CMD_CONFIG_STRESS_GET = 14;
|
||||||
private static final int CMD_CONFIG_STRESS_SET = 15;
|
private static final int CMD_CONFIG_STRESS_SET = 15;
|
||||||
|
private static final int CMD_WORKOUT_WATCH_STATUS = 26;
|
||||||
|
private static final int CMD_WORKOUT_WATCH_OPEN = 30;
|
||||||
|
private static final int CMD_WORKOUT_LOCATION = 48;
|
||||||
private static final int CMD_REALTIME_STATS_START = 45;
|
private static final int CMD_REALTIME_STATS_START = 45;
|
||||||
private static final int CMD_REALTIME_STATS_STOP = 46;
|
private static final int CMD_REALTIME_STATS_STOP = 46;
|
||||||
private static final int CMD_REALTIME_STATS_EVENT = 47;
|
private static final int CMD_REALTIME_STATS_EVENT = 47;
|
||||||
@ -84,10 +89,20 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
private static final int GENDER_MALE = 1;
|
private static final int GENDER_MALE = 1;
|
||||||
private static final int GENDER_FEMALE = 2;
|
private static final int GENDER_FEMALE = 2;
|
||||||
|
|
||||||
|
private static final int WORKOUT_STARTED = 0;
|
||||||
|
private static final int WORKOUT_RESUMED = 1;
|
||||||
|
private static final int WORKOUT_PAUSED = 2;
|
||||||
|
private static final int WORKOUT_FINISHED = 3;
|
||||||
|
|
||||||
private boolean realtimeStarted = false;
|
private boolean realtimeStarted = false;
|
||||||
private boolean realtimeOneShot = false;
|
private boolean realtimeOneShot = false;
|
||||||
private int previousSteps = -1;
|
private int previousSteps = -1;
|
||||||
|
|
||||||
|
private boolean gpsStarted = false;
|
||||||
|
private boolean gpsFixAcquired = false;
|
||||||
|
private boolean workoutStarted = false;
|
||||||
|
private final Handler gpsTimeoutHandler = new Handler();
|
||||||
|
|
||||||
private final XiaomiActivityFileFetcher activityFetcher = new XiaomiActivityFileFetcher(this);
|
private final XiaomiActivityFileFetcher activityFetcher = new XiaomiActivityFileFetcher(this);
|
||||||
|
|
||||||
public XiaomiHealthService(final XiaomiSupport support) {
|
public XiaomiHealthService(final XiaomiSupport support) {
|
||||||
@ -116,6 +131,12 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
case CMD_CONFIG_STRESS_GET:
|
case CMD_CONFIG_STRESS_GET:
|
||||||
handleStressConfig(cmd.getHealth().getStress());
|
handleStressConfig(cmd.getHealth().getStress());
|
||||||
return;
|
return;
|
||||||
|
case CMD_WORKOUT_WATCH_STATUS:
|
||||||
|
handleWorkoutStatus(cmd.getHealth().getWorkoutStatusWatch());
|
||||||
|
return;
|
||||||
|
case CMD_WORKOUT_WATCH_OPEN:
|
||||||
|
handleWorkoutOpen(cmd.getHealth().getWorkoutOpenWatch());
|
||||||
|
return;
|
||||||
case CMD_REALTIME_STATS_EVENT:
|
case CMD_REALTIME_STATS_EVENT:
|
||||||
handleRealtimeStats(cmd.getHealth().getRealTimeStats());
|
handleRealtimeStats(cmd.getHealth().getRealTimeStats());
|
||||||
return;
|
return;
|
||||||
@ -406,6 +427,128 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleWorkoutOpen(final XiaomiProto.WorkoutOpenWatch workoutOpenWatch) {
|
||||||
|
LOG.debug("Workout open on watch: {}", workoutOpenWatch.getSport());
|
||||||
|
|
||||||
|
workoutStarted = false;
|
||||||
|
|
||||||
|
final boolean sendGpsToBand = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
|
||||||
|
if (!sendGpsToBand) {
|
||||||
|
getSupport().sendCommand(
|
||||||
|
"send location disabled",
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_WORKOUT_WATCH_OPEN)
|
||||||
|
.setHealth(XiaomiProto.Health.newBuilder().setWorkoutOpenReply(
|
||||||
|
XiaomiProto.WorkoutOpenReply.newBuilder()
|
||||||
|
.setUnknown1(3)
|
||||||
|
.setUnknown2(2)
|
||||||
|
.setUnknown3(10)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gpsStarted) {
|
||||||
|
gpsStarted = true;
|
||||||
|
gpsFixAcquired = false;
|
||||||
|
GBLocationManager.start(getSupport().getContext(), getSupport());
|
||||||
|
}
|
||||||
|
|
||||||
|
gpsTimeoutHandler.removeCallbacksAndMessages(null);
|
||||||
|
// Timeout if the watch stops sending workout open
|
||||||
|
gpsTimeoutHandler.postDelayed(() -> GBLocationManager.stop(getSupport().getContext(), getSupport()), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWorkoutStatus(final XiaomiProto.WorkoutStatusWatch workoutStatus) {
|
||||||
|
LOG.debug("Got workout status: {}", workoutStatus.getStatus());
|
||||||
|
|
||||||
|
final boolean startOnPhone = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_START_ON_PHONE, false);
|
||||||
|
|
||||||
|
switch (workoutStatus.getStatus()) {
|
||||||
|
case WORKOUT_STARTED:
|
||||||
|
workoutStarted = true;
|
||||||
|
gpsTimeoutHandler.removeCallbacksAndMessages(null);
|
||||||
|
if (startOnPhone) {
|
||||||
|
OpenTracksController.startRecording(getSupport().getContext(), sportToActivityKind(workoutStatus.getSport()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WORKOUT_RESUMED:
|
||||||
|
case WORKOUT_PAUSED:
|
||||||
|
break;
|
||||||
|
case WORKOUT_FINISHED:
|
||||||
|
GBLocationManager.stop(getSupport().getContext(), getSupport());
|
||||||
|
if (startOnPhone) {
|
||||||
|
OpenTracksController.stopRecording(getSupport().getContext());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetGpsLocation(final Location location) {
|
||||||
|
if (!gpsFixAcquired) {
|
||||||
|
gpsFixAcquired = true;
|
||||||
|
getSupport().sendCommand(
|
||||||
|
"send gps fix",
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_WORKOUT_WATCH_OPEN)
|
||||||
|
.setHealth(XiaomiProto.Health.newBuilder().setWorkoutOpenReply(
|
||||||
|
XiaomiProto.WorkoutOpenReply.newBuilder()
|
||||||
|
.setUnknown1(0)
|
||||||
|
.setUnknown2(2)
|
||||||
|
.setUnknown3(10)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workoutStarted) {
|
||||||
|
final XiaomiProto.WorkoutLocation.Builder workoutLocation = XiaomiProto.WorkoutLocation.newBuilder()
|
||||||
|
.setNumSatellites(10)
|
||||||
|
.setTimestamp((int) (location.getTime() / 1000L))
|
||||||
|
.setLongitude(location.getLongitude())
|
||||||
|
.setLatitude(location.getLatitude())
|
||||||
|
.setAltitude(location.getAltitude())
|
||||||
|
.setSpeed(location.getSpeed())
|
||||||
|
.setBearing(location.getBearing())
|
||||||
|
.setHorizontalAccuracy(location.getAccuracy());
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
workoutLocation.setVerticalAccuracy(location.getVerticalAccuracyMeters());
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupport().sendCommand(
|
||||||
|
"send gps location",
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_WORKOUT_LOCATION)
|
||||||
|
.setHealth(XiaomiProto.Health.newBuilder().setWorkoutLocation(workoutLocation))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sportToActivityKind(final int sport) {
|
||||||
|
switch (sport) {
|
||||||
|
case 1: // outdoor run
|
||||||
|
case 5: // trail run
|
||||||
|
return ActivityKind.TYPE_RUNNING;
|
||||||
|
case 2:
|
||||||
|
return ActivityKind.TYPE_WALKING;
|
||||||
|
case 3: // hiking
|
||||||
|
case 4: // trekking
|
||||||
|
return ActivityKind.TYPE_HIKING;
|
||||||
|
case 6:
|
||||||
|
return ActivityKind.TYPE_CYCLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unknown sport {}", sport);
|
||||||
|
|
||||||
|
return ActivityKind.TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
public XiaomiActivityFileFetcher getActivityFetcher() {
|
public XiaomiActivityFileFetcher getActivityFetcher() {
|
||||||
return activityFetcher;
|
return activityFetcher;
|
||||||
}
|
}
|
||||||
|
@ -392,7 +392,11 @@ message VitalityScore {
|
|||||||
|
|
||||||
message WorkoutStatusWatch {
|
message WorkoutStatusWatch {
|
||||||
optional uint32 timestamp = 1; // seconds
|
optional uint32 timestamp = 1; // seconds
|
||||||
optional uint32 unknown2 = 2;
|
optional uint32 sport = 3;
|
||||||
|
optional uint32 status = 4; // 0 started, 1 resumed, 2 paused, 3 finished
|
||||||
|
optional bytes activityFileIds = 5;
|
||||||
|
optional uint32 unknown6 = 6; // 2
|
||||||
|
optional uint32 unknown10 = 10; // 0
|
||||||
}
|
}
|
||||||
|
|
||||||
message WorkoutOpenWatch {
|
message WorkoutOpenWatch {
|
||||||
@ -404,7 +408,7 @@ message WorkoutOpenWatch {
|
|||||||
|
|
||||||
message WorkoutOpenReply {
|
message WorkoutOpenReply {
|
||||||
// 3 2 10 when no gps permissions at all
|
// 3 2 10 when no gps permissions at all
|
||||||
// 5 2 10 when no background permissions?
|
// 5 2 10 when no all time gps permission
|
||||||
// ...
|
// ...
|
||||||
// 0 * * when phone gps is working fine
|
// 0 * * when phone gps is working fine
|
||||||
// 0 2 10
|
// 0 2 10
|
||||||
@ -415,14 +419,15 @@ message WorkoutOpenReply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message WorkoutLocation {
|
message WorkoutLocation {
|
||||||
optional uint32 unknown1 = 1; // 10, sometimes 2
|
optional uint32 numSatellites = 1; // 10, sometimes 2?
|
||||||
optional uint32 timestamp = 2; // seconds
|
optional uint32 timestamp = 2; // seconds
|
||||||
optional double longitude = 3;
|
optional double longitude = 3;
|
||||||
optional double latitude = 4;
|
optional double latitude = 4;
|
||||||
optional float unknown6 = 6; // ?
|
optional double altitude = 5;
|
||||||
optional float unknown7 = 7; // altitude?
|
optional float speed = 6;
|
||||||
optional float unknown8 = 8; // ?
|
optional float bearing = 7;
|
||||||
optional float unknown9 = 9; // ?
|
optional float horizontalAccuracy = 8;
|
||||||
|
optional float verticalAccuracy = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RealTimeStats {
|
message RealTimeStats {
|
||||||
|
Loading…
Reference in New Issue
Block a user