mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-03 00:43:02 +01:00
Mi Band 8: Activity fetch base (wip)
This commit is contained in:
parent
6c710d594d
commit
ae0a7bb806
@ -413,8 +413,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchRecordedData(final int dataTypes) {
|
public void onFetchRecordedData(final int dataTypes) {
|
||||||
// TODO
|
healthService.onFetchRecordedData(dataTypes);
|
||||||
super.onFetchRecordedData(dataTypes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -514,7 +513,8 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
final byte[] commandBytes = command.toByteArray();
|
final byte[] commandBytes = command.toByteArray();
|
||||||
final byte[] encryptedCommandBytes = authService.encrypt(commandBytes, encryptedIndex);
|
final byte[] encryptedCommandBytes = authService.encrypt(commandBytes, encryptedIndex);
|
||||||
final int commandLength = 6 + encryptedCommandBytes.length;
|
final int commandLength = 6 + encryptedCommandBytes.length;
|
||||||
if (commandLength > getMTU()) {
|
if (getMTU() != 0 && commandLength > getMTU()) {
|
||||||
|
// TODO MTU is 0 sometimes?
|
||||||
LOG.warn("Command with {} bytes is too large for MTU of {}", commandLength, getMTU());
|
LOG.warn("Command with {} bytes is too large for MTU of {}", commandLength, getMTU());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,7 +524,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
buf.put((byte) 1); // 1 for encrypted
|
buf.put((byte) 1); // 1 for encrypted
|
||||||
buf.putShort(encryptedIndex++);
|
buf.putShort(encryptedIndex++);
|
||||||
buf.put(encryptedCommandBytes);
|
buf.put(encryptedCommandBytes);
|
||||||
LOG.debug("Sending command {} as {}", GB.hexdump(commandBytes), GB.hexdump(buf.array()));
|
LOG.debug("Sending command {}", GB.hexdump(commandBytes));
|
||||||
builder.write(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), buf.array());
|
builder.write(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), buf.array());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
/* Copyright (C) 2023 José Rebelo
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
|
||||||
|
|
||||||
|
public class XiaomiActivityParser {
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
@ -47,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
public class XiaomiHealthService extends AbstractXiaomiService {
|
public class XiaomiHealthService extends AbstractXiaomiService {
|
||||||
@ -55,6 +58,8 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
public static final int COMMAND_TYPE = 8;
|
public static final int COMMAND_TYPE = 8;
|
||||||
|
|
||||||
private static final int CMD_SET_USER_INFO = 0;
|
private static final int CMD_SET_USER_INFO = 0;
|
||||||
|
private static final int CMD_ACTIVITY_FETCH_1 = 1;
|
||||||
|
private static final int CMD_ACTIVITY_FETCH_2 = 2;
|
||||||
private static final int CMD_CONFIG_SPO2_GET = 8;
|
private static final int CMD_CONFIG_SPO2_GET = 8;
|
||||||
private static final int CMD_CONFIG_SPO2_SET = 9;
|
private static final int CMD_CONFIG_SPO2_SET = 9;
|
||||||
private static final int CMD_CONFIG_HEART_RATE_GET = 10;
|
private static final int CMD_CONFIG_HEART_RATE_GET = 10;
|
||||||
@ -84,6 +89,10 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
case CMD_SET_USER_INFO:
|
case CMD_SET_USER_INFO:
|
||||||
LOG.debug("Got user info set ack, status={}", cmd.getStatus());
|
LOG.debug("Got user info set ack, status={}", cmd.getStatus());
|
||||||
return;
|
return;
|
||||||
|
case CMD_ACTIVITY_FETCH_1:
|
||||||
|
case CMD_ACTIVITY_FETCH_2:
|
||||||
|
handleActivityFetchResponse(cmd.getHealth().getActivityRecordIds().toByteArray());
|
||||||
|
return;
|
||||||
case CMD_CONFIG_SPO2_GET:
|
case CMD_CONFIG_SPO2_GET:
|
||||||
handleSpo2Config(cmd.getHealth().getSpo2());
|
handleSpo2Config(cmd.getHealth().getSpo2());
|
||||||
return;
|
return;
|
||||||
@ -386,6 +395,65 @@ public class XiaomiHealthService extends AbstractXiaomiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onFetchRecordedData(final int dataTypes) {
|
||||||
|
LOG.debug("Fetch recorded data: {}", dataTypes);
|
||||||
|
final TransactionBuilder builder = getSupport().createTransactionBuilder("fetch recorded data step 1");
|
||||||
|
|
||||||
|
getSupport().sendCommand(
|
||||||
|
builder,
|
||||||
|
XiaomiProto.Command.newBuilder()
|
||||||
|
.setType(COMMAND_TYPE)
|
||||||
|
.setSubtype(CMD_ACTIVITY_FETCH_1)
|
||||||
|
.setHealth(XiaomiProto.Health.newBuilder().setActivitySyncRequest1(
|
||||||
|
// TODO official app sends 0, but doesn't work every time?
|
||||||
|
XiaomiProto.ActivitySyncRequest1.newBuilder().setUnknown1(1)
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO we need to wait for the reply from the previous before sending this one?
|
||||||
|
//getSupport().sendCommand(
|
||||||
|
// builder,
|
||||||
|
// XiaomiProto.Command.newBuilder()
|
||||||
|
// .setType(COMMAND_TYPE)
|
||||||
|
// .setSubtype(CMD_ACTIVITY_FETCH_2)
|
||||||
|
// .build()
|
||||||
|
//);
|
||||||
|
|
||||||
|
builder.queue(getSupport().getQueue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleActivityFetchResponse(final byte[] recordIds) {
|
||||||
|
if ((recordIds.length % 7) != 0) {
|
||||||
|
LOG.warn("recordIds {} length = {}, not a multiple of 7, can't parse", GB.hexdump(recordIds), recordIds.length);
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Got {} record IDs", recordIds.length / 7);
|
||||||
|
|
||||||
|
final ByteBuffer buf = ByteBuffer.wrap(recordIds).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
while (buf.position() < buf.limit()) {
|
||||||
|
final int ts = buf.getInt();
|
||||||
|
final int tz = buf.get(); // 15 min blocks
|
||||||
|
final int version = buf.get();
|
||||||
|
final int flags = buf.get();
|
||||||
|
// bit 0 is type - 0 activity, 1 sports
|
||||||
|
final int type = (flags >> 7) & 1;
|
||||||
|
// bit 1 to 6 bits are subtype
|
||||||
|
// for activity: activity, sleep, measurements, etc
|
||||||
|
// for workout: workout type (8 freestyle)
|
||||||
|
final int subtype = (flags & 127) >> 2;
|
||||||
|
// bit 6 and 7 - 0/1 - summary vs details
|
||||||
|
final int detailType = flags & 3;
|
||||||
|
LOG.debug(
|
||||||
|
"Activity Record: ts = {}, tz = {}, flags = {}, type = {}, subtype = {}, detailType = {}, version = {}",
|
||||||
|
ts, tz, String.format("0x%02X", flags), type, subtype, detailType, version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onHeartRateTest() {
|
public void onHeartRateTest() {
|
||||||
LOG.debug("Trigger heart rate one-shot test");
|
LOG.debug("Trigger heart rate one-shot test");
|
||||||
|
|
||||||
|
@ -281,8 +281,10 @@ message Charger {
|
|||||||
|
|
||||||
message Health {
|
message Health {
|
||||||
optional UserInfo userInfo = 1;
|
optional UserInfo userInfo = 1;
|
||||||
// 8, 1 get | 8, 3 set
|
|
||||||
optional bytes unknownActivity2 = 2;
|
optional bytes activityRecordIds = 2;
|
||||||
|
optional ActivitySyncRequest1 activitySyncRequest1 = 5;
|
||||||
|
|
||||||
optional SpO2 spo2 = 7;
|
optional SpO2 spo2 = 7;
|
||||||
optional HeartRate heartRate = 8;
|
optional HeartRate heartRate = 8;
|
||||||
// 8, 12 get | 8, 13 set
|
// 8, 12 get | 8, 13 set
|
||||||
@ -319,6 +321,10 @@ message UserInfo {
|
|||||||
optional uint32 goalMoving = 11; // minutes
|
optional uint32 goalMoving = 11; // minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ActivitySyncRequest1 {
|
||||||
|
optional uint32 unknown1 = 1; // 0/1
|
||||||
|
}
|
||||||
|
|
||||||
message SpO2 {
|
message SpO2 {
|
||||||
optional uint32 unknown1 = 1; // 1
|
optional uint32 unknown1 = 1; // 1
|
||||||
optional bool allDayTracking = 2;
|
optional bool allDayTracking = 2;
|
||||||
@ -385,17 +391,21 @@ message WorkoutStatusWatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message WorkoutOpenWatch {
|
message WorkoutOpenWatch {
|
||||||
optional uint32 unknown1 = 1; // 2
|
// This is only called when gps is needed?
|
||||||
|
// 1 outdoor running, 2 walking, 3 hiking, 4 trekking, 5 trail run, 6 outdoor cycling
|
||||||
|
optional uint32 sport = 1;
|
||||||
optional uint32 unknown2 = 2; // 2
|
optional uint32 unknown2 = 2; // 2
|
||||||
}
|
}
|
||||||
|
|
||||||
message WorkoutOpenReply {
|
message WorkoutOpenReply {
|
||||||
// 3 2 10
|
// 3 2 10 when no gps permissions at all
|
||||||
|
// 5 2 10 when no background permissions?
|
||||||
// ...
|
// ...
|
||||||
|
// 0 * * when phone gps is working fine
|
||||||
// 0 2 10
|
// 0 2 10
|
||||||
// 0 2 2
|
// 0 2 2
|
||||||
optional uint32 unknown1 = 1;
|
optional uint32 unknown1 = 1;
|
||||||
optional uint32 unknown2 = 2;
|
optional uint32 unknown2 = 2; // always 2?
|
||||||
optional uint32 unknown3 = 3;
|
optional uint32 unknown3 = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user