1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 12:26:48 +01:00

Zepp OS 3: Fix file transfer (notification icons, gpx upload)

This commit is contained in:
José Rebelo 2024-07-20 17:21:21 +01:00
parent 78b2c796bf
commit ab05b566bd
5 changed files with 161 additions and 13 deletions

View File

@ -58,6 +58,8 @@ public class HuamiService {
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ = UUID.fromString("00000017-0000-3512-2118-0009af100700");
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
public static final UUID UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3 = UUID.fromString("00000023-0000-3512-2118-0009af100700");
public static final int ALERT_LEVEL_NONE = 0;
public static final int ALERT_LEVEL_MESSAGE = 1;
public static final int ALERT_LEVEL_PHONE_CALL = 2;

View File

@ -145,6 +145,7 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
if (value[2] == HuamiService.AUTH_SUCCESS) {
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setCallback(null); // remove init operation as the callback
huamiSupport.enableFurtherNotifications(builder, true);
huamiSupport.requestDeviceInfo(builder);
huamiSupport.phase2Initialize(builder);

View File

@ -186,6 +186,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
try {
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setCallback(null); // remove init operation as the callback
huamiSupport.enableFurtherNotifications(builder, true);
huamiSupport.setCurrentTimeWithService(builder);
huamiSupport.requestDeviceInfo(builder);

View File

@ -39,6 +39,8 @@ import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TEMPERATURE_UNIT;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.TIME_FORMAT;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
@ -1334,6 +1336,18 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
}
@Override
public boolean onCharacteristicChanged(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic) {
final UUID characteristicUUID = characteristic.getUuid();
if (HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3.equals(characteristicUUID)) {
fileTransferService.onCharacteristicChanged(characteristic.getValue());
return true;
}
return super.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void handle2021Payload(final short type, final byte[] payload) {
if (payload == null || payload.length == 0) {

View File

@ -28,6 +28,7 @@ import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
@ -46,12 +47,16 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
private static final byte CMD_TRANSFER_RESPONSE = 0x04;
private static final byte CMD_DATA_SEND = 0x10;
private static final byte CMD_DATA_ACK = 0x11;
private static final byte CMD_DATA_V3_SEND = 0x12;
private static final byte CMD_DATA_V3_ACK = 0x13;
private static final byte FLAG_FIRST_CHUNK = 0x01;
private static final byte FLAG_LAST_CHUNK = 0x02;
private static final byte FLAG_CRC = 0x04;
private final Map<Byte, FileTransferRequest> mSessionRequests = new HashMap<>();
private int mVersion = -1;
private int mChunkSize = -1;
public ZeppOsFileTransferService(final ZeppOsSupport support) {
@ -70,13 +75,19 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
switch (payload[0]) {
case CMD_CAPABILITIES_RESPONSE:
final int version = payload[1] & 0xff;
if (version != 1 && version != 2) {
LOG.error("Unsupported file transfer service version: {}", version);
mVersion = payload[1] & 0xff;
if (mVersion != 1 && mVersion != 2 && mVersion != 3) {
LOG.error("Unsupported file transfer service version: {}", mVersion);
return;
}
mChunkSize = BLETypeConversions.toUint16(payload, 2);
LOG.info("Got file transfer service: version={}, chunkSize={}", version, mChunkSize);
// TODO parse the rest for v3
LOG.info("Got file transfer service: version={}, chunkSize={}", mVersion, mChunkSize);
if (mVersion == 3) {
final TransactionBuilder builder = getSupport().createTransactionBuilder("enable file transfer v3 notifications");
builder.notify(getSupport().getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3), true);
builder.queue(getSupport().getQueue());
}
return;
case CMD_TRANSFER_REQUEST:
handleFileTransferRequest(payload);
@ -168,16 +179,22 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
final FileTransferRequest request = new FileTransferRequest(url, filename, new byte[length], compressed, getSupport());
request.setCrc32(crc32);
final ByteBuffer buf = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_TRANSFER_RESPONSE);
buf.put(session);
buf.put((byte) 0x00);
buf.putInt(0);
if (mVersion < 3) {
final ByteBuffer buf = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_TRANSFER_RESPONSE);
buf.put(session);
buf.put((byte) 0x00);
buf.putInt(0);
write("send file transfer response", buf.array());
} else {
// FIXME: Receive files on v3
LOG.error("Receiving files on V3 is not implemented");
return;
}
mSessionRequests.put(session, request);
write("send file transfer response", buf.array());
}
private void handleFileTransferData(final byte[] payload) {
@ -270,12 +287,24 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
final FileTransferRequest request = new FileTransferRequest(url, filename, bytes, false, callback);
if (mVersion == 3 && !mSessionRequests.isEmpty()) {
// FIXME non-zero session on v3
LOG.error("File transfer v3 only supports single session, not sending file");
callback.onFileUploadFinish(false);
return;
}
byte session = (byte) mSessionRequests.size();
while (mSessionRequests.containsKey(session)) {
session++;
}
final ByteBuffer buf = ByteBuffer.allocate(2 + url.length() + 1 + filename.length() + 1 + 4 + 4);
int payloadSize = 2 + url.length() + 1 + filename.length() + 1 + 4 + 4;
if (mVersion == 3) {
payloadSize += 2;
}
final ByteBuffer buf = ByteBuffer.allocate(payloadSize);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_TRANSFER_REQUEST);
buf.put(session);
@ -285,6 +314,11 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
buf.put((byte) 0x00);
buf.putInt(bytes.length);
buf.putInt(request.getCrc32());
if (mVersion == 3) {
// compression ?
buf.put((byte) 0);
buf.put((byte) 0);
}
write("send file upload request", buf.array());
@ -306,6 +340,20 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
LOG.debug("Sending file data for session={}, progress={}, index={}", session, request.getProgress(), request.getIndex());
if (mVersion < 3) {
writeChunkV1(request, session);
} else {
if (session != 0) {
// FIXME non-zero session on v3
LOG.error("Sending non-zero session on v3 is not supported, got session={}", session);
mSessionRequests.remove(session);
return;
}
writeChunkV3(request);
}
}
private void writeChunkV1(final FileTransferRequest request, final byte session) {
final ByteBuffer buf = ByteBuffer.allocate(10 + mChunkSize);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_DATA_SEND);
@ -344,6 +392,48 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
write("send file data", buf.array());
}
private void writeChunkV3(final FileTransferRequest request) {
final byte[] chunk = ArrayUtils.subarray(
request.getBytes(),
request.getProgress(),
request.getProgress() + mChunkSize
);
byte flags = 0;
if (request.getProgress() == 0) {
flags |= FLAG_FIRST_CHUNK;
}
if (request.getProgress() + mChunkSize >= request.getSize()) {
flags |= FLAG_LAST_CHUNK;
}
final int partSize = getSupport().getMTU() - 3;
final ByteBuffer buf = ByteBuffer.allocate(chunk.length + 5);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_DATA_V3_SEND);
buf.put(flags);
buf.put(request.getIndex());
buf.putShort((short) chunk.length);
buf.put(chunk);
final byte[] payload = buf.array();
final TransactionBuilder builder = getSupport().createTransactionBuilder("send chunk v3");
for (int i = 0; i < payload.length; i += partSize) {
final byte[] part = ArrayUtils.subarray(payload, i, i + partSize);
builder.write(
getSupport().getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3),
part
);
}
builder.queue(getSupport().getQueue());
request.setProgress(request.getProgress() + chunk.length);
request.setIndex((byte) (request.getIndex() + 1));
request.getCallback().onFileUploadProgress(request.getProgress());
}
private void onUploadFinish(final byte session, final boolean success) {
final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) {
@ -356,6 +446,46 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
request.getCallback().onFileUploadFinish(success);
}
public void onCharacteristicChanged(final byte[] value) {
if (value[0] != CMD_DATA_V3_ACK) {
LOG.error("Got non-ack on file transfer characteristic");
return;
}
final byte session = (byte) 0; // FIXME non-zero session on v3
final byte status = value[1];
final byte chunkIndex = value[2];
final byte unk1 = value[3]; // 1/2?
LOG.info(
"Band acknowledged file transfer data: session={}, status={}, chunkIndex={}, unk1={}",
session,
status,
chunkIndex,
unk1
);
final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) {
LOG.error("No request found for v3 session {}", session);
return;
}
if (status != 0) {
LOG.error("Unexpected status from band, aborting session {}", session);
onUploadFinish(session, false);
return;
}
if (request.getIndex() - 1 != chunkIndex) {
LOG.error("Got ack for unexpected chunk index {}, expected {}", chunkIndex, request.getIndex() - 1);
onUploadFinish(session, false);
return;
}
sendNextQueuedData(session);
}
/**
* Wrapper class to keep track of ongoing file send requests and their progress.
*/