Basic path is working on Android side
This commit is contained in:
parent
a98ac862f0
commit
0e73995630
|
@ -55,8 +55,6 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
productFlavors {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -22,8 +22,10 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
|
@ -34,6 +36,7 @@ import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
|||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.HexFileValidationException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory;
|
||||
|
||||
/* package */ abstract class BaseCustomDfuImpl extends BaseDfuImpl {
|
||||
/**
|
||||
|
@ -101,23 +104,22 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
mProgressInfo.addBytesSent(characteristic.getValue().length);
|
||||
mPacketsSentSinceNotification++;
|
||||
|
||||
// If a packet receipt notification is expected, or the last packet was sent, do nothing. There onCharacteristicChanged listener will catch either
|
||||
// a packet confirmation (if there are more bytes to send) or the image received notification (it upload process was completed)
|
||||
final boolean notificationExpected = mPacketsBeforeNotification > 0 && mPacketsSentSinceNotification == mPacketsBeforeNotification;
|
||||
final boolean lastPacketTransferred = mProgressInfo.isComplete();
|
||||
final boolean lastObjectPacketTransferred = mProgressInfo.isObjectComplete();
|
||||
|
||||
// This flag may be true only in Secure DFU.
|
||||
// In Secure DFU we usually do not get any notification after the object is completed, therefor the lock must be notified here.
|
||||
if (lastObjectPacketTransferred) {
|
||||
// When a Packet Receipt Notification notification is expected
|
||||
// we must not call notifyLock() as the process will resume after notification is received.
|
||||
if (notificationExpected)
|
||||
return;
|
||||
|
||||
// In Secure DFU we (usually, depends on the page size and PRN value) do not get any notification after the object is completed,
|
||||
// therefor the lock must be notified here to resume the main process.
|
||||
if (lastPacketTransferred || lastObjectPacketTransferred) {
|
||||
mFirmwareUploadInProgress = false;
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
// When a notification is expected (either a Packet Receipt Notification or one that's send after the whole image is completed)
|
||||
// we must not call notifyLock as the process will resume after notification is received.
|
||||
if (notificationExpected || lastPacketTransferred)
|
||||
return;
|
||||
|
||||
// When neither of them is true, send the next packet
|
||||
try {
|
||||
|
@ -125,15 +127,15 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
// The writing might have been aborted (mAborted = true), an error might have occurred.
|
||||
// In that case stop sending.
|
||||
if (mAborted || mError != 0 || mRemoteErrorOccurred || mResetRequestSent) {
|
||||
// notify waiting thread
|
||||
synchronized (mLock) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Upload terminated");
|
||||
mLock.notifyAll();
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final byte[] buffer = mBuffer;
|
||||
final int available = mProgressInfo.getAvailableObjectSizeIsBytes();
|
||||
byte[] buffer = mBuffer;
|
||||
if (available < 20)
|
||||
buffer = new byte[available];
|
||||
final int size = mFirmwareStream.read(buffer);
|
||||
writePacket(gatt, characteristic, buffer, size);
|
||||
return;
|
||||
|
@ -170,8 +172,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
protected void handlePacketReceiptNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
||||
// Secure DFU:
|
||||
// When PRN is set to be received after the object is complete we don't want to send anything. First the object needs to be executed.
|
||||
if (!mFirmwareUploadInProgress)
|
||||
if (!mFirmwareUploadInProgress) {
|
||||
handleNotification(gatt, characteristic);
|
||||
return;
|
||||
}
|
||||
|
||||
final BluetoothGattCharacteristic packetCharacteristic = gatt.getService(getDfuServiceUUID()).getCharacteristic(getPacketCharacteristicUUID());
|
||||
try {
|
||||
|
@ -185,7 +189,19 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
return;
|
||||
}
|
||||
|
||||
final byte[] buffer = mBuffer;
|
||||
final boolean lastPacketTransferred = mProgressInfo.isComplete();
|
||||
final boolean lastObjectPacketTransferred = mProgressInfo.isObjectComplete();
|
||||
|
||||
if (lastPacketTransferred || lastObjectPacketTransferred) {
|
||||
mFirmwareUploadInProgress = false;
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
|
||||
final int available = mProgressInfo.getAvailableObjectSizeIsBytes();
|
||||
byte[] buffer = mBuffer;
|
||||
if (available < 20)
|
||||
buffer = new byte[available];
|
||||
final int size = mFirmwareStream.read(buffer);
|
||||
writePacket(gatt, packetCharacteristic, buffer, size);
|
||||
} catch (final HexFileValidationException e) {
|
||||
|
@ -230,6 +246,26 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
protected abstract UUID getDfuServiceUUID();
|
||||
|
||||
/**
|
||||
* Wends the whole init packet stream to the given characteristic.
|
||||
* @param characteristic the target characteristic
|
||||
* @throws DfuException
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeInitData(final BluetoothGattCharacteristic characteristic) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
|
||||
try {
|
||||
byte[] data = new byte[20];
|
||||
int size;
|
||||
while ((size = mInitPacketStream.read(data, 0, data.length)) != -1) {
|
||||
writeInitPacket(characteristic, data, size);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading Init packet file", e);
|
||||
throw new DfuException("Error while reading Init packet file", DfuBaseService.ERROR_FILE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the Init packet to the characteristic. This method is SYNCHRONOUS and wait until the {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
|
||||
* will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
|
@ -241,7 +277,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeInitPacket(final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException,
|
||||
private void writeInitPacket(final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
byte[] locBuffer = buffer;
|
||||
if (buffer.length != size) {
|
||||
|
@ -329,7 +365,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* @param buffer the buffer with 1-20 bytes
|
||||
* @param size the number of bytes from the buffer to send
|
||||
*/
|
||||
protected void writePacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) {
|
||||
private void writePacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) {
|
||||
byte[] locBuffer = buffer;
|
||||
if (size <= 0) // This should never happen
|
||||
return;
|
||||
|
@ -339,6 +375,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(locBuffer);
|
||||
logi("Writing packet: " + parse(locBuffer)); // TODO remove
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
// FIXME BLE buffer overflow
|
||||
// after writing to the device with WRITE_NO_RESPONSE property the onCharacteristicWrite callback is received immediately after writing data to a buffer.
|
||||
|
@ -347,4 +384,95 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
// More info: this works fine on Nexus 5 (Android 4.4) (4.3 seconds) and on Samsung S4 (Android 4.3) (20 seconds) so this is a driver issue.
|
||||
// Nexus 4 and 7 uses Qualcomm chip, Nexus 5 and Samsung uses Broadcom chips.
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the BLE connection to the device and removes/restores bonding, if a proper flags were set in the {@link DfuServiceInitiator}.
|
||||
* This method will also change the DFU state to completed or restart the service to send the second part.
|
||||
* @param intent the intent used to start the DFU service. It contains all user flags in the bundle.
|
||||
* @param forceRefresh true, if cache should be cleared even for a bonded device. Usually the Service Changed indication should be used for this purpose.
|
||||
*/
|
||||
protected void finalize(final Intent intent, final boolean forceRefresh) {
|
||||
/*
|
||||
* We are done with DFU. Now the service may refresh device cache and clear stored services.
|
||||
* For bonded device this is required only if if doesn't support Service Changed indication.
|
||||
* Android shouldn't cache services of non-bonded devices having Service Changed characteristic in their database, but it does, so...
|
||||
*/
|
||||
final boolean keepBond = intent.getBooleanExtra(DfuBaseService.EXTRA_KEEP_BOND, false);
|
||||
mService.refreshDeviceCache(mGatt, forceRefresh || !keepBond);
|
||||
|
||||
// Close the device
|
||||
mService.close(mGatt);
|
||||
|
||||
/*
|
||||
* During the update the bonding information on the target device may have been removed.
|
||||
* To create bond with the new application set the EXTRA_RESTORE_BOND extra to true.
|
||||
* In case the bond information is copied to the new application the new bonding is not required.
|
||||
*/
|
||||
boolean alreadyWaited = false;
|
||||
if (mGatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
final boolean restoreBond = intent.getBooleanExtra(DfuBaseService.EXTRA_RESTORE_BOND, false);
|
||||
if (restoreBond || !keepBond || (mFileType & DfuBaseService.TYPE_SOFT_DEVICE) > 0) {
|
||||
// The bond information was lost.
|
||||
removeBond();
|
||||
|
||||
// Give some time for removing the bond information. 300ms was to short, let's set it to 2 seconds just to be sure.
|
||||
mService.waitFor(2000);
|
||||
alreadyWaited = true;
|
||||
}
|
||||
|
||||
if (restoreBond && (mFileType & DfuBaseService.TYPE_APPLICATION) > 0) {
|
||||
// Restore pairing when application was updated.
|
||||
createBond();
|
||||
alreadyWaited = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to send PROGRESS_COMPLETED message only when all files has been transmitted.
|
||||
* In case you want to send the Soft Device and/or Bootloader and the Application, the service will be started twice: one to send SD+BL, and the
|
||||
* second time to send the Application only (using the new Bootloader). In the first case we do not send PROGRESS_COMPLETED notification.
|
||||
*/
|
||||
if (mProgressInfo.isLastPart()) {
|
||||
// Delay this event a little bit. Android needs some time to prepare for reconnection.
|
||||
if (!alreadyWaited)
|
||||
mService.waitFor(1400);
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_COMPLETED);
|
||||
} else {
|
||||
/*
|
||||
* In case when the Soft Device has been upgraded, and the application should be send in the following connection, we have to
|
||||
* make sure that we know the address the device is advertising with. Depending on the method used to start the DFU bootloader the first time
|
||||
* the new Bootloader may advertise with the same address or one incremented by 1.
|
||||
* When the buttonless update was used, the bootloader will use the same address as the application. The cached list of services on the Android device
|
||||
* should be cleared thanks to the Service Changed characteristic (the fact that it exists if not bonded, or the Service Changed indication on bonded one).
|
||||
* In case of forced DFU mode (using a button), the Bootloader does not know whether there was the Service Changed characteristic present in the list of
|
||||
* application's services so it must advertise with a different address. The same situation applies when the new Soft Device was uploaded and the old
|
||||
* application has been removed in this process.
|
||||
*
|
||||
* We could have save the fact of jumping as a parameter of the service but it ma be that some Android devices must first scan a device before connecting to it.
|
||||
* It a device with the address+1 has never been detected before the service could have failed on connection.
|
||||
*/
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Scanning for the DFU Bootloader...");
|
||||
final String newAddress = BootloaderScannerFactory.getScanner().searchFor(mGatt.getDevice().getAddress());
|
||||
if (newAddress != null)
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "DFU Bootloader found with address " + newAddress);
|
||||
else {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "DFU Bootloader not found. Trying the same address...");
|
||||
}
|
||||
|
||||
/*
|
||||
* The current service instance has uploaded the Soft Device and/or Bootloader.
|
||||
* We need to start another instance that will try to send application only.
|
||||
*/
|
||||
logi("Starting service that will upload application");
|
||||
final Intent newIntent = new Intent();
|
||||
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_FILE_MIME_TYPE, DfuBaseService.MIME_TYPE_ZIP); // ensure this is set (e.g. for scripts)
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_FILE_TYPE, DfuBaseService.TYPE_APPLICATION); // set the type to application only
|
||||
if (newAddress != null)
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_DEVICE_ADDRESS, newAddress);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_PART_CURRENT, mProgressInfo.getCurrentPart() + 1);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_PARTS_TOTAL, mProgressInfo.getTotalParts());
|
||||
mService.startService(newIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
|
@ -97,6 +96,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
protected DfuBaseService mService;
|
||||
protected DfuProgressInfo mProgressInfo;
|
||||
protected int mImageSizeInBytes;
|
||||
protected int mInitPacketSizeInBytes;
|
||||
|
||||
private final BroadcastReceiver mBondStateBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
|
@ -247,6 +247,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
mFirmwareStream = firmwareStream;
|
||||
mInitPacketStream = initPacketStream;
|
||||
int size;
|
||||
try {
|
||||
size = initPacketStream.available();
|
||||
} catch (final IOException e) {
|
||||
size = 0;
|
||||
// not possible
|
||||
}
|
||||
mInitPacketSizeInBytes = size;
|
||||
try {
|
||||
size = firmwareStream.available();
|
||||
} catch (final IOException e) {
|
||||
|
|
|
@ -93,7 +93,9 @@ import android.support.annotation.NonNull;
|
|||
}
|
||||
|
||||
public int getAvailableObjectSizeIsBytes() {
|
||||
return maxObjectSizeInBytes - (bytesSent % maxObjectSizeInBytes);
|
||||
final int remainingBytes = imageSizeInBytes - bytesSent;
|
||||
final int remainingChunk = maxObjectSizeInBytes - (bytesSent % maxObjectSizeInBytes);
|
||||
return Math.min(remainingBytes, remainingChunk);
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
|
|
|
@ -28,6 +28,10 @@ import android.bluetooth.BluetoothGattService;
|
|||
/* package */ class DfuServiceProvider {
|
||||
|
||||
/* package */ static BaseDfuImpl getDfuImpl(final DfuBaseService service, final BluetoothGatt gatt) {
|
||||
final BluetoothGattService secureService = gatt.getService(SecureDfuImpl.DFU_SERVICE_UUID);
|
||||
if (secureService != null) {
|
||||
return new SecureDfuImpl(service);
|
||||
}
|
||||
final BluetoothGattService legacyService = gatt.getService(LegacyDfuImpl.DFU_SERVICE_UUID);
|
||||
if (legacyService != null) {
|
||||
return new LegacyDfuImpl(service);
|
||||
|
|
|
@ -46,7 +46,13 @@ import no.nordicsemi.android.error.GattError;
|
|||
import no.nordicsemi.android.error.LegacyDfuError;
|
||||
|
||||
/* package */ class LegacyDfuImpl extends BaseCustomDfuImpl {
|
||||
public static final int DFU_STATUS_SUCCESS = 1;
|
||||
// UUIDs used by the DFU
|
||||
protected static final UUID DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_VERSION = new UUID(0x000015341212EFDEL, 0x1523785FEABCD123L);
|
||||
|
||||
private static final int DFU_STATUS_SUCCESS = 1;
|
||||
// Operation codes and packets
|
||||
private static final int OP_CODE_START_DFU_KEY = 0x01; // 1
|
||||
private static final int OP_CODE_INIT_DFU_PARAMS_KEY = 0x02; // 2
|
||||
|
@ -68,12 +74,6 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
//private static final byte[] OP_CODE_REPORT_RECEIVED_IMAGE_SIZE = new byte[] { OP_CODE_PACKET_REPORT_RECEIVED_IMAGE_SIZE_KEY };
|
||||
private static final byte[] OP_CODE_PACKET_RECEIPT_NOTIF_REQ = new byte[]{OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY, 0x00, 0x00};
|
||||
|
||||
// UUIDs used by the DFU
|
||||
protected static final UUID DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_VERSION = new UUID(0x000015341212EFDEL, 0x1523785FEABCD123L);
|
||||
|
||||
private BluetoothGattCharacteristic mControlPointCharacteristic;
|
||||
private BluetoothGattCharacteristic mPacketCharacteristic;
|
||||
|
||||
|
@ -82,6 +82,8 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
*/
|
||||
private boolean mImageSizeInProgress;
|
||||
|
||||
private final LegacyBluetoothCallback mBluetoothCallback = new LegacyBluetoothCallback();
|
||||
|
||||
protected class LegacyBluetoothCallback extends BaseCustomBluetoothCallback {
|
||||
@Override
|
||||
protected void onPacketCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
|
@ -141,7 +143,7 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
|
||||
@Override
|
||||
protected BaseCustomBluetoothCallback getGattCallback() {
|
||||
return new LegacyBluetoothCallback();
|
||||
return mBluetoothCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -518,16 +520,9 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
logi("Sending the Initialize DFU Parameters START (Op Code = 2, Value = 0)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_START);
|
||||
|
||||
try {
|
||||
byte[] data = new byte[20];
|
||||
int size;
|
||||
while ((size = mInitPacketStream.read(data, 0, data.length)) != -1) {
|
||||
writeInitPacket(mPacketCharacteristic, data, size);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading Init packet file");
|
||||
throw new DfuException("Error while reading Init packet file", DfuBaseService.ERROR_FILE_ERROR);
|
||||
}
|
||||
logi("Sending " + mImageSizeInBytes + " bytes of init packet");
|
||||
writeInitData(mPacketCharacteristic);
|
||||
|
||||
logi("Sending the Initialize DFU Parameters COMPLETE (Op Code = 2, Value = 1)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_INIT_DFU_PARAMS_COMPLETE);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Initialize DFU Parameters completed");
|
||||
|
@ -601,86 +596,13 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
mService.waitUntilDisconnected();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Disconnected by the remote device");
|
||||
|
||||
// We are ready with DFU, the device is disconnected, let's close it and finalize the operation.
|
||||
|
||||
// In the DFU version 0.5, in case the device is bonded, the target device does not send the Service Changed indication after
|
||||
// a jump from bootloader mode to app mode. This issue has been fixed in DFU version 0.6 (SDK 8.0). If the DFU bootloader has been
|
||||
// configured to preserve the bond information we do not need to enforce refreshing services, as it will notify the phone using the
|
||||
// Service Changed indication.
|
||||
final boolean keepBond = intent.getBooleanExtra(DfuBaseService.EXTRA_KEEP_BOND, false);
|
||||
mService.refreshDeviceCache(gatt, version == 5 || !keepBond);
|
||||
|
||||
// Close the device
|
||||
mService.close(gatt);
|
||||
|
||||
// During the update the bonding information on the target device may have been removed.
|
||||
// To create bond with the new application set the EXTRA_RESTORE_BOND extra to true.
|
||||
// In case the bond information is copied to the new application the new bonding is not required.
|
||||
boolean alreadyWaited = false;
|
||||
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
final boolean restoreBond = intent.getBooleanExtra(DfuBaseService.EXTRA_RESTORE_BOND, false);
|
||||
|
||||
if (restoreBond || !keepBond || (fileType & DfuBaseService.TYPE_SOFT_DEVICE) > 0) {
|
||||
// The bond information was lost.
|
||||
removeBond();
|
||||
|
||||
// Give some time for removing the bond information. 300ms was to short, let's set it to 2 seconds just to be sure.
|
||||
mService.waitFor(2000);
|
||||
alreadyWaited = true;
|
||||
}
|
||||
|
||||
if (restoreBond && (fileType & DfuBaseService.TYPE_APPLICATION) > 0) {
|
||||
// Restore pairing when application was updated.
|
||||
createBond();
|
||||
alreadyWaited = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to send PROGRESS_COMPLETED message only when all files has been transmitted.
|
||||
* In case you want to send the Soft Device and/or Bootloader and the Application, the service will be started twice: one to send SD+BL, and the
|
||||
* second time to send the Application only (using the new Bootloader). In the first case we do not send PROGRESS_COMPLETED notification.
|
||||
*/
|
||||
if (mProgressInfo.isLastPart()) {
|
||||
// Delay this event a little bit. Android needs some time to prepare for reconnection.
|
||||
if (!alreadyWaited)
|
||||
mService.waitFor(1400);
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_COMPLETED);
|
||||
} else {
|
||||
/*
|
||||
* In case when the Soft Device has been upgraded, and the application should be send in the following connection, we have to
|
||||
* make sure that we know the address the device is advertising with. Depending on the method used to start the DFU bootloader the first time
|
||||
* the new Bootloader may advertise with the same address or one incremented by 1.
|
||||
* When the buttonless update was used, the bootloader will use the same address as the application. The cached list of services on the Android device
|
||||
* should be cleared thanks to the Service Changed characteristic (the fact that it exists if not bonded, or the Service Changed indication on bonded one).
|
||||
* In case of forced DFU mode (using a button), the Bootloader does not know whether there was the Service Changed characteristic present in the list of
|
||||
* application's services so it must advertise with a different address. The same situation applies when the new Soft Device was uploaded and the old
|
||||
* application has been removed in this process.
|
||||
*
|
||||
* We could have save the fact of jumping as a parameter of the service but it ma be that some Android devices must first scan a device before connecting to it.
|
||||
* It a device with the address+1 has never been detected before the service could have failed on connection.
|
||||
*/
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Scanning for the DFU Bootloader...");
|
||||
final String newAddress = BootloaderScannerFactory.getScanner().searchFor(gatt.getDevice().getAddress());
|
||||
if (newAddress != null)
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "DFU Bootloader found with address " + newAddress);
|
||||
else {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "DFU Bootloader not found. Trying the same address...");
|
||||
}
|
||||
|
||||
/*
|
||||
* The current service instance has uploaded the Soft Device and/or Bootloader.
|
||||
* We need to start another instance that will try to send application only.
|
||||
*/
|
||||
logi("Starting service that will upload application");
|
||||
final Intent newIntent = new Intent();
|
||||
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_FILE_MIME_TYPE, DfuBaseService.MIME_TYPE_ZIP); // ensure this is set (e.g. for scripts)
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_FILE_TYPE, DfuBaseService.TYPE_APPLICATION); // set the type to application only
|
||||
if (newAddress != null)
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_DEVICE_ADDRESS, newAddress);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_PART_CURRENT, mProgressInfo.getCurrentPart() + 1);
|
||||
newIntent.putExtra(DfuBaseService.EXTRA_PARTS_TOTAL, mProgressInfo.getTotalParts());
|
||||
mService.startService(newIntent);
|
||||
}
|
||||
finalize(intent, version == 5);
|
||||
} catch (final UnknownResponseException e) {
|
||||
final int error = DfuBaseService.ERROR_INVALID_RESPONSE;
|
||||
loge(e.getMessage());
|
||||
|
@ -693,7 +615,7 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
} catch (final RemoteDfuException e) {
|
||||
final int error = DfuBaseService.ERROR_REMOTE_MASK | e.getErrorNumber();
|
||||
loge(e.getMessage());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format("Remote DFU error: %s", GattError.parse(error)));
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format("Remote DFU error: %s", LegacyDfuError.parse(error)));
|
||||
|
||||
logi("Sending Reset command (Op Code = 6)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_RESET);
|
||||
|
@ -723,7 +645,7 @@ import no.nordicsemi.android.error.LegacyDfuError;
|
|||
*/
|
||||
private int getStatusCode(final byte[] response, final int request) throws UnknownResponseException {
|
||||
if (response == null || response.length != 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || response[1] != request || response[2] < 1 || response[2] > 6)
|
||||
throw new UnknownResponseException("Invalid response received", response, request);
|
||||
throw new UnknownResponseException("Invalid response received", response, OP_CODE_RESPONSE_CODE_KEY, request);
|
||||
return response[2];
|
||||
}
|
||||
|
||||
|
|
|
@ -23,54 +23,437 @@
|
|||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.RemoteDfuException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.SizeValidationException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UnknownResponseException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
import no.nordicsemi.android.error.GattError;
|
||||
import no.nordicsemi.android.error.SecureDfuError;
|
||||
|
||||
/* package */ class SecureDfuImpl extends BaseCustomDfuImpl {
|
||||
// UUIDs used by the DFU
|
||||
protected static final UUID DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L); // TODO should be changed later to final UUIDs
|
||||
protected static final UUID DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L);
|
||||
protected static final UUID DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L);
|
||||
|
||||
private static final int DFU_STATUS_SUCCESS = 1;
|
||||
|
||||
// Object types
|
||||
private static final int OBJECT_COMMAND = 0x01;
|
||||
private static final int OBJECT_DATA = 0x02;
|
||||
// Operation codes and packets
|
||||
private static final int OP_CODE_CREATE_KEY = 0x01;
|
||||
private static final int OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY = 0x02;
|
||||
private static final int OP_CODE_CALCULATE_CHECKSUM_KEY = 0x03;
|
||||
private static final int OP_CODE_EXECUTE_KEY = 0x04;
|
||||
private static final int OP_CODE_READ_OBJECT_KEY = 0x05;
|
||||
private static final int OP_CODE_READ_OBJECT_INFO_KEY = 0x06;
|
||||
private static final int OP_CODE_RESPONSE_CODE_KEY = 0x60;
|
||||
private static final byte[] OP_CODE_CREATE_COMMAND = new byte[]{OP_CODE_CREATE_KEY, OBJECT_COMMAND, 0x00, 0x00, 0x00, 0x00 };
|
||||
private static final byte[] OP_CODE_CREATE_DATA = new byte[]{OP_CODE_CREATE_KEY, OBJECT_DATA, 0x00, 0x00, 0x00, 0x00};
|
||||
private static final byte[] OP_CODE_PACKET_RECEIPT_NOTIF_REQ = new byte[]{OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY, 0x00, 0x00 /* param PRN uint16 in Little Endian */};
|
||||
private static final byte[] OP_CODE_CALCULATE_CHECKSUM = new byte[]{OP_CODE_CALCULATE_CHECKSUM_KEY};
|
||||
private static final byte[] OP_CODE_EXECUTE = new byte[]{OP_CODE_EXECUTE_KEY};
|
||||
private static final byte[] OP_CODE_READ_COMMAND_OBJECT = new byte[]{OP_CODE_READ_OBJECT_KEY, OBJECT_COMMAND};
|
||||
private static final byte[] OP_CODE_READ_DATA_OBJECT = new byte[]{OP_CODE_READ_OBJECT_KEY, OBJECT_DATA}; // Reads last error message
|
||||
private static final byte[] OP_CODE_READ_INFO = new byte[]{OP_CODE_READ_OBJECT_INFO_KEY, 0x00 /* type */};
|
||||
|
||||
private BluetoothGattCharacteristic mControlPointCharacteristic;
|
||||
private BluetoothGattCharacteristic mPacketCharacteristic;
|
||||
|
||||
private final SecureBluetoothCallback mBluetoothCallback = new SecureBluetoothCallback();
|
||||
|
||||
protected class SecureBluetoothCallback extends BaseCustomBluetoothCallback {
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
||||
if (characteristic.getValue() == null || characteristic.getValue().length < 3) {
|
||||
loge("Empty response: " + parse(characteristic));
|
||||
mError = DfuBaseService.ERROR_INVALID_RESPONSE;
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
|
||||
final int responseType = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
|
||||
|
||||
// The first byte should always be the response code
|
||||
if (responseType == OP_CODE_RESPONSE_CODE_KEY) {
|
||||
final int requestType = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
|
||||
|
||||
switch (requestType) {
|
||||
case OP_CODE_CALCULATE_CHECKSUM_KEY: {
|
||||
mProgressInfo.setBytesReceived(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3));
|
||||
logi("PRN, bytes received: " + mProgressInfo.getBytesReceived()); // TODO remove
|
||||
// TODO check CRC?
|
||||
handlePacketReceiptNotification(gatt, characteristic);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/*
|
||||
* If the DFU target device is in invalid state (f.e. the Init Packet is required but has not been selected), the target will send DFU_STATUS_INVALID_STATE error
|
||||
* for each firmware packet that was send. We are interested may ignore all but the first one.
|
||||
* After obtaining a remote DFU error the OP_CODE_RESET_KEY will be sent.
|
||||
*/
|
||||
if (mRemoteErrorOccurred)
|
||||
break;
|
||||
final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 2);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
mRemoteErrorOccurred = true;
|
||||
|
||||
handleNotification(gatt, characteristic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loge("Invalid response: " + parse(characteristic));
|
||||
mError = DfuBaseService.ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
}
|
||||
|
||||
SecureDfuImpl(final DfuBaseService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredService(final BluetoothGatt gatt) {
|
||||
final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID);
|
||||
return dfuService != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredCharacteristics(final BluetoothGatt gatt) {
|
||||
final BluetoothGattService dfuService = gatt.getService(DFU_SERVICE_UUID);
|
||||
mControlPointCharacteristic = dfuService.getCharacteristic(DFU_CONTROL_POINT_UUID);
|
||||
mPacketCharacteristic = dfuService.getCharacteristic(DFU_PACKET_UUID);
|
||||
return mControlPointCharacteristic != null && mPacketCharacteristic != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseBluetoothGattCallback getGattCallback() {
|
||||
return null;
|
||||
return mBluetoothCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getControlPointCharacteristicUUID() {
|
||||
return null;
|
||||
return DFU_CONTROL_POINT_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getPacketCharacteristicUUID() {
|
||||
return null;
|
||||
return DFU_PACKET_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getDfuServiceUUID() {
|
||||
return null;
|
||||
return DFU_SERVICE_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredService(BluetoothGatt gatt) {
|
||||
return false;
|
||||
public void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_STARTING);
|
||||
|
||||
// Add one second delay to avoid the traffic jam before the DFU mode is enabled
|
||||
// Related:
|
||||
// issue: https://github.com/NordicSemiconductor/Android-DFU-Library/issues/10
|
||||
// pull request: https://github.com/NordicSemiconductor/Android-DFU-Library/pull/12
|
||||
mService.waitFor(1000);
|
||||
// End
|
||||
|
||||
final BluetoothGatt gatt = mGatt;
|
||||
|
||||
// Enable notifications
|
||||
enableCCCD(mControlPointCharacteristic, NOTIFICATIONS);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Notifications enabled");
|
||||
|
||||
// Wait a second here before going further
|
||||
// Related:
|
||||
// pull request: https://github.com/NordicSemiconductor/Android-DFU-Library/pull/11
|
||||
mService.waitFor(1000);
|
||||
// End
|
||||
|
||||
try {
|
||||
byte[] response;
|
||||
int status;
|
||||
ObjectInfo info;
|
||||
ObjectChecksum checksum;
|
||||
|
||||
logi("Sending Read Command Object Info command (Op Code = 6, Type = 1)");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Reading command object info...");
|
||||
info = readObjectInfo(OBJECT_COMMAND);
|
||||
if (mInitPacketSizeInBytes > info.maxSize) {
|
||||
// ignore this. DFU target will send an error if init packet is too large after sending Create command
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredCharacteristics(BluetoothGatt gatt) {
|
||||
return false;
|
||||
logi("Disabling Packet Receipt Notifications (Op Code = 2, Value = 0)");
|
||||
setNumberOfPackets(OP_CODE_PACKET_RECEIPT_NOTIF_REQ, 0);
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_PACKET_RECEIPT_NOTIF_REQ);
|
||||
|
||||
// Read response
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Disabling Packet Receipt Notif failed", status);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif disabled (Op Code = 2, Value = 0)");
|
||||
|
||||
// TODO resume uploading
|
||||
|
||||
// Create the Init object
|
||||
logi("Creating Init packet object (Op Code = 1, Type = 1, Size = " + mInitPacketSizeInBytes + ")");
|
||||
writeCreateRequest(OBJECT_COMMAND, mInitPacketSizeInBytes);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object created");
|
||||
|
||||
// Write Init data to the Packet Characteristic
|
||||
logi("Sending " + mInitPacketSizeInBytes + " bytes of init packet");
|
||||
writeInitData(mPacketCharacteristic);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object sent");
|
||||
|
||||
// Calculate Checksum
|
||||
logi("Sending Calculate Checksum command (Op Code = 3)");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Calculating checksum...");
|
||||
checksum = readChecksum();
|
||||
|
||||
// Execute Init packet
|
||||
logi("Executing init packet (Op Code = 4)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_EXECUTE);
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_EXECUTE_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Executing Init packet failed", status);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Command object executed");
|
||||
|
||||
// Send the number of packets of firmware before receiving a receipt notification
|
||||
final int numberOfPacketsBeforeNotification = mPacketsBeforeNotification;
|
||||
if (numberOfPacketsBeforeNotification > 0) {
|
||||
logi("Sending the number of packets before notifications (Op Code = 2, Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
setNumberOfPackets(OP_CODE_PACKET_RECEIPT_NOTIF_REQ, numberOfPacketsBeforeNotification);
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_PACKET_RECEIPT_NOTIF_REQ);
|
||||
|
||||
// Read response
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Sending the number of packets failed", status);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 2) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performDfu(Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
|
||||
logi("Sending Read Data Object Info command (Op Code = 6, Type = 2)");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Reading data object info...");
|
||||
info = readObjectInfo(OBJECT_DATA);
|
||||
mProgressInfo.setMaxObjectSizeInBytes(info.maxSize);
|
||||
|
||||
// TODO resume?
|
||||
|
||||
// Number of chunks in which the data will be sent
|
||||
final int count = (mImageSizeInBytes + info.maxSize - 1) / info.maxSize;
|
||||
// Chunk iterator
|
||||
int i = 1;
|
||||
while (mProgressInfo.getAvailableObjectSizeIsBytes() > 0) {
|
||||
// Create the Data object
|
||||
logi("Creating Data object (Op Code = 1, Type = 2, Size = " + mProgressInfo.getAvailableObjectSizeIsBytes() + ") (" + i + "/" + count + ")");
|
||||
writeCreateRequest(OBJECT_DATA, mProgressInfo.getAvailableObjectSizeIsBytes());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Data object (" + i + "/" + count + ") created");
|
||||
i++;
|
||||
|
||||
// Send the current object part
|
||||
uploadFirmwareImage(mPacketCharacteristic);
|
||||
|
||||
// Calculate Checksum
|
||||
logi("Sending Calculate Checksum command (Op Code = 3)");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Calculating checksum...");
|
||||
checksum = readChecksum();
|
||||
|
||||
// Execute Init packet
|
||||
logi("Executing data object (Op Code = 4)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_EXECUTE);
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_EXECUTE_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS) {
|
||||
// TODO read Error code
|
||||
throw new RemoteDfuException("Executing data object failed", status);
|
||||
}
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Data object executed");
|
||||
}
|
||||
|
||||
// The device will reset so we don't have to send Disconnect signal.
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_DISCONNECTING);
|
||||
mService.waitUntilDisconnected();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Disconnected by the remote device");
|
||||
|
||||
// We are ready with DFU, the device is disconnected, let's close it and finalize the operation.
|
||||
finalize(intent, false);
|
||||
} catch (final UnknownResponseException e) {
|
||||
final int error = DfuBaseService.ERROR_INVALID_RESPONSE;
|
||||
loge(e.getMessage());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, e.getMessage());
|
||||
|
||||
// logi("Sending Reset command (Op Code = 6)");
|
||||
// writeOpCode(mControlPointCharacteristic, OP_CODE_RESET);
|
||||
// mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Reset request sent");
|
||||
mService.terminateConnection(gatt, error);
|
||||
} catch (final RemoteDfuException e) {
|
||||
// TODO read Error code
|
||||
|
||||
final int error = DfuBaseService.ERROR_REMOTE_MASK | e.getErrorNumber();
|
||||
loge(e.getMessage());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, String.format("Remote DFU error: %s", SecureDfuError.parse(error)));
|
||||
|
||||
// logi("Sending Reset command (Op Code = 6)");
|
||||
// writeOpCode(mControlPointCharacteristic, OP_CODE_RESET);
|
||||
// mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Reset request sent");
|
||||
mService.terminateConnection(gatt, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the response received is valid and returns the status code.
|
||||
*
|
||||
* @param response the response received from the DFU device.
|
||||
* @param request the expected Op Code
|
||||
* @return the status code
|
||||
* @throws UnknownResponseException if response was not valid
|
||||
*/
|
||||
private int getStatusCode(final byte[] response, final int request) throws UnknownResponseException {
|
||||
if (response == null || response.length < 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || response[1] != request || response[2] < SecureDfuError.INVALID_CODE || response[2] > 10)
|
||||
throw new UnknownResponseException("Invalid response received", response, OP_CODE_RESPONSE_CODE_KEY, request);
|
||||
return response[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets number of data packets that will be send before the notification will be received.
|
||||
*
|
||||
* @param data control point data packet
|
||||
* @param value number of packets before receiving notification. If this value is 0, then the notification of packet receipt will be disabled by the DFU target.
|
||||
*/
|
||||
private void setNumberOfPackets(final byte[] data, final int value) {
|
||||
data[1] = (byte) (value & 0xFF);
|
||||
data[2] = (byte) ((value >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object size in correct position of the data array.
|
||||
*
|
||||
* @param data control point data packet
|
||||
* @param value Object size in bytes.
|
||||
*/
|
||||
private void setObjectSize(final byte[] data, final int value) {
|
||||
data[2] = (byte) (value & 0xFF);
|
||||
data[3] = (byte) ((value >> 8) & 0xFF);
|
||||
data[4] = (byte) ((value >> 16) & 0xFF);
|
||||
data[5] = (byte) ((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the operation code to the characteristic. This method is SYNCHRONOUS and wait until the
|
||||
* {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
|
||||
* will be called or the device gets disconnected.
|
||||
* If connection state will change, or an error will occur, an exception will be thrown.
|
||||
*
|
||||
* @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT
|
||||
* @param value the value to write to the characteristic
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
writeOpCode(characteristic, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes Create Object request providing the type and size of the object.
|
||||
* @param type {@link #OBJECT_COMMAND} or {@link #OBJECT_DATA}
|
||||
* @param size size of the object or current part of the object
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
* @throws RemoteDfuException
|
||||
* @throws UnknownResponseException
|
||||
*/
|
||||
private void writeCreateRequest(final int type, final int size) throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException {
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to create object: device disconnected");
|
||||
|
||||
final byte[] data = (type == OBJECT_COMMAND) ? OP_CODE_CREATE_COMMAND : OP_CODE_CREATE_DATA;
|
||||
setObjectSize(data, size);
|
||||
writeOpCode(mControlPointCharacteristic, data);
|
||||
|
||||
final byte[] response = readNotificationResponse();
|
||||
final int status = getStatusCode(response, OP_CODE_CREATE_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Creating Command object failed", status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Object Info for object with given type. The object info contains the max object size, the last offset and CRC32 of the whole object until now.
|
||||
*
|
||||
* @return requested object info
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private ObjectInfo readObjectInfo(final int type) throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException {
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read object info: device disconnected");
|
||||
|
||||
OP_CODE_READ_INFO[1] = (byte) type;
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_READ_INFO);
|
||||
|
||||
final byte[] response = readNotificationResponse();
|
||||
final int status = getStatusCode(response, OP_CODE_READ_OBJECT_INFO_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Reading Object Info failed", status);
|
||||
|
||||
final ObjectInfo info = new ObjectInfo();
|
||||
info.maxSize = mControlPointCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3);
|
||||
info.offset = mControlPointCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3 + 4);
|
||||
info.CRC32 = mControlPointCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3 + 8);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Response received (Op Code = %d, Status = %d, Max size = %d, Offset = %d, CRC = %08X)", response[1], status, info.maxSize, info.offset, info.CRC32));
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Calculate Checksum request. As a response a notification will be sent with current offset and CRC32 of the current object.
|
||||
*
|
||||
* @return requested object info
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private ObjectChecksum readChecksum() throws DeviceDisconnectedException, DfuException, UploadAbortedException, RemoteDfuException, UnknownResponseException {
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read Checksum: device disconnected");
|
||||
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_CALCULATE_CHECKSUM);
|
||||
|
||||
final byte[] response = readNotificationResponse();
|
||||
final int status = getStatusCode(response, OP_CODE_CALCULATE_CHECKSUM_KEY);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Receiving Checksum failed", status);
|
||||
|
||||
final ObjectChecksum info = new ObjectChecksum();
|
||||
info.offset = mControlPointCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3);
|
||||
info.CRC32 = mControlPointCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 3 + 4);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, String.format(Locale.US, "Response received (Op Code = %d, Status = %d, Offset = %d, CRC = %08X)", response[1], status, info.offset, info.CRC32));
|
||||
return info;
|
||||
}
|
||||
|
||||
private class ObjectInfo extends ObjectChecksum {
|
||||
protected int maxSize;
|
||||
}
|
||||
|
||||
private class ObjectChecksum {
|
||||
protected int offset;
|
||||
protected int CRC32;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,18 +27,20 @@ public class UnknownResponseException extends Exception {
|
|||
private static final char[] HEX_ARRAY = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
private final byte[] mResponse;
|
||||
private final int mExpectedReturnCode;
|
||||
private final int mExpectedOpCode;
|
||||
|
||||
public UnknownResponseException(final String message, final byte[] response, final int expectedOpCode) {
|
||||
public UnknownResponseException(final String message, final byte[] response, final int expectedReturnCode, final int expectedOpCode) {
|
||||
super(message);
|
||||
|
||||
mResponse = response != null ? response : new byte[0];
|
||||
mExpectedReturnCode = expectedReturnCode;
|
||||
mExpectedOpCode = expectedOpCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("%s (response: %s, expected: 0x10%02X..)", super.getMessage(), bytesToHex(mResponse, 0, mResponse.length), mExpectedOpCode);
|
||||
return String.format("%s (response: %s, expected: 0x%02X%02X..)", super.getMessage(), bytesToHex(mResponse, 0, mResponse.length), mExpectedReturnCode, mExpectedOpCode);
|
||||
}
|
||||
|
||||
public static String bytesToHex(final byte[] bytes, final int start, final int length) {
|
||||
|
|
|
@ -169,21 +169,11 @@ public class GattError {
|
|||
case DfuBaseService.ERROR_INIT_PACKET_REQUIRED:
|
||||
return "INIT PACKET REQUIRED";
|
||||
default:
|
||||
// Deprecated: use Legacy or SecureDfuError parser
|
||||
if ((DfuBaseService.ERROR_REMOTE_MASK & error) > 0) {
|
||||
switch (error & (~DfuBaseService.ERROR_REMOTE_MASK)) {
|
||||
case LegacyDfuError.INVALID_STATE:
|
||||
return "REMOTE DFU INVALID STATE";
|
||||
case LegacyDfuError.NOT_SUPPORTED:
|
||||
return "REMOTE DFU NOT SUPPORTED";
|
||||
case LegacyDfuError.DATA_SIZE_EXCEEDS_LIMIT:
|
||||
return "REMOTE DFU DATA SIZE EXCEEDS LIMIT";
|
||||
case LegacyDfuError.CRC_ERROR:
|
||||
return "REMOTE DFU INVALID CRC ERROR";
|
||||
case LegacyDfuError.OPERATION_FAILED:
|
||||
return "REMOTE DFU OPERATION FAILED";
|
||||
return LegacyDfuError.parse(error);
|
||||
}
|
||||
}
|
||||
return "UNKNOWN (" + error + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
package no.nordicsemi.android.error;
|
||||
|
||||
import no.nordicsemi.android.dfu.DfuBaseService;
|
||||
|
||||
public final class LegacyDfuError {
|
||||
// DFU status values
|
||||
public static final int INVALID_STATE = 2;
|
||||
|
@ -29,4 +31,21 @@ public final class LegacyDfuError {
|
|||
public static final int DATA_SIZE_EXCEEDS_LIMIT = 4;
|
||||
public static final int CRC_ERROR = 5;
|
||||
public static final int OPERATION_FAILED = 6;
|
||||
|
||||
public static String parse(final int error) {
|
||||
switch (error & (~DfuBaseService.ERROR_REMOTE_MASK)) {
|
||||
case INVALID_STATE:
|
||||
return "REMOTE DFU INVALID STATE";
|
||||
case NOT_SUPPORTED:
|
||||
return "REMOTE DFU NOT SUPPORTED";
|
||||
case DATA_SIZE_EXCEEDS_LIMIT:
|
||||
return "REMOTE DFU DATA SIZE EXCEEDS LIMIT";
|
||||
case CRC_ERROR:
|
||||
return "REMOTE DFU INVALID CRC ERROR";
|
||||
case OPERATION_FAILED:
|
||||
return "REMOTE DFU OPERATION FAILED";
|
||||
default:
|
||||
return "UNKNOWN (" + error + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* Copyright (c) 2016, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
************************************************************************************************************************************************/
|
||||
|
||||
package no.nordicsemi.android.error;
|
||||
|
||||
import no.nordicsemi.android.dfu.DfuBaseService;
|
||||
|
||||
public final class SecureDfuError {
|
||||
// DFU status values
|
||||
public static final int INVALID_CODE = 0;
|
||||
public static final int OP_CODE_NOT_SUPPORTED = 2;
|
||||
public static final int INVALID_PARAM = 3;
|
||||
public static final int INSUFFICIENT_RESOURCES = 4;
|
||||
public static final int INVALID_OBJECT = 5;
|
||||
public static final int SIGNATURE_DOES_NOT_MATCH = 6;
|
||||
public static final int UNSUPPORTED_TYPE = 7;
|
||||
public static final int OPERATION_FAILED = 10; // 0xA
|
||||
|
||||
public static String parse(final int error) {
|
||||
switch (error & (~DfuBaseService.ERROR_REMOTE_MASK)) {
|
||||
case INVALID_CODE:
|
||||
return "REMOTE DFU INVALID CODE";
|
||||
case OP_CODE_NOT_SUPPORTED:
|
||||
return "REMOTE DFU OP CODE NOT SUPPORTED";
|
||||
case INVALID_PARAM:
|
||||
return "REMOTE DFU INVALID PARAM";
|
||||
case INSUFFICIENT_RESOURCES:
|
||||
return "REMOTE DFU INSUFFICIENT RESOURCES";
|
||||
case INVALID_OBJECT:
|
||||
return "REMOTE DFU INVALID OBJECT";
|
||||
case SIGNATURE_DOES_NOT_MATCH:
|
||||
return "REMOTE DFU SIGNATURE DOES NOT MATCH";
|
||||
case UNSUPPORTED_TYPE:
|
||||
return "REMOTE DFU UNSUPPORTED TYPE";
|
||||
case OPERATION_FAILED:
|
||||
return "REMOTE DFU OPERATION FAILED";
|
||||
default:
|
||||
return "UNKNOWN (" + error + ")";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue