Merge branch 'master' into secure_dfu
Conflicts: dfu/build.gradle dfu/dfu.iml
This commit is contained in:
commit
a98ac862f0
|
@ -42,6 +42,7 @@ ext {
|
|||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 23
|
||||
|
@ -60,7 +61,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:support-v4:23.3.0'
|
||||
compile 'com.android.support:support-v4:23.4.0'
|
||||
compile 'com.google.code.gson:gson:2.5'
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.3.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||
|
@ -102,8 +102,8 @@
|
|||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.3.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="gson-2.5" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -24,9 +24,10 @@ package no.nordicsemi.android.dfu;
|
|||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
|
@ -38,11 +39,11 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
/**
|
||||
* Flag indicating whether the init packet has been already transferred or not.
|
||||
*/
|
||||
private boolean mInitPacketSent;
|
||||
private boolean mInitPacketInProgress;
|
||||
/**
|
||||
* Flag indicating whether the firmware is being transmitted or not.
|
||||
*/
|
||||
private boolean mFirmwareUploadStarted;
|
||||
private boolean mFirmwareUploadInProgress;
|
||||
/**
|
||||
* The number of packets of firmware data to be send before receiving a new Packets receipt notification. 0 disables the packets notifications.
|
||||
*/
|
||||
|
@ -91,14 +92,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* - do nothing, because we have to wait for the notification to confirm the data received
|
||||
*/
|
||||
if (characteristic.getUuid().equals(getPacketCharacteristicUUID())) {
|
||||
if (mInitPacketSent) {
|
||||
if (mInitPacketInProgress) {
|
||||
// We've got confirmation that the init packet was sent
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic));
|
||||
mInitPacketSent = false;
|
||||
} else if (mFirmwareUploadStarted) {
|
||||
mInitPacketInProgress = false;
|
||||
} else if (mFirmwareUploadInProgress) {
|
||||
// If the PACKET characteristic was written with image data, update counters
|
||||
mProgressInfo.addBytesSent(characteristic.getValue().length);
|
||||
mService.updateProgressNotification(mProgressInfo);
|
||||
mPacketsSentSinceNotification++;
|
||||
|
||||
// If a packet receipt notification is expected, or the last packet was sent, do nothing. There onCharacteristicChanged listener will catch either
|
||||
|
@ -110,7 +110,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
// 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) {
|
||||
mFirmwareUploadStarted = false;
|
||||
mFirmwareUploadInProgress = false;
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ 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 (!mFirmwareUploadStarted)
|
||||
if (!mFirmwareUploadInProgress)
|
||||
return;
|
||||
|
||||
final BluetoothGattCharacteristic packetCharacteristic = gatt.getService(getDfuServiceUUID()).getCharacteristic(getPacketCharacteristicUUID());
|
||||
|
@ -200,13 +200,28 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
protected void handleNotification(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Notification received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic));
|
||||
mReceivedData = characteristic.getValue();
|
||||
mFirmwareUploadStarted = false;
|
||||
mFirmwareUploadInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
BaseCustomDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream);
|
||||
mPacketsBeforeNotification = packetsBeforeNotification;
|
||||
BaseCustomDfuImpl(final DfuBaseService service) {
|
||||
super(service);
|
||||
|
||||
// Read preferences
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(service);
|
||||
final boolean packetReceiptNotificationEnabled = preferences.getBoolean(DfuSettingsConstants.SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED, true);
|
||||
String value = preferences.getString(DfuSettingsConstants.SETTINGS_NUMBER_OF_PACKETS, String.valueOf(DfuSettingsConstants.SETTINGS_NUMBER_OF_PACKETS_DEFAULT));
|
||||
int numberOfPackets;
|
||||
try {
|
||||
numberOfPackets = Integer.parseInt(value);
|
||||
if (numberOfPackets < 0 || numberOfPackets > 0xFFFF)
|
||||
numberOfPackets = DfuSettingsConstants.SETTINGS_NUMBER_OF_PACKETS_DEFAULT;
|
||||
} catch (final NumberFormatException e) {
|
||||
numberOfPackets = DfuSettingsConstants.SETTINGS_NUMBER_OF_PACKETS_DEFAULT;
|
||||
}
|
||||
if (!packetReceiptNotificationEnabled)
|
||||
numberOfPackets = 0;
|
||||
mPacketsBeforeNotification = numberOfPackets;
|
||||
}
|
||||
|
||||
protected abstract UUID getControlPointCharacteristicUUID();
|
||||
|
@ -217,9 +232,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
/**
|
||||
* 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 connection state will change from {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
* will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @param characteristic the characteristic to write to. Should be the DFU PACKET
|
||||
* @param buffer the init packet as a byte array. This must be shorter or equal to 20 bytes (TODO check this restriction).
|
||||
* @param size the init packet size
|
||||
|
@ -227,7 +241,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeInitPacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException,
|
||||
protected void writeInitPacket(final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
byte[] locBuffer = buffer;
|
||||
if (buffer.length != size) {
|
||||
|
@ -236,19 +250,19 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mInitPacketSent = true;
|
||||
mInitPacketInProgress = true;
|
||||
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(locBuffer);
|
||||
logi("Sending init packet (Value = " + parse(locBuffer) + ")");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Writing to characteristic " + characteristic.getUuid());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
mGatt.writeCharacteristic(characteristic);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mInitPacketSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((mInitPacketInProgress && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -258,32 +272,31 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to write Init DFU Parameters", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to write Init DFU Parameters", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to write Init DFU Parameters: device disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts sending the data. This method is SYNCHRONOUS and terminates when the whole file will be uploaded or the connection status will change from {@link #STATE_CONNECTED_AND_READY}. If
|
||||
* connection state will change, or an error will occur, an exception will be thrown.
|
||||
* Starts sending the data. This method is SYNCHRONOUS and terminates when the whole file will be uploaded or the device get disconnected.
|
||||
* If connection state will change, or an error will occur, an exception will be thrown.
|
||||
*
|
||||
* @param gatt the GATT device (DFU target)
|
||||
* @param packetCharacteristic the characteristic to write file content to. Must be the DFU PACKET
|
||||
* @return The response value received from notification with Op Code = 3 when all bytes will be uploaded successfully.
|
||||
* @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of the transmission. The error core will be saved in {@link #mConnectionState}.
|
||||
* @throws DeviceDisconnectedException Thrown when the device will disconnect in the middle of the transmission.
|
||||
* @throws DfuException Thrown if DFU error occur
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected byte[] uploadFirmwareImage(final BluetoothGatt gatt, final BluetoothGattCharacteristic packetCharacteristic) throws DeviceDisconnectedException,
|
||||
protected byte[] uploadFirmwareImage(final BluetoothGattCharacteristic packetCharacteristic) throws DeviceDisconnectedException,
|
||||
DfuException, UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mFirmwareUploadStarted = true;
|
||||
mFirmwareUploadInProgress = true;
|
||||
|
||||
final byte[] buffer = mBuffer;
|
||||
try {
|
||||
final int size = mFirmwareStream.read(buffer);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Sending firmware to characteristic " + packetCharacteristic.getUuid() + "...");
|
||||
writePacket(gatt, packetCharacteristic, buffer, size);
|
||||
writePacket(mGatt, packetCharacteristic, buffer, size);
|
||||
} catch (final HexFileValidationException e) {
|
||||
throw new DfuException("HEX file not valid", DfuBaseService.ERROR_FILE_INVALID);
|
||||
} catch (final IOException e) {
|
||||
|
@ -292,7 +305,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mFirmwareUploadStarted && mReceivedData == null && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((mFirmwareUploadInProgress && mReceivedData == null && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -303,8 +316,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Uploading Firmware Image failed", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Uploading Firmware Image failed: device disconnected", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Uploading Firmware Image failed: device disconnected");
|
||||
|
||||
return mReceivedData;
|
||||
}
|
||||
|
@ -312,7 +325,6 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
/**
|
||||
* Writes the buffer to the characteristic. The maximum size of the buffer is 20 bytes. This method is ASYNCHRONOUS and returns immediately after adding the data to TX queue.
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @param characteristic the characteristic to write to. Should be the DFU PACKET
|
||||
* @param buffer the buffer with 1-20 bytes
|
||||
* @param size the number of bytes from the buffer to send
|
||||
|
|
|
@ -44,44 +44,38 @@ 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.HexFileValidationException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
|
||||
/* package */ abstract class BaseDfuImpl {
|
||||
/* package */ abstract class BaseDfuImpl implements DfuService {
|
||||
public static final String TAG = "DfuImpl";
|
||||
|
||||
protected static final UUID GENERIC_ATTRIBUTE_SERVICE_UUID = new UUID(0x0000180100001000L, 0x800000805F9B34FBL);
|
||||
protected static final UUID SERVICE_CHANGED_UUID = new UUID(0x00002A0500001000L, 0x800000805F9B34FBL);
|
||||
protected static final UUID CLIENT_CHARACTERISTIC_CONFIG = new UUID(0x0000290200001000L, 0x800000805f9b34fbL);
|
||||
protected static final int NOTIFICATIONS = 1;
|
||||
protected static final int INDICATIONS = 2;
|
||||
|
||||
private static final int NOTIFICATIONS = 1;
|
||||
private static final int INDICATIONS = 2;
|
||||
protected static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
protected static final int MAX_PACKET_SIZE = 20; // the maximum number of bytes in one packet is 20. May be less.
|
||||
|
||||
/**
|
||||
* The current connection state. If its value is > 0 than an error has occurred. Error number is a negative value of mConnectionState
|
||||
*/
|
||||
protected int mConnectionState;
|
||||
protected final static int STATE_DISCONNECTED = 0;
|
||||
protected final static int STATE_CONNECTING = -1;
|
||||
protected final static int STATE_CONNECTED = -2;
|
||||
protected final static int STATE_CONNECTED_AND_READY = -3; // indicates that services were discovered
|
||||
protected final static int STATE_DISCONNECTING = -4;
|
||||
protected final static int STATE_CLOSED = -5;
|
||||
|
||||
/**
|
||||
* Lock used in synchronization purposes
|
||||
*/
|
||||
protected final Object mLock = new Object();
|
||||
|
||||
protected final InputStream mFirmwareStream;
|
||||
protected final InputStream mInitPacketStream;
|
||||
protected InputStream mFirmwareStream;
|
||||
protected InputStream mInitPacketStream;
|
||||
|
||||
/** The target GATT device. */
|
||||
protected BluetoothGatt mGatt;
|
||||
/** The firmware type. See TYPE_* constants. */
|
||||
protected int mFileType;
|
||||
/** Flag set to true if sending was paused. */
|
||||
protected boolean mPaused;
|
||||
/** Flag set to true if sending was aborted. */
|
||||
protected boolean mAborted;
|
||||
/** Flag indicating whether the device is still connected. */
|
||||
protected boolean mConnected;
|
||||
/**
|
||||
* Flag indicating whether the request was completed or not
|
||||
*/
|
||||
|
@ -100,36 +94,16 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
*/
|
||||
protected byte[] mReceivedData = null;
|
||||
protected final byte[] mBuffer = new byte[MAX_PACKET_SIZE];
|
||||
/**
|
||||
* The target device address
|
||||
*/
|
||||
private String mDeviceAddress;
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
protected DfuBaseService mService;
|
||||
protected final DfuProgressInfo mProgressInfo;
|
||||
|
||||
private final BroadcastReceiver mConnectionStateBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
// Obtain the device and check it this is the one that we are connected to
|
||||
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (!device.getAddress().equals(mDeviceAddress))
|
||||
return;
|
||||
|
||||
final String action = intent.getAction();
|
||||
|
||||
logi("Action received: " + action);
|
||||
mConnectionState = STATE_DISCONNECTED;
|
||||
notifyLock();
|
||||
}
|
||||
};
|
||||
protected DfuProgressInfo mProgressInfo;
|
||||
protected int mImageSizeInBytes;
|
||||
|
||||
private final BroadcastReceiver mBondStateBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
// Obtain the device and check it this is the one that we are connected to
|
||||
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (!device.getAddress().equals(mDeviceAddress))
|
||||
if (!device.equals(mGatt.getDevice()))
|
||||
return;
|
||||
|
||||
// Read bond state
|
||||
|
@ -143,80 +117,11 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
};
|
||||
|
||||
protected class BaseBluetoothGattCallback extends BluetoothGattCallback {
|
||||
@Override
|
||||
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
|
||||
// Check whether an error occurred
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (newState == BluetoothGatt.STATE_CONNECTED) {
|
||||
logi("Connected to GATT server");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Connected to " + mDeviceAddress);
|
||||
mConnectionState = STATE_CONNECTED;
|
||||
// The Implementation object is created depending on device services, so after the device is connected and services were scanned.
|
||||
// public void onConnected() { }
|
||||
|
||||
/*
|
||||
* The onConnectionStateChange callback is called just after establishing connection and before sending Encryption Request BLE event in case of a paired device.
|
||||
* In that case and when the Service Changed CCCD is enabled we will get the indication after initializing the encryption, about 1600 milliseconds later.
|
||||
* If we discover services right after connecting, the onServicesDiscovered callback will be called immediately, before receiving the indication and the following
|
||||
* service discovery and we may end up with old, application's services instead.
|
||||
*
|
||||
* This is to support the buttonless switch from application to bootloader mode where the DFU bootloader notifies the master about service change.
|
||||
* Tested on Nexus 4 (Android 4.4.4 and 5), Nexus 5 (Android 5), Samsung Note 2 (Android 4.4.2). The time after connection to end of service discovery is about 1.6s
|
||||
* on Samsung Note 2.
|
||||
*
|
||||
* NOTE: We are doing this to avoid the hack with calling the hidden gatt.refresh() method, at least for bonded devices.
|
||||
*/
|
||||
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
logd("Waiting 1600 ms for a possible Service Changed indication...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "wait(1600)");
|
||||
wait(1600);
|
||||
|
||||
// After 1.6s the services are already discovered so the following gatt.discoverServices() finishes almost immediately.
|
||||
|
||||
// NOTE: This also works with shorted waiting time. The gatt.discoverServices() must be called after the indication is received which is
|
||||
// about 600ms after establishing connection. Values 600 - 1600ms should be OK.
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to discover services after successful connection.
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Discovering services...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.discoverServices()");
|
||||
final boolean success = gatt.discoverServices();
|
||||
logi("Attempting to start service discovery... " + (success ? "succeed" : "failed"));
|
||||
|
||||
if (!success) {
|
||||
mError = DfuBaseService.ERROR_SERVICE_DISCOVERY_NOT_STARTED;
|
||||
} else {
|
||||
// Just return here, lock will be notified when service discovery finishes
|
||||
return;
|
||||
}
|
||||
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
|
||||
logi("Disconnected from GATT server");
|
||||
mPaused = false;
|
||||
mConnectionState = STATE_DISCONNECTED;
|
||||
}
|
||||
} else {
|
||||
loge("Connection state change error: " + status + " newState: " + newState);
|
||||
if (newState == BluetoothGatt.STATE_DISCONNECTED)
|
||||
mConnectionState = STATE_DISCONNECTED;
|
||||
mPaused = false;
|
||||
mError = DfuBaseService.ERROR_CONNECTION_STATE_MASK | status;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
logi("Services discovered");
|
||||
mConnectionState = STATE_CONNECTED_AND_READY;
|
||||
} else {
|
||||
loge("Service discovery error: " + status);
|
||||
mError = DfuBaseService.ERROR_CONNECTION_MASK | status;
|
||||
}
|
||||
public void onDisconnected() {
|
||||
mConnected = false;
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
|
@ -240,6 +145,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Read Response received from descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor));
|
||||
if (SERVICE_CHANGED_UUID.equals(descriptor.getCharacteristic().getUuid())) {
|
||||
// We have enabled indications for the Service Changed characteristic
|
||||
mRequestCompleted = true;
|
||||
|
@ -259,6 +165,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to descr." + descriptor.getCharacteristic().getUuid() + ", value (0x): " + parse(descriptor));
|
||||
if (SERVICE_CHANGED_UUID.equals(descriptor.getCharacteristic().getUuid())) {
|
||||
// We have enabled indications for the Service Changed characteristic
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Indications enabled for " + descriptor.getCharacteristic().getUuid());
|
||||
|
@ -274,9 +181,16 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
notifyLock();
|
||||
}
|
||||
|
||||
// This method is repeated here and in the service class for performance matters.
|
||||
|
||||
protected String parse(final BluetoothGattCharacteristic characteristic) {
|
||||
final byte[] data = characteristic.getValue();
|
||||
return parse(characteristic.getValue());
|
||||
}
|
||||
|
||||
protected String parse(final BluetoothGattDescriptor descriptor) {
|
||||
return parse(descriptor.getValue());
|
||||
}
|
||||
|
||||
private String parse(final byte[] data) {
|
||||
if (data == null)
|
||||
return "";
|
||||
final int length = data.length;
|
||||
|
@ -295,8 +209,41 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
};
|
||||
|
||||
/* package */ BaseDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream) {
|
||||
/* package */ BaseDfuImpl(final DfuBaseService service) {
|
||||
mService = service;
|
||||
mConnected = true; // the device is connected when impl object it created
|
||||
|
||||
final IntentFilter bondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
service.registerReceiver(mBondStateBroadcastReceiver, bondFilter);
|
||||
}
|
||||
|
||||
/* package */ void onDestroy() {
|
||||
mService.unregisterReceiver(mBondStateBroadcastReceiver);
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
mPaused = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
mPaused = false;
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
mPaused = false;
|
||||
mAborted = true;
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
|
||||
mGatt = gatt;
|
||||
mFileType = fileType;
|
||||
mFirmwareStream = firmwareStream;
|
||||
mInitPacketStream = initPacketStream;
|
||||
int size;
|
||||
|
@ -306,34 +253,75 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
size = 0;
|
||||
// not possible
|
||||
}
|
||||
mProgressInfo = new DfuProgressInfo(size);
|
||||
mImageSizeInBytes = size;
|
||||
|
||||
final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
service.registerReceiver(mConnectionStateBroadcastReceiver, filter);
|
||||
final int currentPart = intent.getIntExtra(DfuBaseService.EXTRA_PART_CURRENT, 1);
|
||||
final int totalParts = intent.getIntExtra(DfuBaseService.EXTRA_PARTS_TOTAL, 1);
|
||||
mProgressInfo = mService.mProgressInfo.init(size, currentPart, totalParts);
|
||||
|
||||
final IntentFilter bondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
service.registerReceiver(mBondStateBroadcastReceiver, bondFilter);
|
||||
}
|
||||
// If we are bonded we may want to enable Service Changed characteristic indications.
|
||||
// Note: This feature will be introduced in the SDK 8.0 as this is the proper way to refresh attribute list on the phone.
|
||||
|
||||
/* package */ void unregister() {
|
||||
mService.unregisterReceiver(mConnectionStateBroadcastReceiver);
|
||||
mService.unregisterReceiver(mBondStateBroadcastReceiver);
|
||||
mService = null;
|
||||
}
|
||||
// This has been fixed on Android 6 (?). Now the Android enables Service Changed indications automatically during bonding.
|
||||
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
final BluetoothGattService genericAttributeService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE_UUID);
|
||||
if (genericAttributeService != null) {
|
||||
final BluetoothGattCharacteristic serviceChangedCharacteristic = genericAttributeService.getCharacteristic(SERVICE_CHANGED_UUID);
|
||||
if (serviceChangedCharacteristic != null) {
|
||||
// Let's read the current value of the Service Changed CCCD
|
||||
final boolean serviceChangedIndicationsEnabled = isServiceChangedCCCDEnabled();
|
||||
|
||||
/* package */ void pause() {
|
||||
mPaused = true;
|
||||
}
|
||||
if (!serviceChangedIndicationsEnabled) {
|
||||
enableCCCD(serviceChangedCharacteristic, INDICATIONS);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Service Changed indications enabled");
|
||||
|
||||
/* package */ void resume() {
|
||||
mPaused = false;
|
||||
notifyLock();
|
||||
}
|
||||
/*
|
||||
* NOTE: The DFU Bootloader from SDK 8.0 (v0.6 and 0.5) has the following issue:
|
||||
*
|
||||
* When the central device (phone) connects to a bonded device (or connects and bonds) which supports the Service Changed characteristic,
|
||||
* but does not have the Service Changed indications enabled, the phone must enable them, disconnect and reconnect before starting the
|
||||
* DFU operation. This is because the current version of the Soft Device saves the ATT table on the DISCONNECTED event.
|
||||
* Sending the "jump to Bootloader" command (0x01-04) will cause the disconnect followed be a reset. The Soft Device does not
|
||||
* have time to store the ATT table on Flash memory before the reset.
|
||||
*
|
||||
* This applies only if:
|
||||
* - the device was bonded before an upgrade,
|
||||
* - the Application or the Bootloader is upgraded (upgrade of the Soft Device will erase the bond information anyway),
|
||||
* - Application:
|
||||
* if the DFU Bootloader has been modified and compiled to preserve the LTK and the ATT table after application upgrade (at least 2 pages)
|
||||
* See: \Nordic\nrf51\components\libraries\bootloader_dfu\dfu_types.h, line 56(?):
|
||||
* #define DFU_APP_DATA_RESERVED 0x0000 -> 0x0800+ //< Size of Application Data that must be preserved between application updates...
|
||||
* - Bootloader:
|
||||
* The Application memory should not be removed when the Bootloader is upgraded, so the Bootloader configuration does not matter.
|
||||
*
|
||||
* If the bond information is not to be preserved between the old and new applications, we may skip this disconnect/reconnect process.
|
||||
* The DFU Bootloader will send the SD indication anyway when we will just continue here, as the information whether it should send it or not it is not being
|
||||
* read from the application's ATT table, but rather passed as an argument of the "reboot to bootloader" method.
|
||||
*/
|
||||
final boolean keepBond = intent.getBooleanExtra(DfuBaseService.EXTRA_KEEP_BOND, false);
|
||||
if (keepBond && (fileType & DfuBaseService.TYPE_SOFT_DEVICE) == 0) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Restarting service...");
|
||||
|
||||
/* package */ void abort() {
|
||||
mPaused = false;
|
||||
mAborted = true;
|
||||
notifyLock();
|
||||
// Disconnect
|
||||
mService.disconnect(gatt);
|
||||
|
||||
// Close the device
|
||||
mService.close(gatt);
|
||||
|
||||
logi("Restarting service");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Restarting service...");
|
||||
final Intent newIntent = new Intent();
|
||||
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
|
||||
mService.startService(newIntent);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Service Changed indications enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void notifyLock() {
|
||||
|
@ -357,153 +345,23 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
/**
|
||||
* Returns the final BluetoothGattCallback instance, depending on the implementation.
|
||||
*/
|
||||
protected abstract BluetoothGattCallback getGattCallback();
|
||||
|
||||
/**
|
||||
* Connects to the BLE device with given address. This method is SYNCHRONOUS, it wait until the connection status change from {@link #STATE_CONNECTING} to {@link #STATE_CONNECTED_AND_READY} or an
|
||||
* error occurs. This method returns <code>null</code> if Bluetooth adapter is disabled.
|
||||
*
|
||||
* @param address the device address
|
||||
* @return the GATT device or <code>null</code> if Bluetooth adapter is disabled.
|
||||
*/
|
||||
protected BluetoothGatt connect(final String address) {
|
||||
if (!mBluetoothAdapter.isEnabled())
|
||||
return null;
|
||||
|
||||
mConnectionState = STATE_CONNECTING;
|
||||
|
||||
logi("Connecting to the device...");
|
||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt = device.connectGatt(autoConnect = false)");
|
||||
final BluetoothGatt gatt = device.connectGatt(mService, false, getGattCallback());
|
||||
|
||||
// We have to wait until the device is connected and services are discovered
|
||||
// Connection error may occur as well.
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (((mConnectionState == STATE_CONNECTING || mConnectionState == STATE_CONNECTED) && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
return gatt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the device and cleans local variables in case of error. This method is SYNCHRONOUS and wait until the disconnecting process will be completed.
|
||||
*
|
||||
* @param gatt the GATT device to be disconnected
|
||||
* @param error error number
|
||||
*/
|
||||
protected void terminateConnection(final BluetoothGatt gatt, final int error) {
|
||||
if (mConnectionState != STATE_DISCONNECTED) {
|
||||
// Disconnect from the device
|
||||
disconnect(gatt);
|
||||
}
|
||||
|
||||
// Close the device
|
||||
refreshDeviceCache(gatt, false); // This should be set to true when DFU Version is 0.5 or lower
|
||||
close(gatt);
|
||||
mService.updateProgressNotification(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the device. This is SYNCHRONOUS method and waits until the callback returns new state. Terminates immediately if device is already disconnected. Do not call this method
|
||||
* directly, use {@link #terminateConnection(android.bluetooth.BluetoothGatt, int)} instead.
|
||||
*
|
||||
* @param gatt the GATT device that has to be disconnected
|
||||
*/
|
||||
protected void disconnect(final BluetoothGatt gatt) {
|
||||
if (mConnectionState == STATE_DISCONNECTED)
|
||||
return;
|
||||
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Disconnecting...");
|
||||
mService.updateProgressNotification(mProgressInfo.setProgress(DfuBaseService.PROGRESS_DISCONNECTING));
|
||||
|
||||
mConnectionState = STATE_DISCONNECTING;
|
||||
|
||||
logi("Disconnecting from the device...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.disconnect()");
|
||||
gatt.disconnect();
|
||||
|
||||
// We have to wait until device gets disconnected or an error occur
|
||||
waitUntilDisconnected();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the connection state will change to {@link #STATE_DISCONNECTED} or until an error occurs.
|
||||
*/
|
||||
protected void waitUntilDisconnected() {
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (mConnectionState != STATE_DISCONNECTED && mError == 0)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the GATT device and cleans up.
|
||||
*
|
||||
* @param gatt the GATT device to be closed
|
||||
*/
|
||||
protected void close(final BluetoothGatt gatt) {
|
||||
logi("Cleaning up...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.close()");
|
||||
gatt.close();
|
||||
mConnectionState = STATE_CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the device cache. After uploading new firmware the DFU target will have other services than before.
|
||||
*
|
||||
* @param gatt the GATT device to be refreshed
|
||||
* @param force <code>true</code> to force the refresh
|
||||
*/
|
||||
protected void refreshDeviceCache(final BluetoothGatt gatt, final boolean force) {
|
||||
/*
|
||||
* If the device is bonded this is up to the Service Changed characteristic to notify Android that the services has changed.
|
||||
* There is no need for this trick in that case.
|
||||
* If not bonded, the Android should not keep the services cached when the Service Changed characteristic is present in the target device database.
|
||||
* However, due to the Android bug (still exists in Android 5.0.1), it is keeping them anyway and the only way to clear services is by using this hidden refresh method.
|
||||
*/
|
||||
if (force || gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.refresh() (hidden)");
|
||||
/*
|
||||
* There is a refresh() method in BluetoothGatt class but for now it's hidden. We will call it using reflections.
|
||||
*/
|
||||
try {
|
||||
final Method refresh = gatt.getClass().getMethod("refresh");
|
||||
if (refresh != null) {
|
||||
final boolean success = (Boolean) refresh.invoke(gatt);
|
||||
logi("Refreshing result: " + success);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
loge("An exception occurred while refreshing device", e);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Refreshing failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
protected abstract BaseBluetoothGattCallback getGattCallback();
|
||||
|
||||
/**
|
||||
* Enables or disables the notifications for given characteristic. This method is SYNCHRONOUS and wait until the
|
||||
* {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int)} will be called or the connection state will change from {@link #STATE_CONNECTED_AND_READY}. If
|
||||
* connection state will change, or an error will occur, an exception will be thrown.
|
||||
* {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, 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 gatt the GATT device
|
||||
* @param characteristic the characteristic to enable or disable notifications for
|
||||
* @param type {@link #NOTIFICATIONS} or {@link #INDICATIONS}
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void enableCCCD(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int type) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
protected void enableCCCD(final BluetoothGattCharacteristic characteristic, final int type) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
final BluetoothGatt gatt = mGatt;
|
||||
final String debugString = type == NOTIFICATIONS ? "notifications" : "indications";
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to set " + debugString + " state", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected");
|
||||
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
|
@ -527,7 +385,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
// We have to wait until device receives a response or an error occur
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((!cccdEnabled && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused) {
|
||||
while ((!cccdEnabled && mConnected && mError == 0 && !mAborted) || mPaused) {
|
||||
mLock.wait();
|
||||
// Check the value of the CCCD
|
||||
cccdEnabled = descriptor.getValue() != null && descriptor.getValue().length == 2 && descriptor.getValue()[0] > 0 && descriptor.getValue()[1] == 0;
|
||||
|
@ -540,23 +398,24 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to set " + debugString + " state", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to set " + debugString + " state", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to set " + debugString + " state: device disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of the Service Changed Client Characteristic Configuration descriptor (CCCD).
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @return <code>true</code> if Service Changed CCCD is enabled and set to INDICATE
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private boolean isServiceChangedCCCDEnabled(final BluetoothGatt gatt) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD", mConnectionState);
|
||||
private boolean isServiceChangedCCCDEnabled() throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected");
|
||||
|
||||
// If the Service Changed characteristic or the CCCD is not available we return false.
|
||||
final BluetoothGatt gatt = mGatt;
|
||||
final BluetoothGattService genericAttributeService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE_UUID);
|
||||
if (genericAttributeService == null)
|
||||
return false;
|
||||
|
@ -574,13 +433,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
logi("Reading Service Changed CCCD value...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Reading Service Changed CCCD value...");
|
||||
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.readDescriptor(" + descriptor.getUuid() + ")");
|
||||
gatt.readDescriptor(descriptor);
|
||||
|
||||
// We have to wait until device receives a response or an error occur
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((!mRequestCompleted && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((!mRequestCompleted && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -590,8 +449,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to read Service Changed CCCD", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read Service Changed CCCD: device disconnected");
|
||||
|
||||
// Return true if the CCCD value is
|
||||
return descriptor.getValue() != null && descriptor.getValue().length == 2
|
||||
|
@ -601,10 +460,9 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
/**
|
||||
* 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 connection state will change from {@link #STATE_CONNECTED_AND_READY}.
|
||||
* If connection state will change, or an error will occur, an exception will be thrown.
|
||||
* {@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 gatt the GATT device
|
||||
* @param characteristic the characteristic to write to. Should be the DFU CONTROL POINT
|
||||
* @param value the value to write to the characteristic
|
||||
* @param reset whether the command trigger restarting the device
|
||||
|
@ -612,7 +470,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeOpCode(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] value, final boolean reset) throws DeviceDisconnectedException, DfuException,
|
||||
protected void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value, final boolean reset) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
|
@ -627,12 +485,12 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
characteristic.setValue(value);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Writing to characteristic " + characteristic.getUuid());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
mGatt.writeCharacteristic(characteristic);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((!mRequestCompleted && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((!mRequestCompleted && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -642,12 +500,13 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (!mResetRequestSent && mError != 0)
|
||||
throw new DfuException("Unable to write Op Code " + value[0], mError);
|
||||
if (!mResetRequestSent && mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to write Op Code " + value[0], mConnectionState);
|
||||
if (!mResetRequestSent && !mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to write Op Code " + value[0] + ": device disconnected");
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
protected boolean createBond(final BluetoothDevice device) {
|
||||
protected boolean createBond() {
|
||||
final BluetoothDevice device = mGatt.getDevice();
|
||||
if (device.getBondState() == BluetoothDevice.BOND_BONDED)
|
||||
return true;
|
||||
|
||||
|
@ -693,10 +552,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
/**
|
||||
* Removes the bond information for the given device.
|
||||
*
|
||||
* @param device the device to unbound
|
||||
* @return <code>true</code> if operation succeeded, <code>false</code> otherwise
|
||||
*/
|
||||
protected boolean removeBond(final BluetoothDevice device) {
|
||||
protected boolean removeBond() {
|
||||
final BluetoothDevice device = mGatt.getDevice();
|
||||
if (device.getBondState() == BluetoothDevice.BOND_NONE)
|
||||
return true;
|
||||
|
||||
|
@ -730,8 +589,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
|
||||
/**
|
||||
* Waits until the notification will arrive. Returns the data returned by the notification. This method will block the thread if response is not ready or connection state will change from
|
||||
* {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
* Waits until the notification will arrive. Returns the data returned by the notification. This method will block the thread until response is not ready or
|
||||
* the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
*
|
||||
* @return the value returned by the Control Point notification
|
||||
* @throws DeviceDisconnectedException
|
||||
|
@ -743,7 +602,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
mError = 0;
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mReceivedData == null && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((mReceivedData == null && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -753,8 +612,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to write Op Code", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to write Op Code", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to write Op Code: device disconnected");
|
||||
return mReceivedData;
|
||||
}
|
||||
|
||||
|
@ -786,17 +645,17 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
|
||||
protected void logw(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
// if (BuildConfig.DEBUG) // TODO
|
||||
Log.w(TAG, message);
|
||||
}
|
||||
|
||||
protected void logi(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
// if (BuildConfig.DEBUG) // TODO
|
||||
Log.i(TAG, message);
|
||||
}
|
||||
|
||||
protected void logd(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
// if (BuildConfig.DEBUG) // TODO
|
||||
Log.d(TAG, message);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -41,5 +41,5 @@ public interface DfuLogListener {
|
|||
* </ul>
|
||||
* @param message the log message
|
||||
*/
|
||||
public void onLogEvent(final String deviceAddress, final int level, final String message);
|
||||
void onLogEvent(final String deviceAddress, final int level, final String message);
|
||||
}
|
||||
|
|
|
@ -22,48 +22,62 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/* package */ class DfuProgressInfo {
|
||||
interface ProgressListener {
|
||||
void updateProgressNotification();
|
||||
}
|
||||
|
||||
private final ProgressListener mListener;
|
||||
private int progress;
|
||||
private int bytesSent;
|
||||
private int lastBytesSent;
|
||||
private int bytesReceived;
|
||||
private int imageSizeInBytes;
|
||||
private int maxObjectSizeInBytes;
|
||||
private int currentPart;
|
||||
private int totalParts;
|
||||
private long timeStart, lastProgressTime;
|
||||
|
||||
public DfuProgressInfo(final int imageSizeInBytes) {
|
||||
this.imageSizeInBytes = imageSizeInBytes;
|
||||
this.maxObjectSizeInBytes = Integer.MAX_VALUE; // by default the whole firmware will be sent as a single object
|
||||
public DfuProgressInfo(final @NonNull ProgressListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public DfuProgressInfo setPart(final int currentPart, final int totalParts) {
|
||||
public DfuProgressInfo init(final int imageSizeInBytes, final int currentPart, final int totalParts) {
|
||||
this.imageSizeInBytes = imageSizeInBytes;
|
||||
this.maxObjectSizeInBytes = Integer.MAX_VALUE; // by default the whole firmware will be sent as a single object
|
||||
this.currentPart = currentPart;
|
||||
this.totalParts = totalParts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo setProgress(final int progress) {
|
||||
this.progress = progress;
|
||||
public DfuProgressInfo setTotalPart(final int totalParts) {
|
||||
this.totalParts = totalParts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo setBytesSent(final int bytesSent) {
|
||||
public void setProgress(final int progress) {
|
||||
this.progress = progress;
|
||||
mListener.updateProgressNotification();
|
||||
}
|
||||
|
||||
public void setBytesSent(final int bytesSent) {
|
||||
if (this.bytesSent == 0 && bytesSent == 0) {
|
||||
timeStart = SystemClock.elapsedRealtime();
|
||||
}
|
||||
this.bytesSent = bytesSent;
|
||||
this.progress = (int) (100.0f * bytesSent / imageSizeInBytes);
|
||||
return this;
|
||||
mListener.updateProgressNotification();
|
||||
}
|
||||
|
||||
public DfuProgressInfo addBytesSent(final int increment) {
|
||||
return setBytesSent(bytesSent + increment);
|
||||
public void addBytesSent(final int increment) {
|
||||
setBytesSent(bytesSent + increment);
|
||||
}
|
||||
|
||||
public DfuProgressInfo setBytesReceived(final int bytesReceived) {
|
||||
public void setBytesReceived(final int bytesReceived) {
|
||||
this.bytesReceived = bytesReceived;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo addBytesReceived(final int increment) {
|
||||
return setBytesReceived(bytesReceived + increment);
|
||||
}
|
||||
|
||||
public void setMaxObjectSizeInBytes(final int bytes) {
|
||||
|
@ -98,6 +112,19 @@ package no.nordicsemi.android.dfu;
|
|||
return imageSizeInBytes;
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
final float speed = now - timeStart != 0 ? (float) (bytesSent - lastBytesSent) / (float) (now - lastProgressTime) : 0.0f;
|
||||
lastProgressTime = now;
|
||||
lastBytesSent = bytesSent;
|
||||
return speed;
|
||||
}
|
||||
|
||||
public float getAverageSpeed() {
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
return now - timeStart != 0 ? (float) bytesSent / (float) (now - timeStart) : 0.0f;
|
||||
}
|
||||
|
||||
public int getCurrentPart() {
|
||||
return currentPart;
|
||||
}
|
||||
|
@ -105,4 +132,8 @@ package no.nordicsemi.android.dfu;
|
|||
public int getTotalParts() {
|
||||
return totalParts;
|
||||
}
|
||||
|
||||
public boolean isLastPart() {
|
||||
return currentPart == totalParts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,32 +32,32 @@ public interface DfuProgressListener {
|
|||
* Method called when the DFU service started connecting with the DFU target.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDeviceConnecting(final String deviceAddress);
|
||||
void onDeviceConnecting(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service has successfully connected, discovered services and found DFU service on the DFU target.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDeviceConnected(final String deviceAddress);
|
||||
void onDeviceConnected(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process is starting. This includes reading the DFU Version characteristic, sending DFU_START command as well as the Init packet, if set.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDfuProcessStarting(final String deviceAddress);
|
||||
void onDfuProcessStarting(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process was started and bytes about to be sent.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDfuProcessStarted(final String deviceAddress);
|
||||
void onDfuProcessStarted(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service discovered that the DFU target is in the application mode and must be switched to DFU mode.
|
||||
* The switch command will be sent and the DFU process should start again. There will be no {@link #onDeviceDisconnected(String)} event following this call.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onEnablingDfuMode(final String deviceAddress);
|
||||
void onEnablingDfuMode(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called during uploading the firmware. It will not be called twice with the same value of percent, however, in case of small firmware files, some values may be omitted.
|
||||
|
@ -69,37 +69,37 @@ public interface DfuProgressListener {
|
|||
* then the service starts again and send the application as part 2
|
||||
* @param partsTotal total number of parts
|
||||
*/
|
||||
public void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal);
|
||||
void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal);
|
||||
|
||||
/**
|
||||
* Method called when the new firmware is being validated on the target device.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onFirmwareValidating(final String deviceAddress);
|
||||
void onFirmwareValidating(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service started to disconnect from the target device.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDeviceDisconnecting(final String deviceAddress);
|
||||
void onDeviceDisconnecting(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the service disconnected from the device. The device has been reset.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDeviceDisconnected(final String deviceAddress);
|
||||
void onDeviceDisconnected(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process succeeded.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDfuCompleted(final String deviceAddress);
|
||||
void onDfuCompleted(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when the DFU process has been aborted.
|
||||
* @param deviceAddress the target device address
|
||||
*/
|
||||
public void onDfuAborted(final String deviceAddress);
|
||||
void onDfuAborted(final String deviceAddress);
|
||||
|
||||
/**
|
||||
* Method called when an error occur.
|
||||
|
@ -109,5 +109,5 @@ public interface DfuProgressListener {
|
|||
* {@link DfuBaseService#ERROR_TYPE_DFU_REMOTE}, {@link DfuBaseService#ERROR_TYPE_OTHER}.
|
||||
* @param message the error message
|
||||
*/
|
||||
public void onError(final String deviceAddress, final int error, final int errorType, final String message);
|
||||
void onError(final String deviceAddress, final int error, final int errorType, final String message);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.DfuException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
|
||||
/* package */ interface DfuService {
|
||||
/** Pauses the DFU process. */
|
||||
void pause();
|
||||
|
||||
/** Resumes the paused DFU process. */
|
||||
void resume();
|
||||
|
||||
/** Terminates the DFU process. The device will disconnect and restore old application or bootloader. */
|
||||
void abort();
|
||||
|
||||
/** This method must return true if the device is compatible with this DFU implementation, false otherwise. */
|
||||
boolean hasRequiredService(final BluetoothGatt gatt);
|
||||
|
||||
/** This method must return true if the device is compatible with this DFU implementation, false otherwise. */
|
||||
boolean hasRequiredCharacteristics(final BluetoothGatt gatt);
|
||||
|
||||
/** Initializes the DFU implementation and does some initial setting up. */
|
||||
boolean initialize(final Intent intent, final BluetoothGatt gatt, final int fileType, final InputStream firmwareStream, final InputStream initPacketStream) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
|
||||
/** Performs the DFU process. */
|
||||
void performDfu(final Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
|
||||
/* package */ class DfuServiceProvider {
|
||||
|
||||
/* package */ static BaseDfuImpl getDfuImpl(final DfuBaseService service, final BluetoothGatt gatt) {
|
||||
final BluetoothGattService legacyService = gatt.getService(LegacyDfuImpl.DFU_SERVICE_UUID);
|
||||
if (legacyService != null) {
|
||||
return new LegacyDfuImpl(service);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ public interface DfuSettingsConstants {
|
|||
* <p>If true (default) the Packet Receipt Notification procedure will be enabled. See DFU documentation on http://infocenter.nordicsemi.com for more details.
|
||||
* The number of packets before receiving a Packet Receipt Notification is set with property {@link #SETTINGS_NUMBER_OF_PACKETS}.
|
||||
*/
|
||||
public static final String SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED = "settings_packet_receipt_notification_enabled";
|
||||
String SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED = "settings_packet_receipt_notification_enabled";
|
||||
|
||||
/**
|
||||
* This property must contain a positive integer value, usually from range 1-200.
|
||||
|
@ -41,7 +41,7 @@ public interface DfuSettingsConstants {
|
|||
* the device chip manufacturer) and the queue may reach its limit. When does, the transmission stops and Android Bluetooth hangs. Using PRN procedure eliminates this problem as
|
||||
* the notification is send when all packets were delivered the queue is empty.
|
||||
*/
|
||||
public static final String SETTINGS_NUMBER_OF_PACKETS = "settings_number_of_packets";
|
||||
String SETTINGS_NUMBER_OF_PACKETS = "settings_number_of_packets";
|
||||
|
||||
/**
|
||||
* The default value of {@link #SETTINGS_NUMBER_OF_PACKETS} property. Different phones sent a different number of packets each connection interval. The values are (for tested phones):
|
||||
|
@ -52,7 +52,7 @@ public interface DfuSettingsConstants {
|
|||
* </ul>
|
||||
* The least common multiplier is 12 which is reasonably small. You may try other values, like 24 etc. Values higher than ~300 may cause the Bluetooth outgoing queue overflow error.
|
||||
*/
|
||||
public static final int SETTINGS_NUMBER_OF_PACKETS_DEFAULT = 12;
|
||||
int SETTINGS_NUMBER_OF_PACKETS_DEFAULT = 12;
|
||||
|
||||
/**
|
||||
* This property must contain an integer value.
|
||||
|
@ -61,13 +61,13 @@ public interface DfuSettingsConstants {
|
|||
* a firmware in HEX onto another MCU via nRF chip, set this value to 0.
|
||||
* <p>If you are using the PC nrf util tool to create a ZIP Distribution Packet with the firmware and Init Packet this option does not apply as the nrf tool will convert HEX to BIN itself.
|
||||
*/
|
||||
public static final String SETTINGS_MBR_SIZE = "settings_mbr_size";
|
||||
String SETTINGS_MBR_SIZE = "settings_mbr_size";
|
||||
|
||||
/**
|
||||
* The default value of the MBR size.
|
||||
* @see #SETTINGS_DEFAULT_MBR_SIZE
|
||||
*/
|
||||
public static final int SETTINGS_DEFAULT_MBR_SIZE = 0x1000;
|
||||
int SETTINGS_DEFAULT_MBR_SIZE = 0x1000;
|
||||
|
||||
/**
|
||||
* This property must contain a boolean value.
|
||||
|
@ -79,5 +79,5 @@ public interface DfuSettingsConstants {
|
|||
* This guessing may not be always correct. One situation may be when the nRF chip is used to flash update on external MCU using DFU. The DFU procedure may be implemented in the
|
||||
* application, which may (and usually does) have more services. In such case set the value of this property to true.
|
||||
*/
|
||||
public static final String SETTINGS_ASSUME_DFU_NODE = "settings_assume_dfu_mode";
|
||||
String SETTINGS_ASSUME_DFU_NODE = "settings_assume_dfu_mode";
|
||||
}
|
||||
|
|
|
@ -22,26 +22,31 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import no.nordicsemi.android.dfu.internal.ArchiveInputStream;
|
||||
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.UnknownResponseException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
import no.nordicsemi.android.dfu.internal.scanner.BootloaderScannerFactory;
|
||||
import no.nordicsemi.android.error.GattError;
|
||||
import no.nordicsemi.android.error.LegacyDfuError;
|
||||
|
||||
/* package */ class LegacyDfuImpl extends BaseCustomDfuImpl {
|
||||
// DFU status values
|
||||
public static final int DFU_STATUS_SUCCESS = 1;
|
||||
public static final int DFU_STATUS_INVALID_STATE = 2;
|
||||
public static final int DFU_STATUS_NOT_SUPPORTED = 3;
|
||||
public static final int DFU_STATUS_DATA_SIZE_EXCEEDS_LIMIT = 4;
|
||||
public static final int DFU_STATUS_CRC_ERROR = 5;
|
||||
public static final int DFU_STATUS_OPERATION_FAILED = 6;
|
||||
// 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
|
||||
|
@ -64,23 +69,26 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
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
|
||||
private static final UUID DFU_SERVICE_UUID = new UUID(0x000015301212EFDEL, 0x1523785FEABCD123L);
|
||||
private static final UUID DFU_CONTROL_POINT_UUID = new UUID(0x000015311212EFDEL, 0x1523785FEABCD123L);
|
||||
private static final UUID DFU_PACKET_UUID = new UUID(0x000015321212EFDEL, 0x1523785FEABCD123L);
|
||||
private static final UUID DFU_VERSION = new UUID(0x000015341212EFDEL, 0x1523785FEABCD123L);
|
||||
private int mFileType;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the image size has been already transferred or not
|
||||
* Flag indicating whether the image size has been already transferred or not.
|
||||
*/
|
||||
private boolean mImageSizeSent;
|
||||
private boolean mImageSizeInProgress;
|
||||
|
||||
protected class LegacyBluetoothCallback extends BaseCustomBluetoothCallback {
|
||||
@Override
|
||||
protected void onPacketCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
if (mImageSizeSent) {
|
||||
if (mImageSizeInProgress) {
|
||||
// We've got confirmation that the image size was sent
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic));
|
||||
mImageSizeSent = false;
|
||||
mImageSizeInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +99,6 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
switch (responseType) {
|
||||
case OP_CODE_PACKET_RECEIPT_NOTIF_KEY:
|
||||
mProgressInfo.setBytesReceived(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 1));
|
||||
mService.updateProgressNotification(mProgressInfo);
|
||||
handlePacketReceiptNotification(gatt, characteristic);
|
||||
break;
|
||||
case OP_CODE_RESPONSE_CODE_KEY:
|
||||
|
@ -114,12 +121,26 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
}
|
||||
}
|
||||
|
||||
LegacyDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream, packetsBeforeNotification);
|
||||
/* package */ LegacyDfuImpl(final DfuBaseService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BluetoothGattCallback getGattCallback() {
|
||||
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 BaseCustomBluetoothCallback getGattCallback() {
|
||||
return new LegacyBluetoothCallback();
|
||||
}
|
||||
|
||||
|
@ -138,6 +159,549 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
return DFU_SERVICE_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
/*
|
||||
* The DFU Version characteristic has been added in SDK 7.0.
|
||||
*
|
||||
* It may return version number in 2 bytes (f.e. 0x05-00), where the first one is the minor version and the second one is the major version.
|
||||
* In case of 0x05-00 the DFU has the version 0.5.
|
||||
*
|
||||
* Currently the following version numbers are supported:
|
||||
*
|
||||
* - 0.1 (0x01-00) - The service is connected to the device in application mode, not to the DFU Bootloader. The application supports Long Term Key (LTK)
|
||||
* sharing and buttonless update. Enable notifications on the DFU Control Point characteristic and write 0x01-04 into it to jump to the Bootloader.
|
||||
* Check the Bootloader version again for more info about the Bootloader version.
|
||||
*
|
||||
* - 0.5 (0x05-00) - The device is in the OTA-DFU Bootloader mode. The Bootloader supports LTK sharing and requires the Extended Init Packet. It supports
|
||||
* a SoftDevice, Bootloader or an Application update. SoftDevice and a Bootloader may be sent together.
|
||||
*
|
||||
* - 0.6 (0x06-00) - The device is in the OTA-DFU Bootloader mode. The DFU Bootloader is from SDK 8.0 and has the same features as version 0.5. It also
|
||||
* supports also sending Service Changed notification in application mode after successful or aborted upload so no refreshing services is required.
|
||||
*/
|
||||
final BluetoothGattCharacteristic versionCharacteristic = gatt.getService(DFU_SERVICE_UUID).getCharacteristic(DFU_VERSION); // this may be null for older versions of the Bootloader
|
||||
|
||||
/*
|
||||
* Read the version number if available.
|
||||
* The version number consists of 2 bytes: major and minor. Therefore f.e. the version 5 (00-05) can be read as 0.5.
|
||||
*
|
||||
* Currently supported versions are:
|
||||
* * no DFU Version characteristic - we may be either in the bootloader mode or in the app mode. The DFU Bootloader from SDK 6.1 did not have this characteristic,
|
||||
* but it also supported the buttonless update. Usually, the application must have had some additional services (like Heart Rate, etc)
|
||||
* so if the number of services greater is than 3 (Generic Access, Generic Attribute, DFU Service) we can also assume we are in
|
||||
* the application mode and jump is required.
|
||||
*
|
||||
* * version = 1 (major = 0, minor = 1) - Application with DFU buttonless update supported. A jump to DFU mode is required.
|
||||
*
|
||||
* * version = 5 (major = 0, minor = 5) - Since version 5 the Extended Init Packet is required. Keep in mind that if we are in the app mode the DFU Version characteristic
|
||||
* still returns version = 1, as it is independent from the DFU Bootloader. The version = 5 is reported only after successful jump to
|
||||
* the DFU mode. In version = 5 the bond information is always lost. Released in SDK 7.0.0.
|
||||
*
|
||||
* * version = 6 (major = 0, minor = 6) - The DFU Bootloader may be configured to keep the bond information after application update. Please, see the {@link #EXTRA_KEEP_BOND}
|
||||
* documentation for more information about how to enable the feature (disabled by default). A change in the DFU bootloader source and
|
||||
* setting the {@link DfuServiceInitiator#setKeepBond} to true is required. Released in SDK 8.0.0.
|
||||
*
|
||||
* * version = 7 (major = 0, minor = 7) - The SHA-256 firmware hash is used in the Extended Init Packet instead of CRC-16. This feature is transparent for the DFU Service.
|
||||
*
|
||||
* * version = 8 (major = 0, minor = 8) - The Extended Init Packet is signed using the private key. The bootloader, using the public key, is able to verify the content.
|
||||
* Released in SDK 9.0.0 as experimental feature.
|
||||
* Caution! The firmware type (Application, Bootloader, SoftDevice or SoftDevice+Bootloader) is not encrypted as it is not a part of the
|
||||
* Extended Init Packet. A change in the protocol will be required to fix this issue.
|
||||
*/
|
||||
int version = 0;
|
||||
if (versionCharacteristic != null) {
|
||||
version = readVersion(versionCharacteristic);
|
||||
final int minor = (version & 0x0F);
|
||||
final int major = (version >> 8);
|
||||
logi("Version number read: " + major + "." + minor);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Version number read: " + major + "." + minor);
|
||||
} else {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "DFU Version characteristic not found");
|
||||
}
|
||||
|
||||
/*
|
||||
* In case of old DFU bootloader versions, where there was no DFU Version characteristic, the service was unable to determine whether it was in the application mode, or in
|
||||
* bootloader mode. In that case, if the following boolean value is set to false (default) the bootloader will count number of services on the device. In case of 3 service
|
||||
* it will start the DFU procedure (Generic Access, Generic Attribute, DFU Service). If more services will be found, it assumes that a jump to the DFU bootloader is required.
|
||||
*
|
||||
* However, in some cases, the DFU bootloader is used to flash firmware on other chip via nRF5x. In that case the application may support DFU operation without switching
|
||||
* to the bootloader mode itself.
|
||||
*
|
||||
* For newer implementations of DFU in such case the DFU Version should return value other than 0x0100 (major 0, minor 1) which means that the application does not support
|
||||
* DFU process itself but rather support jump to the bootloader mode.
|
||||
*/
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mService);
|
||||
final boolean assumeDfuMode = preferences.getBoolean(DfuSettingsConstants.SETTINGS_ASSUME_DFU_NODE, false);
|
||||
|
||||
/*
|
||||
* Check if we are in the DFU Bootloader or in the Application that supports the buttonless update.
|
||||
*
|
||||
* In the DFU from SDK 6.1, which was also supporting the buttonless update, there was no DFU Version characteristic. In that case we may find out whether
|
||||
* we are in the bootloader or application by simply checking the number of characteristics. This may be overridden by setting the DfuSettingsConstants.SETTINGS_ASSUME_DFU_NODE
|
||||
* property to true in Shared Preferences.
|
||||
*/
|
||||
if (version == 1 || (!assumeDfuMode && version == 0 && gatt.getServices().size() > 3 /* No DFU Version char but more services than Generic Access, Generic Attribute, DFU Service */)) {
|
||||
// The service is connected to the application, not to the bootloader
|
||||
logw("Application with buttonless update found");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Application with buttonless update found");
|
||||
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Jumping to the DFU Bootloader...");
|
||||
|
||||
// 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
|
||||
|
||||
// Send 'jump to bootloader command' (Start DFU)
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_ENABLING_DFU_MODE);
|
||||
OP_CODE_START_DFU[1] = 0x04;
|
||||
logi("Sending Start DFU command (Op Code = 1, Upload Mode = 4)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_START_DFU, true);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Jump to bootloader sent (Op Code = 1, Upload Mode = 4)");
|
||||
|
||||
// The device will reset so we don't have to send Disconnect signal.
|
||||
mService.waitUntilDisconnected();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Disconnected by the remote device");
|
||||
|
||||
/*
|
||||
* We would like to avoid using the hack with refreshing the device (refresh method is not in the public API). The refresh method clears the cached services and causes a
|
||||
* service discovery afterwards (when connected). Android, however, does it itself when receive the Service Changed indication when bonded.
|
||||
* In case of unpaired device we may either refresh the services manually (using the hack), or include the Service Changed characteristic.
|
||||
*
|
||||
* According to Bluetooth Core 4.0 (and 4.1) specification:
|
||||
*
|
||||
* [Vol. 3, Part G, 2.5.2 - Attribute Caching]
|
||||
* Note: Clients without a trusted relationship must perform service discovery on each connection if the server supports the Services Changed characteristic.
|
||||
*
|
||||
* However, as up to Android 5 the system does NOT respect this requirement and servers are cached for every device, even if Service Changed is enabled -> Android BUG?
|
||||
* For bonded devices Android performs service re-discovery when SC indication is received.
|
||||
*/
|
||||
final BluetoothGattService gas = gatt.getService(GENERIC_ATTRIBUTE_SERVICE_UUID);
|
||||
final boolean hasServiceChanged = gas != null && gas.getCharacteristic(SERVICE_CHANGED_UUID) != null;
|
||||
mService.refreshDeviceCache(gatt, !hasServiceChanged);
|
||||
|
||||
// Close the device
|
||||
mService.close(gatt);
|
||||
|
||||
logi("Starting service that will connect to the DFU bootloader");
|
||||
final Intent newIntent = new Intent();
|
||||
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
|
||||
mService.startService(newIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the DFU Version characteristic is present and the version returned from it is greater or equal to 0.5, the Extended Init Packet is required.
|
||||
* If the InputStream with init packet is null we may safely abort sending and reset the device as it would happen eventually in few moments.
|
||||
* The DFU target would send DFU INVALID STATE error if the init packet would not be sent before starting file transmission.
|
||||
*/
|
||||
if (version >= 5 && mInitPacketStream == null) {
|
||||
logw("Init packet not set for the DFU Bootloader version " + version);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_ERROR, "The Init packet is required by this version DFU Bootloader");
|
||||
mService.terminateConnection(gatt, DfuBaseService.ERROR_INIT_PACKET_REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Set up the temporary variable that will hold the responses
|
||||
byte[] response;
|
||||
int status;
|
||||
|
||||
/*
|
||||
* The first version of DFU supported only an Application update.
|
||||
* Initializing procedure:
|
||||
* [DFU Start (0x01)] -> DFU Control Point
|
||||
* [App size in bytes (UINT32)] -> DFU Packet
|
||||
* ---------------------------------------------------------------------
|
||||
* Since SDK 6.0 and Soft Device 7.0+ the DFU supports upgrading Soft Device, Bootloader and Application.
|
||||
* Initializing procedure:
|
||||
* [DFU Start (0x01), <Update Mode>] -> DFU Control Point
|
||||
* [SD size in bytes (UINT32), Bootloader size in bytes (UINT32), Application size in bytes (UINT32)] -> DFU Packet
|
||||
* where <Upload Mode> is a bit mask:
|
||||
* 0x01 - Soft Device update
|
||||
* 0x02 - Bootloader update
|
||||
* 0x04 - Application update
|
||||
* so that
|
||||
* 0x03 - Soft Device and Bootloader update
|
||||
* If <Upload Mode> equals 5, 6 or 7 DFU target may return OPERATION_NOT_SUPPORTED [10, 01, 03]. In that case service will try to send
|
||||
* Soft Device and/or Bootloader first, reconnect to the new Bootloader and send the Application in the second connection.
|
||||
* --------------------------------------------------------------------
|
||||
* If DFU target supports only the old DFU, a response [10, 01, 03] will be send as a notification on DFU Control Point characteristic, where:
|
||||
* 10 - Response for...
|
||||
* 01 - DFU Start command
|
||||
* 03 - Operation Not Supported
|
||||
* (see table below)
|
||||
* In that case:
|
||||
* 1. If this is application update - service will try to upload using the old DFU protocol.
|
||||
* 2. In case of SD or BL update an error is returned.
|
||||
*/
|
||||
|
||||
// Obtain size of image(s)
|
||||
int fileType = mFileType;
|
||||
int softDeviceImageSize = (fileType & DfuBaseService.TYPE_SOFT_DEVICE) > 0 ? mImageSizeInBytes : 0;
|
||||
int bootloaderImageSize = (fileType & DfuBaseService.TYPE_BOOTLOADER) > 0 ? mImageSizeInBytes : 0;
|
||||
int appImageSize = (fileType & DfuBaseService.TYPE_APPLICATION) > 0 ? mImageSizeInBytes : 0;
|
||||
// The sizes above may be overwritten if a ZIP file was passed
|
||||
if (mFirmwareStream instanceof ArchiveInputStream) {
|
||||
final ArchiveInputStream zhis = (ArchiveInputStream) mFirmwareStream;
|
||||
softDeviceImageSize = zhis.softDeviceImageSize();
|
||||
bootloaderImageSize = zhis.bootloaderImageSize();
|
||||
appImageSize = zhis.applicationImageSize();
|
||||
}
|
||||
|
||||
try {
|
||||
OP_CODE_START_DFU[1] = (byte) fileType;
|
||||
|
||||
// Send Start DFU command to Control Point
|
||||
logi("Sending Start DFU command (Op Code = 1, Upload Mode = " + fileType + ")");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_START_DFU);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "DFU Start sent (Op Code = 1, Upload Mode = " + fileType + ")");
|
||||
|
||||
// Send image size in bytes to DFU Packet
|
||||
logi("Sending image size array to DFU Packet (" + softDeviceImageSize + "b, " + bootloaderImageSize + "b, " + appImageSize + "b)");
|
||||
writeImageSize(mPacketCharacteristic, softDeviceImageSize, bootloaderImageSize, appImageSize);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Firmware image size sent (" + softDeviceImageSize + "b, " + bootloaderImageSize + "b, " + appImageSize + "b)");
|
||||
|
||||
// A notification will come with confirmation. Let's wait for it a bit
|
||||
response = readNotificationResponse();
|
||||
|
||||
/*
|
||||
* The response received from the DFU device contains:
|
||||
* +---------+--------+----------------------------------------------------+
|
||||
* | byte no | value | description |
|
||||
* +---------+--------+----------------------------------------------------+
|
||||
* | 0 | 16 | Response code |
|
||||
* | 1 | 1 | The Op Code of a request that this response is for |
|
||||
* | 2 | STATUS | See DFU_STATUS_* for status codes |
|
||||
* +---------+--------+----------------------------------------------------+
|
||||
*/
|
||||
status = getStatusCode(response, OP_CODE_START_DFU_KEY);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + " Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Starting DFU failed", status);
|
||||
} catch (final RemoteDfuException e) {
|
||||
try {
|
||||
if (e.getErrorNumber() != LegacyDfuError.NOT_SUPPORTED)
|
||||
throw e;
|
||||
|
||||
// If user wants to send the Soft Device and/or the Bootloader + Application we may try to send the Soft Device/Bootloader files first,
|
||||
// and then reconnect and send the application in the second connection.
|
||||
if ((fileType & DfuBaseService.TYPE_APPLICATION) > 0 && (fileType & (DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER)) > 0) {
|
||||
// Clear the remote error flag
|
||||
mRemoteErrorOccurred = false;
|
||||
|
||||
logw("DFU target does not support (SD/BL)+App update");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "DFU target does not support (SD/BL)+App update");
|
||||
|
||||
fileType &= ~DfuBaseService.TYPE_APPLICATION; // clear application bit
|
||||
mFileType = fileType;
|
||||
OP_CODE_START_DFU[1] = (byte) fileType;
|
||||
mProgressInfo.setTotalPart(2);
|
||||
|
||||
// Set new content type in the ZIP Input Stream and update sizes of images
|
||||
final ArchiveInputStream zhis = (ArchiveInputStream) mFirmwareStream;
|
||||
zhis.setContentType(fileType);
|
||||
appImageSize = 0;
|
||||
|
||||
// Send Start DFU command to Control Point
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Sending only SD/BL");
|
||||
logi("Resending Start DFU command (Op Code = 1, Upload Mode = " + fileType + ")");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_START_DFU);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "DFU Start sent (Op Code = 1, Upload Mode = " + fileType + ")");
|
||||
|
||||
// Send image size in bytes to DFU Packet
|
||||
logi("Sending image size array to DFU Packet: [" + softDeviceImageSize + "b, " + bootloaderImageSize + "b, " + appImageSize + "b]");
|
||||
writeImageSize(mPacketCharacteristic, softDeviceImageSize, bootloaderImageSize, appImageSize);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Firmware image size sent [" + softDeviceImageSize + "b, " + bootloaderImageSize + "b, " + appImageSize + "b]");
|
||||
|
||||
// A notification will come with confirmation. Let's wait for it a bit
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_START_DFU_KEY);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + " Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Starting DFU failed", status);
|
||||
} else
|
||||
throw e;
|
||||
} catch (final RemoteDfuException e1) {
|
||||
if (e1.getErrorNumber() != LegacyDfuError.NOT_SUPPORTED)
|
||||
throw e1;
|
||||
|
||||
// If operation is not supported by DFU target we may try to upload application with legacy mode, using the old DFU protocol
|
||||
if (fileType == DfuBaseService.TYPE_APPLICATION) {
|
||||
// Clear the remote error flag
|
||||
mRemoteErrorOccurred = false;
|
||||
|
||||
// The DFU target does not support DFU v.2 protocol
|
||||
logw("DFU target does not support DFU v.2");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "DFU target does not support DFU v.2");
|
||||
|
||||
// Send Start DFU command to Control Point
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Switching to DFU v.1");
|
||||
logi("Resending Start DFU command (Op Code = 1)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_START_DFU); // If has 2 bytes, but the second one is ignored
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "DFU Start sent (Op Code = 1)");
|
||||
|
||||
// Send image size in bytes to DFU Packet
|
||||
logi("Sending application image size to DFU Packet: " + mImageSizeInBytes + " bytes");
|
||||
writeImageSize(mPacketCharacteristic, mImageSizeInBytes);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Firmware image size sent (" + mImageSizeInBytes + " bytes)");
|
||||
|
||||
// A notification will come with confirmation. Let's wait for it a bit
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_START_DFU_KEY);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Starting DFU failed", status);
|
||||
} else
|
||||
throw e1;
|
||||
}
|
||||
}
|
||||
|
||||
// Since SDK 6.1 this delay is no longer required as the Receive Start DFU notification is postponed until the memory is clear.
|
||||
|
||||
// if ((fileType & TYPE_SOFT_DEVICE) > 0) {
|
||||
// // In the experimental version of bootloader (SDK 6.0.0) we must wait some time until we can proceed with Soft Device update. Bootloader must prepare the RAM for the new firmware.
|
||||
// // Most likely this step will not be needed in the future as the notification received a moment before will be postponed until Bootloader is ready.
|
||||
// mService.waitFor(6000);
|
||||
// }
|
||||
|
||||
/*
|
||||
* If the DFU Version characteristic is present and the version returned from it is greater or equal to 0.5, the Extended Init Packet is required.
|
||||
* For older versions, or if the DFU Version characteristic is not present (pre SDK 7.0.0), the Init Packet (which could have contained only the firmware CRC) was optional.
|
||||
* Deprecated: To calculate the CRC (CRC-CCTII-16 0xFFFF) the following application may be used: http://www.lammertbies.nl/comm/software/index.html -> CRC library.
|
||||
* New: To calculate the CRC (CRC-CCTII-16 0xFFFF) the 'nrf utility' may be used (see below).
|
||||
*
|
||||
* The Init Packet is read from the *.dat file as a binary file. This service you allows to specify the init packet file in two ways.
|
||||
* Since SDK 8.0 and the DFU Library v0.6 using the Distribution packet (ZIP) is recommended. The distribution packet can be created using the
|
||||
* *nrf utility* tool, available together with Master Control Panel v 3.8.0+. See the DFU documentation at http://developer.nordicsemi.com for more details.
|
||||
* An init file may be also provided as a separate file using the {@link #EXTRA_INIT_FILE_PATH} or {@link #EXTRA_INIT_FILE_URI} or in the ZIP file
|
||||
* with the deprecated fixed naming convention:
|
||||
*
|
||||
* a) If the ZIP file contain a softdevice.hex (or .bin) and/or bootloader.hex (or .bin) the 'system.dat' must also be included.
|
||||
* In case when both files are present the CRC should be calculated from the two BIN contents merged together.
|
||||
* This means: if there are softdevice.hex and bootloader.hex files in the ZIP file you have to convert them to BIN
|
||||
* (e.g. using: http://hex2bin.sourceforge.net/ application), copy them into a single file where the soft device is placed as the first one and calculate
|
||||
* the CRC for the whole file.
|
||||
*
|
||||
* b) If the ZIP file contains a application.hex (or .bin) file the 'application.dat' file must be included and contain the Init packet for the application.
|
||||
*/
|
||||
// Send DFU Init Packet
|
||||
if (mInitPacketStream != null) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Writing Initialize DFU Parameters...");
|
||||
|
||||
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 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");
|
||||
|
||||
// A notification will come with confirmation. Let's wait for it a bit
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_INIT_DFU_PARAMS_KEY);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Device returned error after sending init packet", status);
|
||||
}
|
||||
|
||||
// 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 = 8, Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
setNumberOfPackets(OP_CODE_PACKET_RECEIPT_NOTIF_REQ, numberOfPacketsBeforeNotification);
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_PACKET_RECEIPT_NOTIF_REQ);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Packet Receipt Notif Req (Op Code = 8) sent (Value = " + numberOfPacketsBeforeNotification + ")");
|
||||
}
|
||||
|
||||
// Initialize firmware upload
|
||||
logi("Sending Receive Firmware Image request (Op Code = 3)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_RECEIVE_FIRMWARE_IMAGE);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Receive Firmware Image request sent");
|
||||
|
||||
// Send the firmware. The method below sends the first packet and waits until the whole firmware is sent.
|
||||
final long startTime = SystemClock.elapsedRealtime();
|
||||
mProgressInfo.setBytesSent(0);
|
||||
try {
|
||||
logi("Uploading firmware...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Uploading firmware...");
|
||||
response = uploadFirmwareImage(mPacketCharacteristic);
|
||||
} catch (final DeviceDisconnectedException e) {
|
||||
loge("Disconnected while sending data");
|
||||
throw e;
|
||||
// TODO reconnect?
|
||||
}
|
||||
final long endTime = SystemClock.elapsedRealtime();
|
||||
|
||||
// Check the result of the operation
|
||||
status = getStatusCode(response, OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY);
|
||||
logi("Response received. Op Code: " + response[0] + " Req Op Code = " + response[1] + ", Status = " + response[2]);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Device returned error after sending file", status);
|
||||
|
||||
logi("Transfer of " + mProgressInfo.getBytesSent() + " bytes has taken " + (endTime - startTime) + " ms");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Upload completed in " + (endTime - startTime) + " ms");
|
||||
|
||||
// Send Validate request
|
||||
logi("Sending Validate request (Op Code = 4)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_VALIDATE);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Validate request sent");
|
||||
|
||||
// A notification will come with status code. Let's wait for it a bit.
|
||||
response = readNotificationResponse();
|
||||
status = getStatusCode(response, OP_CODE_VALIDATE_KEY);
|
||||
logi("Response received. Op Code: " + response[0] + " Req Op Code = " + response[1] + ", Status = " + response[2]);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Response received (Op Code = " + response[1] + ", Status = " + status + ")");
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
throw new RemoteDfuException("Device returned validation error", status);
|
||||
|
||||
// Send Activate and Reset signal.
|
||||
mProgressInfo.setProgress(DfuBaseService.PROGRESS_DISCONNECTING);
|
||||
logi("Sending Activate and Reset request (Op Code = 5)");
|
||||
writeOpCode(mControlPointCharacteristic, OP_CODE_ACTIVATE_AND_RESET);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_APPLICATION, "Activate and Reset request sent");
|
||||
|
||||
// The device will reset so we don't have to send Disconnect signal.
|
||||
mService.waitUntilDisconnected();
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Disconnected by the remote device");
|
||||
|
||||
// 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);
|
||||
}
|
||||
} 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) {
|
||||
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)));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets number of data packets that will be send before the notification will be received.
|
||||
*
|
||||
|
@ -166,16 +730,15 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
/**
|
||||
* Reads the DFU Version characteristic if such exists. Otherwise it returns 0.
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @param characteristic the characteristic to read
|
||||
* @return a version number or 0 if not present on the bootloader
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private int readVersion(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to read version number", mConnectionState);
|
||||
private int readVersion(final BluetoothGattCharacteristic characteristic) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read version number: device disconnected");
|
||||
// If the DFU Version characteristic is not available we return 0.
|
||||
if (characteristic == null)
|
||||
return 0;
|
||||
|
@ -188,12 +751,12 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
characteristic.setValue((byte[]) null);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.readCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.readCharacteristic(characteristic);
|
||||
mGatt.readCharacteristic(characteristic);
|
||||
|
||||
// We have to wait until device receives a response or an error occur
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (((!mRequestCompleted || characteristic.getValue() == null ) && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused) {
|
||||
while (((!mRequestCompleted || characteristic.getValue() == null ) && mConnected && mError == 0 && !mAborted) || mPaused) {
|
||||
mRequestCompleted = false;
|
||||
mLock.wait();
|
||||
}
|
||||
|
@ -205,8 +768,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to read version number", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to read version number", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to read version number: device disconnected");
|
||||
|
||||
// The version is a 16-bit unsigned int
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0);
|
||||
|
@ -214,49 +777,48 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
|
||||
/**
|
||||
* 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 connection state will change from {@link #STATE_CONNECTED_AND_READY}.
|
||||
* {@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 gatt the GATT device
|
||||
* @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 BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] value) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
private void writeOpCode(final BluetoothGattCharacteristic characteristic, final byte[] value) throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
final boolean reset = value[0] == OP_CODE_RESET_KEY || value[0] == OP_CODE_ACTIVATE_AND_RESET_KEY;
|
||||
writeOpCode(gatt, characteristic, value, reset);
|
||||
writeOpCode(characteristic, value, reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the image size 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 connection state will change from {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
* will be called or the device gets disconnected. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @param characteristic the characteristic to write to. Should be the DFU PACKET
|
||||
* @param imageSize the image size in bytes
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private void writeImageSize(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int imageSize) throws DeviceDisconnectedException, DfuException,
|
||||
private void writeImageSize(final BluetoothGattCharacteristic characteristic, final int imageSize) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mImageSizeSent = true;
|
||||
mImageSizeInProgress = true;
|
||||
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(new byte[4]);
|
||||
characteristic.setValue(imageSize, BluetoothGattCharacteristic.FORMAT_UINT32, 0);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Writing to characteristic " + characteristic.getUuid());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
mGatt.writeCharacteristic(characteristic);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mImageSizeSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((mImageSizeInProgress && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -266,8 +828,8 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to write Image Size", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to write Image Size", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to write Image Size: device disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,11 +838,10 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* Sizes of SD, BL and App are uploaded as 3x UINT32 even though some of them may be 0s. F.e. if only App is being updated the data will be <0x00000000, 0x00000000, [App size]>
|
||||
* </p>
|
||||
* <p>
|
||||
* 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 connection state will
|
||||
* change from {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or an error will occur, an exception will be thrown.
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @param gatt the GATT device
|
||||
* @param characteristic the characteristic to write to. Should be the DFU PACKET
|
||||
* @param softDeviceImageSize the Soft Device image size in bytes
|
||||
* @param bootloaderImageSize the Bootloader image size in bytes
|
||||
|
@ -289,11 +850,11 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private void writeImageSize(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int softDeviceImageSize, final int bootloaderImageSize, final int appImageSize)
|
||||
private void writeImageSize(final BluetoothGattCharacteristic characteristic, final int softDeviceImageSize, final int bootloaderImageSize, final int appImageSize)
|
||||
throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mImageSizeSent = true;
|
||||
mImageSizeInProgress = true;
|
||||
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(new byte[12]);
|
||||
|
@ -302,12 +863,12 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
characteristic.setValue(appImageSize, BluetoothGattCharacteristic.FORMAT_UINT32, 8);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Writing to characteristic " + characteristic.getUuid());
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
mGatt.writeCharacteristic(characteristic);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mImageSizeSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
while ((mImageSizeInProgress && mConnected && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
|
@ -317,7 +878,7 @@ import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
|||
throw new UploadAbortedException();
|
||||
if (mError != 0)
|
||||
throw new DfuException("Unable to write Image Sizes", mError);
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to write Image Sizes", mConnectionState);
|
||||
if (!mConnected)
|
||||
throw new DeviceDisconnectedException("Unable to write Image Sizes: device disconnected");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,18 +22,55 @@
|
|||
|
||||
package no.nordicsemi.android.dfu;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.io.InputStream;
|
||||
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.UploadAbortedException;
|
||||
|
||||
/* package */ class SecureDfuImpl extends BaseCustomDfuImpl {
|
||||
|
||||
SecureDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream, packetsBeforeNotification);
|
||||
SecureDfuImpl(final DfuBaseService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BluetoothGattCallback getGattCallback() {
|
||||
protected BaseBluetoothGattCallback getGattCallback() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getControlPointCharacteristicUUID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getPacketCharacteristicUUID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getDfuServiceUUID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredService(BluetoothGatt gatt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRequiredCharacteristics(BluetoothGatt gatt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performDfu(Intent intent) throws DfuException, DeviceDisconnectedException, UploadAbortedException {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,20 +28,7 @@ package no.nordicsemi.android.dfu.internal.exception;
|
|||
public class DeviceDisconnectedException extends Exception {
|
||||
private static final long serialVersionUID = -6901728550661937942L;
|
||||
|
||||
private final int mState;
|
||||
|
||||
public DeviceDisconnectedException(final String message, final int state) {
|
||||
public DeviceDisconnectedException(final String message) {
|
||||
super(message);
|
||||
|
||||
mState = state;
|
||||
}
|
||||
|
||||
public int getConnectionState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return super.getMessage() + " (connection state: " + mState + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import no.nordicsemi.android.dfu.DfuBaseService;
|
|||
* See: https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h (and other versions) for details.
|
||||
*/
|
||||
public class GattError {
|
||||
|
||||
// Starts at line 106 of gatt_api.h file
|
||||
/**
|
||||
* Converts the connection status given by the {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} to error name.
|
||||
|
@ -170,20 +169,20 @@ public class GattError {
|
|||
case DfuBaseService.ERROR_INIT_PACKET_REQUIRED:
|
||||
return "INIT PACKET REQUIRED";
|
||||
default:
|
||||
/*if ((DfuBaseService.ERROR_REMOTE_MASK & error) > 0) {
|
||||
if ((DfuBaseService.ERROR_REMOTE_MASK & error) > 0) {
|
||||
switch (error & (~DfuBaseService.ERROR_REMOTE_MASK)) {
|
||||
case DfuBaseService.DFU_STATUS_INVALID_STATE:
|
||||
case LegacyDfuError.INVALID_STATE:
|
||||
return "REMOTE DFU INVALID STATE";
|
||||
case DfuBaseService.DFU_STATUS_NOT_SUPPORTED:
|
||||
case LegacyDfuError.NOT_SUPPORTED:
|
||||
return "REMOTE DFU NOT SUPPORTED";
|
||||
case DfuBaseService.DFU_STATUS_DATA_SIZE_EXCEEDS_LIMIT:
|
||||
case LegacyDfuError.DATA_SIZE_EXCEEDS_LIMIT:
|
||||
return "REMOTE DFU DATA SIZE EXCEEDS LIMIT";
|
||||
case DfuBaseService.DFU_STATUS_CRC_ERROR:
|
||||
case LegacyDfuError.CRC_ERROR:
|
||||
return "REMOTE DFU INVALID CRC ERROR";
|
||||
case DfuBaseService.DFU_STATUS_OPERATION_FAILED:
|
||||
case LegacyDfuError.OPERATION_FAILED:
|
||||
return "REMOTE DFU OPERATION FAILED";
|
||||
}
|
||||
}*/ // TODO
|
||||
}
|
||||
return "UNKNOWN (" + error + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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;
|
||||
|
||||
public final class LegacyDfuError {
|
||||
// DFU status values
|
||||
public static final int INVALID_STATE = 2;
|
||||
public static final int NOT_SUPPORTED = 3;
|
||||
public static final int DATA_SIZE_EXCEEDS_LIMIT = 4;
|
||||
public static final int CRC_ERROR = 5;
|
||||
public static final int OPERATION_FAILED = 6;
|
||||
}
|
Loading…
Reference in New Issue