1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2025-02-18 05:17:08 +01:00

Zepp OS: Add incoming file support to file transfer service

This commit is contained in:
José Rebelo 2023-06-11 15:21:39 +01:00
parent c77a5467e7
commit d38afe60c2
5 changed files with 189 additions and 55 deletions

View File

@ -96,15 +96,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder; import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2021; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2021;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
@ -120,7 +116,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsMorningUpdatesService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsMorningUpdatesService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
@ -133,7 +129,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public abstract class Huami2021Support extends HuamiSupport { public abstract class Huami2021Support extends HuamiSupport implements ZeppOsFileTransferService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(Huami2021Support.class); private static final Logger LOG = LoggerFactory.getLogger(Huami2021Support.class);
// Tracks whether realtime HR monitoring is already started, so we can just // Tracks whether realtime HR monitoring is already started, so we can just
@ -141,7 +137,7 @@ public abstract class Huami2021Support extends HuamiSupport {
private boolean heartRateRealtimeStarted; private boolean heartRateRealtimeStarted;
// Services // Services
private final ZeppOsFileUploadService fileUploadService = new ZeppOsFileUploadService(this); private final ZeppOsFileTransferService fileTransferService = new ZeppOsFileTransferService(this);
private final ZeppOsConfigService configService = new ZeppOsConfigService(this); private final ZeppOsConfigService configService = new ZeppOsConfigService(this);
private final ZeppOsAgpsService agpsService = new ZeppOsAgpsService(this); private final ZeppOsAgpsService agpsService = new ZeppOsAgpsService(this);
private final ZeppOsWifiService wifiService = new ZeppOsWifiService(this); private final ZeppOsWifiService wifiService = new ZeppOsWifiService(this);
@ -154,12 +150,12 @@ public abstract class Huami2021Support extends HuamiSupport {
private final ZeppOsAlarmsService alarmsService = new ZeppOsAlarmsService(this); private final ZeppOsAlarmsService alarmsService = new ZeppOsAlarmsService(this);
private final ZeppOsCalendarService calendarService = new ZeppOsCalendarService(this); private final ZeppOsCalendarService calendarService = new ZeppOsCalendarService(this);
private final ZeppOsCannedMessagesService cannedMessagesService = new ZeppOsCannedMessagesService(this); private final ZeppOsCannedMessagesService cannedMessagesService = new ZeppOsCannedMessagesService(this);
private final ZeppOsNotificationService notificationService = new ZeppOsNotificationService(this, fileUploadService); private final ZeppOsNotificationService notificationService = new ZeppOsNotificationService(this, fileTransferService);
private final ZeppOsAlexaService alexaService = new ZeppOsAlexaService(this); private final ZeppOsAlexaService alexaService = new ZeppOsAlexaService(this);
private final ZeppOsAppsService appsService = new ZeppOsAppsService(this); private final ZeppOsAppsService appsService = new ZeppOsAppsService(this);
private final Map<Short, AbstractZeppOsService> mServiceMap = new LinkedHashMap<Short, AbstractZeppOsService>() {{ private final Map<Short, AbstractZeppOsService> mServiceMap = new LinkedHashMap<Short, AbstractZeppOsService>() {{
put(fileUploadService.getEndpoint(), fileUploadService); put(fileTransferService.getEndpoint(), fileTransferService);
put(configService.getEndpoint(), configService); put(configService.getEndpoint(), configService);
put(agpsService.getEndpoint(), agpsService); put(agpsService.getEndpoint(), agpsService);
put(wifiService.getEndpoint(), wifiService); put(wifiService.getEndpoint(), wifiService);
@ -645,7 +641,7 @@ public abstract class Huami2021Support extends HuamiSupport {
this, this,
agpsHandler.getFile(), agpsHandler.getFile(),
agpsService, agpsService,
fileUploadService, fileTransferService,
configService configService
).perform(); ).perform();
} catch (final Exception e) { } catch (final Exception e) {
@ -661,7 +657,7 @@ public abstract class Huami2021Support extends HuamiSupport {
new ZeppOsGpxRouteUploadOperation( new ZeppOsGpxRouteUploadOperation(
this, this,
gpxRouteHandler.getFile(), gpxRouteHandler.getFile(),
fileUploadService fileTransferService
).perform(); ).perform();
} catch (final Exception e) { } catch (final Exception e) {
GB.toast(getContext(), "Gpx route file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); GB.toast(getContext(), "Gpx route file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
@ -1868,6 +1864,21 @@ public abstract class Huami2021Support extends HuamiSupport {
} }
} }
@Override
public void onFileUploadFinish(final boolean success) {
LOG.warn("Unexpected file upload finish: {}", success);
}
@Override
public void onFileUploadProgress(final int progress) {
LOG.warn("Unexpected file upload progress: {}", progress);
}
@Override
public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
LOG.info("File received: url={} filename={} length={}", url, filename, data.length);
}
private byte bool(final boolean b) { private byte bool(final boolean b) {
return (byte) (b ? 1 : 0); return (byte) (b ? 1 : 0);
} }

View File

@ -28,20 +28,20 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressActi
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
/** /**
* Updates the AGPS EPO on a Zepp OS device. Update goes as follows: * Updates the AGPS EPO on a Zepp OS device. Update goes as follows:
* 1. Request an upload start from {@link ZeppOsAgpsService} * 1. Request an upload start from {@link ZeppOsAgpsService}
* 2. After successful ack from 1, upload the file to agps://upgrade using {@link ZeppOsFileUploadService} * 2. After successful ack from 1, upload the file to agps://upgrade using {@link ZeppOsFileTransferService}
* 3. After successful ack from 2, trigger the actual update with {@link ZeppOsAgpsService} * 3. After successful ack from 2, trigger the actual update with {@link ZeppOsAgpsService}
* 4. After successful ack from 3, update is finished. Trigger an AGPS config request from {@link ZeppOsConfigService} * 4. After successful ack from 3, update is finished. Trigger an AGPS config request from {@link ZeppOsConfigService}
* to reload the AGPS update and expiration timestamps. * to reload the AGPS update and expiration timestamps.
*/ */
public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<Huami2021Support> public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<Huami2021Support>
implements ZeppOsFileUploadService.Callback, ZeppOsAgpsService.Callback { implements ZeppOsFileTransferService.Callback, ZeppOsAgpsService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsUpdateOperation.class); private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsUpdateOperation.class);
private static final String AGPS_UPDATE_URL = "agps://upgrade"; private static final String AGPS_UPDATE_URL = "agps://upgrade";
@ -51,19 +51,19 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<Huami2021Su
private final byte[] fileBytes; private final byte[] fileBytes;
private final ZeppOsAgpsService agpsService; private final ZeppOsAgpsService agpsService;
private final ZeppOsFileUploadService fileUploadService; private final ZeppOsFileTransferService fileTransferService;
private final ZeppOsConfigService configService; private final ZeppOsConfigService configService;
public ZeppOsAgpsUpdateOperation(final Huami2021Support support, public ZeppOsAgpsUpdateOperation(final Huami2021Support support,
final ZeppOsAgpsFile file, final ZeppOsAgpsFile file,
final ZeppOsAgpsService agpsService, final ZeppOsAgpsService agpsService,
final ZeppOsFileUploadService fileUploadService, final ZeppOsFileTransferService fileTransferService,
final ZeppOsConfigService configService) { final ZeppOsConfigService configService) {
super(support); super(support);
this.file = file; this.file = file;
this.fileBytes = file.getUihhBytes(); this.fileBytes = file.getUihhBytes();
this.agpsService = agpsService; this.agpsService = agpsService;
this.fileUploadService = fileUploadService; this.fileTransferService = fileTransferService;
this.configService = configService; this.configService = configService;
} }
@ -99,6 +99,11 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<Huami2021Su
updateProgress(progressPercent); updateProgress(progressPercent);
} }
@Override
public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
LOG.warn("Received unexpected file: url={} filename={} length={}", url, filename, data.length);
}
@Override @Override
public void onAgpsUploadStartResponse(final boolean success) { public void onAgpsUploadStartResponse(final boolean success) {
if (!success) { if (!success) {
@ -106,7 +111,7 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<Huami2021Su
return; return;
} }
fileUploadService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, this); fileTransferService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, this);
} }
@Override @Override

View File

@ -26,31 +26,31 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<Huami2021Support> public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<Huami2021Support>
implements ZeppOsFileUploadService.Callback { implements ZeppOsFileTransferService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteUploadOperation.class); private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteUploadOperation.class);
private final ZeppOsGpxRouteFile file; private final ZeppOsGpxRouteFile file;
private final byte[] fileBytes; private final byte[] fileBytes;
private final ZeppOsFileUploadService fileUploadService; private final ZeppOsFileTransferService fileTransferService;
public ZeppOsGpxRouteUploadOperation(final Huami2021Support support, public ZeppOsGpxRouteUploadOperation(final Huami2021Support support,
final ZeppOsGpxRouteFile file, final ZeppOsGpxRouteFile file,
final ZeppOsFileUploadService fileUploadService) { final ZeppOsFileTransferService fileTransferService) {
super(support); super(support);
this.file = file; this.file = file;
this.fileBytes = file.getEncodedBytes(); this.fileBytes = file.getEncodedBytes();
this.fileUploadService = fileUploadService; this.fileTransferService = fileTransferService;
} }
@Override @Override
protected void doPerform() throws IOException { protected void doPerform() throws IOException {
fileUploadService.sendFile( fileTransferService.sendFile(
"sport://file_transfer?appId=7073283073&params={}", "sport://file_transfer?appId=7073283073&params={}",
"track_" + file.getTimestamp() + ".dat", "track_" + file.getTimestamp() + ".dat",
fileBytes, fileBytes,
@ -88,6 +88,11 @@ public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<Huami20
updateProgress(progressPercent); updateProgress(progressPercent);
} }
@Override
public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
LOG.warn("Received unexpected file: url={} filename={} length={}", url, filename, data.length);
}
private void updateProgress(final int progressPercent) { private void updateProgress(final int progressPercent) {
try { try {
final TransactionBuilder builder = performInitialized("send gpx route upload progress"); final TransactionBuilder builder = performInitialized("send gpx route upload progress");

View File

@ -31,28 +31,28 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class ZeppOsFileUploadService extends AbstractZeppOsService { public class ZeppOsFileTransferService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFileUploadService.class); private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFileTransferService.class);
private static final short ENDPOINT = 0x000d; private static final short ENDPOINT = 0x000d;
private static final byte CMD_CAPABILITIES_REQUEST = 0x01; private static final byte CMD_CAPABILITIES_REQUEST = 0x01;
private static final byte CMD_CAPABILITIES_RESPONSE = 0x02; private static final byte CMD_CAPABILITIES_RESPONSE = 0x02;
private static final byte CMD_UPLOAD_REQUEST = 0x03; private static final byte CMD_TRANSFER_REQUEST = 0x03;
private static final byte CMD_UPLOAD_RESPONSE = 0x04; private static final byte CMD_TRANSFER_RESPONSE = 0x04;
private static final byte CMD_DATA_SEND = 0x10; private static final byte CMD_DATA_SEND = 0x10;
private static final byte CMD_DATA_ACK = 0x11; private static final byte CMD_DATA_ACK = 0x11;
private static final byte FLAG_FIRST_CHUNK = 0x01; private static final byte FLAG_FIRST_CHUNK = 0x01;
private static final byte FLAG_LAST_CHUNK = 0x02; private static final byte FLAG_LAST_CHUNK = 0x02;
private final Map<Byte, FileSendRequest> mSessionRequests = new HashMap<>(); private final Map<Byte, FileTransferRequest> mSessionRequests = new HashMap<>();
private int mChunkSize = -1; private int mChunkSize = -1;
public ZeppOsFileUploadService(final Huami2021Support support) { public ZeppOsFileTransferService(final Huami2021Support support) {
super(support); super(support);
} }
@ -75,25 +75,28 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
case CMD_CAPABILITIES_RESPONSE: case CMD_CAPABILITIES_RESPONSE:
final int version = payload[1] & 0xff; final int version = payload[1] & 0xff;
if (version != 1 && version != 2) { if (version != 1 && version != 2) {
LOG.error("Unsupported file upload service version: {}", version); LOG.error("Unsupported file transfer service version: {}", version);
return; return;
} }
mChunkSize = BLETypeConversions.toUint16(payload, 2); mChunkSize = BLETypeConversions.toUint16(payload, 2);
LOG.info("Got file upload service: version={}, chunkSize={}", version, mChunkSize); LOG.info("Got file transfer service: version={}, chunkSize={}", version, mChunkSize);
return; return;
case CMD_UPLOAD_RESPONSE: case CMD_TRANSFER_REQUEST:
handleFileTransferRequest(payload);
return;
case CMD_TRANSFER_RESPONSE:
session = payload[1]; session = payload[1];
status = payload[2]; status = payload[2];
final int existingProgress = BLETypeConversions.toUint32(payload, 3); final int existingProgress = BLETypeConversions.toUint32(payload, 3);
LOG.info("Band acknowledged file upload request: session={}, status={}, existingProgress={}", session, status, existingProgress); LOG.info("Band acknowledged file transfer request: session={}, status={}, existingProgress={}", session, status, existingProgress);
if (status != 0) { if (status != 0) {
LOG.error("Unexpected status from band for session {}, aborting", session); LOG.error("Unexpected status from band for session {}, aborting", session);
onFinish(session, false); onUploadFinish(session, false);
return; return;
} }
if (existingProgress != 0) { if (existingProgress != 0) {
LOG.info("Updating existing progress for session {} to {}", session, existingProgress); LOG.info("Updating existing progress for session {} to {}", session, existingProgress);
final FileSendRequest request = mSessionRequests.get(session); final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) { if (request == null) {
LOG.error("No request found for session {}", session); LOG.error("No request found for session {}", session);
return; return;
@ -102,19 +105,22 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
} }
sendNextQueuedData(session); sendNextQueuedData(session);
return; return;
case CMD_DATA_SEND:
handleFileTransferData(payload);
return;
case CMD_DATA_ACK: case CMD_DATA_ACK:
session = payload[1]; session = payload[1];
status = payload[2]; status = payload[2];
LOG.info("Band acknowledged file upload data: session={}, status={}", session, status); LOG.info("Band acknowledged file transfer data: session={}, status={}", session, status);
if (status != 0) { if (status != 0) {
LOG.error("Unexpected status from band, aborting session {}", session); LOG.error("Unexpected status from band, aborting session {}", session);
onFinish(session, false); onUploadFinish(session, false);
return; return;
} }
sendNextQueuedData(session); sendNextQueuedData(session);
return; return;
default: default:
LOG.warn("Unexpected file upload byte {}", String.format("0x%02x", payload[0])); LOG.warn("Unexpected file transfer byte {}", String.format("0x%02x", payload[0]));
} }
} }
@ -127,6 +133,93 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
write(builder, new byte[]{CMD_CAPABILITIES_REQUEST}); write(builder, new byte[]{CMD_CAPABILITIES_REQUEST});
} }
private void handleFileTransferRequest(final byte[] payload) {
// File transfer request initialized from watch
int pos = 1;
final byte session = payload[pos++];
final String url = StringUtils.untilNullTerminator(payload, pos);
if (url == null) {
LOG.error("Unable to parse url from transfer request");
return;
}
pos += url.length() + 1;
final String filename = StringUtils.untilNullTerminator(payload, pos);
if (filename == null) {
LOG.error("Unable to parse filename from transfer request");
return;
}
pos += filename.length() + 1;
final int length = BLETypeConversions.toUint32(payload, pos);
pos += 4;
final int crc32 = BLETypeConversions.toUint32(payload, pos);
LOG.info("Got transfer request: session={}, url={}, filename={}, length={}", session, url, filename, length);
final FileTransferRequest request = new FileTransferRequest(url, filename, new byte[length], 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);
mSessionRequests.put(session, request);
write("send file transfer response", buf.array());
}
private void handleFileTransferData(final byte[] payload) {
final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
buf.get(); // Discard first byte
final byte secondByte = buf.get();
final boolean firstPacket = (secondByte == 1);
final boolean lastPacket = (secondByte == 2);
final byte session = buf.get();
final byte index = buf.get();
final short size = buf.getShort();
final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) {
LOG.error("No request found for session {}", session);
return;
}
if (index != request.index) {
LOG.warn("Unexpected index {}, expected {}", index, request.index);
return;
}
if (firstPacket && request.getProgress() != 0) {
LOG.warn("Got first packet, but progress is {}", request.getProgress());
return;
}
buf.get(request.getBytes(), request.getProgress(), size);
request.setIndex((byte) (index + 1));
request.setProgress(request.getProgress() + size);
LOG.debug("Got data for session={}, progress={}/{}", session, request.getProgress(), request.getSize());
if (lastPacket) {
mSessionRequests.remove(session);
if (request.getProgress() != request.getSize()) {
LOG.warn("Request not finished: {}/{}", request.getProgress(), request.getSize());
return;
}
final int checksum = CheckSums.getCRC32(request.getBytes());
if (checksum != request.getCrc32()) {
LOG.warn("Checksum mismatch: expected {}, got {}", request.getCrc32(), checksum);
return;
}
request.getCallback().onFileDownloadFinish(request.getUrl(), request.getFilename(), request.getBytes());
}
}
public void sendFile(final String url, final String filename, final byte[] bytes, final Callback callback) { public void sendFile(final String url, final String filename, final byte[] bytes, final Callback callback) {
if (mChunkSize < 0) { if (mChunkSize < 0) {
LOG.error("Service not initialized, refusing to send {}", url); LOG.error("Service not initialized, refusing to send {}", url);
@ -136,20 +229,23 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
LOG.info("Sending {} bytes to {}", bytes.length, url); LOG.info("Sending {} bytes to {}", bytes.length, url);
final FileSendRequest request = new FileSendRequest(url, filename, bytes, callback); final FileTransferRequest request = new FileTransferRequest(url, filename, bytes, callback);
final byte session = (byte) mSessionRequests.size(); byte session = (byte) mSessionRequests.size();
while (mSessionRequests.containsKey(session)) {
session++;
}
final ByteBuffer buf = ByteBuffer.allocate(2 + url.length() + 1 + filename.length() + 1 + 4 + 4); final ByteBuffer buf = ByteBuffer.allocate(2 + url.length() + 1 + filename.length() + 1 + 4 + 4);
buf.order(ByteOrder.LITTLE_ENDIAN); buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(CMD_UPLOAD_REQUEST); buf.put(CMD_TRANSFER_REQUEST);
buf.put(session); buf.put(session);
buf.put(url.getBytes(StandardCharsets.UTF_8)); buf.put(url.getBytes(StandardCharsets.UTF_8));
buf.put((byte) 0x00); buf.put((byte) 0x00);
buf.put(filename.getBytes(StandardCharsets.UTF_8)); buf.put(filename.getBytes(StandardCharsets.UTF_8));
buf.put((byte) 0x00); buf.put((byte) 0x00);
buf.putInt(bytes.length); buf.putInt(bytes.length);
buf.putInt(CheckSums.getCRC32(bytes)); buf.putInt(request.getCrc32());
write("send file upload request", buf.array()); write("send file upload request", buf.array());
@ -157,7 +253,7 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
} }
private void sendNextQueuedData(final byte session) { private void sendNextQueuedData(final byte session) {
final FileSendRequest request = mSessionRequests.get(session); final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) { if (request == null) {
LOG.error("No request found for session {}", session); LOG.error("No request found for session {}", session);
return; return;
@ -165,7 +261,7 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
if (request.getProgress() >= request.getSize()) { if (request.getProgress() >= request.getSize()) {
LOG.info("Sending {} finished", request.getUrl()); LOG.info("Sending {} finished", request.getUrl());
onFinish(session, true); onUploadFinish(session, true);
return; return;
} }
@ -209,8 +305,8 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
write("send file data", buf.array()); write("send file data", buf.array());
} }
private void onFinish(final byte session, final boolean success) { private void onUploadFinish(final byte session, final boolean success) {
final FileSendRequest request = mSessionRequests.get(session); final FileTransferRequest request = mSessionRequests.get(session);
if (request == null) { if (request == null) {
LOG.error("No request found for session {}", session); LOG.error("No request found for session {}", session);
return; return;
@ -224,19 +320,21 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
/** /**
* Wrapper class to keep track of ongoing file send requests and their progress. * Wrapper class to keep track of ongoing file send requests and their progress.
*/ */
public static class FileSendRequest { public static class FileTransferRequest {
private final String url; private final String url;
private final String filename; private final String filename;
private final byte[] bytes; private final byte[] bytes;
private final Callback callback; private final Callback callback;
private int progress = 0; private int progress = 0;
private byte index = 0; private byte index = 0;
private int crc32;
public FileSendRequest(final String url, final String filename, final byte[] bytes, final Callback callback) { public FileTransferRequest(final String url, final String filename, final byte[] bytes, final Callback callback) {
this.url = url; this.url = url;
this.filename = filename; this.filename = filename;
this.bytes = bytes; this.bytes = bytes;
this.callback = callback; this.callback = callback;
this.crc32 = CheckSums.getCRC32(bytes);
} }
public String getUrl() { public String getUrl() {
@ -274,11 +372,21 @@ public class ZeppOsFileUploadService extends AbstractZeppOsService {
public void setIndex(final byte index) { public void setIndex(final byte index) {
this.index = index; this.index = index;
} }
public int getCrc32() {
return crc32;
}
public void setCrc32(final int crc32) {
this.crc32 = crc32;
}
} }
public interface Callback { public interface Callback {
void onFileUploadFinish(boolean success); void onFileUploadFinish(boolean success);
void onFileUploadProgress(int progress); void onFileUploadProgress(int progress);
void onFileDownloadFinish(String url, String filename, byte[] data);
} }
} }

View File

@ -71,11 +71,11 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
// This needs to be simplified. // This needs to be simplified.
private final LimitedQueue mNotificationReplyAction = new LimitedQueue(16); private final LimitedQueue mNotificationReplyAction = new LimitedQueue(16);
private final ZeppOsFileUploadService fileUploadService; private final ZeppOsFileTransferService fileTransferService;
public ZeppOsNotificationService(final Huami2021Support support, final ZeppOsFileUploadService fileUploadService) { public ZeppOsNotificationService(final Huami2021Support support, final ZeppOsFileTransferService fileTransferService) {
super(support); super(support);
this.fileUploadService = fileUploadService; this.fileTransferService = fileTransferService;
} }
@Override @Override
@ -380,11 +380,11 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
); );
final String filename = String.format("logo_%s.tga", packageName.replace(".", "_")); final String filename = String.format("logo_%s.tga", packageName.replace(".", "_"));
fileUploadService.sendFile( fileTransferService.sendFile(
url, url,
filename, filename,
tga565, tga565,
new ZeppOsFileUploadService.Callback() { new ZeppOsFileTransferService.Callback() {
@Override @Override
public void onFileUploadFinish(final boolean success) { public void onFileUploadFinish(final boolean success) {
LOG.info("Finished sending icon, success={}", success); LOG.info("Finished sending icon, success={}", success);
@ -397,6 +397,11 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
public void onFileUploadProgress(final int progress) { public void onFileUploadProgress(final int progress) {
LOG.trace("Icon send progress: {}", progress); LOG.trace("Icon send progress: {}", progress);
} }
@Override
public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
LOG.warn("Receiver unexpected file: url={} filename={} length={}", url, filename, data.length);
}
} }
); );