Initial changes for Legacy DFU, not working.
This commit is contained in:
parent
e97e52eee3
commit
c901011d04
|
@ -41,8 +41,7 @@ ext {
|
|||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.2'
|
||||
|
||||
buildToolsVersion '23.0.3'
|
||||
defaultConfig {
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 23
|
||||
|
@ -55,11 +54,13 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
productFlavors {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
compile 'com.android.support:support-v4:23.3.0'
|
||||
compile 'com.google.code.gson:gson:2.5'
|
||||
}
|
||||
/*
|
||||
|
|
41
dfu/dfu.iml
41
dfu/dfu.iml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":dfu" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="DFULibrary" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<module external.linked.project.id=":dfu" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../../nRFMasterControlPanel" external.system.id="GRADLE" external.system.module.group="nRFMasterControlPanel" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
|
@ -12,10 +12,7 @@
|
|||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
|
||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugAndroidTestSources</task>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
|
@ -29,7 +26,7 @@
|
|||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||
|
@ -51,6 +48,15 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
||||
|
@ -58,6 +64,15 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
|
@ -65,34 +80,30 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/docs" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
||||
<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.1.1/jars" />
|
||||
<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/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/ivy.xml" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/poms" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/release" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.1.1" level="project" />
|
||||
<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="gson-2.5" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,338 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.BluetoothGattCharacteristic;
|
||||
|
||||
import java.io.IOException;
|
||||
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.HexFileValidationException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
|
||||
/* package */ abstract class BaseCustomDfuImpl extends BaseDfuImpl {
|
||||
/**
|
||||
* Flag indicating whether the init packet has been already transferred or not.
|
||||
*/
|
||||
private boolean mInitPacketSent;
|
||||
/**
|
||||
* Flag indicating whether the firmware is being transmitted or not.
|
||||
*/
|
||||
private boolean mFirmwareUploadStarted;
|
||||
/**
|
||||
* The number of packets of firmware data to be send before receiving a new Packets receipt notification. 0 disables the packets notifications.
|
||||
*/
|
||||
protected final int mPacketsBeforeNotification;
|
||||
/**
|
||||
* The number of packets sent since last notification.
|
||||
*/
|
||||
protected int mPacketsSentSinceNotification;
|
||||
/**
|
||||
* <p>
|
||||
* Flag set to <code>true</code> when the DFU target had send a notification with status other than success. Setting it to <code>true</code> will abort sending firmware and
|
||||
* stop logging notifications (read below for explanation).
|
||||
* </p>
|
||||
* <p>
|
||||
* The onCharacteristicWrite(..) callback is called when Android writes the packet into the outgoing queue, not when it physically sends the data.
|
||||
* This means that the service will first put up to N* packets, one by one, to the queue, while in fact the first one is transmitted.
|
||||
* In case the DFU target is in an invalid state it will notify Android with a notification 10-03-02 for each packet of firmware that has been sent.
|
||||
* After receiving the first such notification, the DFU service will add the reset command to the outgoing queue, but it will still be receiving such notifications
|
||||
* until all the data packets are sent. Those notifications should be ignored. This flag will prevent from logging "Notification received..." more than once.
|
||||
* </p>
|
||||
* <p>
|
||||
* Additionally, sometimes after writing the command 6 ({@link LegacyDfuImpl#OP_CODE_RESET}), Android will receive a notification and update the characteristic value with 10-03-02 and the callback for write
|
||||
* reset command will log "[DFU] Data written to ..., value (0x): 10-03-02" instead of "...(x0): 06". But this does not matter for the DFU process.
|
||||
* </p>
|
||||
* <p>
|
||||
* N* - Value of Packet Receipt Notification, 10 by default.
|
||||
* </p>
|
||||
*/
|
||||
protected boolean mRemoteErrorOccurred;
|
||||
|
||||
protected class BaseCustomBluetoothCallback extends BaseBluetoothGattCallback {
|
||||
protected void onPacketCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
// this method must be overwritten on the final class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
/*
|
||||
* This method is called when either a CONTROL POINT or PACKET characteristic has been written.
|
||||
* If it is the CONTROL POINT characteristic, just set the {@link mRequestCompleted} flag to true. The main thread will continue its task when notified.
|
||||
* If the PACKET characteristic was written we must:
|
||||
* - if the image size was written in DFU Start procedure, just set flag to true
|
||||
* otherwise
|
||||
* - send the next packet, if notification is not required at that moment, or
|
||||
* - do nothing, because we have to wait for the notification to confirm the data received
|
||||
*/
|
||||
if (characteristic.getUuid().equals(getPacketCharacteristicUUID())) {
|
||||
if (mInitPacketSent) {
|
||||
// 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) {
|
||||
// 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
|
||||
// a packet confirmation (if there are more bytes to send) or the image received notification (it upload process was completed)
|
||||
final boolean notificationExpected = mPacketsBeforeNotification > 0 && mPacketsSentSinceNotification == mPacketsBeforeNotification;
|
||||
final boolean lastPacketTransferred = mProgressInfo.isComplete();
|
||||
final boolean lastObjectPacketTransferred = mProgressInfo.isObjectComplete();
|
||||
|
||||
// This flag may be true only in Secure DFU.
|
||||
// In Secure DFU we usually do not get any notification after the object is completed, therefor the lock must be notified here.
|
||||
if (lastObjectPacketTransferred) {
|
||||
mFirmwareUploadStarted = false;
|
||||
notifyLock();
|
||||
return;
|
||||
}
|
||||
// When a notification is expected (either a Packet Receipt Notification or one that's send after the whole image is completed)
|
||||
// we must not call notifyLock as the process will resume after notification is received.
|
||||
if (notificationExpected || lastPacketTransferred)
|
||||
return;
|
||||
|
||||
// When neither of them is true, send the next packet
|
||||
try {
|
||||
waitIfPaused();
|
||||
// The writing might have been aborted (mAborted = true), an error might have occurred.
|
||||
// In that case stop sending.
|
||||
if (mAborted || mError != 0 || mRemoteErrorOccurred || mResetRequestSent) {
|
||||
// notify waiting thread
|
||||
synchronized (mLock) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Upload terminated");
|
||||
mLock.notifyAll();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final byte[] buffer = mBuffer;
|
||||
final int size = mFirmwareStream.read(buffer);
|
||||
writePacket(gatt, characteristic, buffer, size);
|
||||
return;
|
||||
} catch (final HexFileValidationException e) {
|
||||
loge("Invalid HEX file");
|
||||
mError = DfuBaseService.ERROR_FILE_INVALID;
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading the input stream", e);
|
||||
mError = DfuBaseService.ERROR_FILE_IO_EXCEPTION;
|
||||
}
|
||||
} else {
|
||||
onPacketCharacteristicWrite(gatt, characteristic, status);
|
||||
}
|
||||
} else {
|
||||
// If the CONTROL POINT characteristic was written just set the flag to true. The main thread will continue its task when notified.
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Data written to " + characteristic.getUuid() + ", value (0x): " + parse(characteristic));
|
||||
mRequestCompleted = true;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* If a Reset (Op Code = 6) or Activate and Reset (Op Code = 5) commands are sent, the DFU target resets and sometimes does it so quickly that does not manage to send
|
||||
* any ACK to the controller and error 133 is thrown here. This bug should be fixed in SDK 8.0+ where the target would gracefully disconnect before restarting.
|
||||
*/
|
||||
if (mResetRequestSent)
|
||||
mRequestCompleted = true;
|
||||
else {
|
||||
loge("Characteristic write error: " + status);
|
||||
mError = DfuBaseService.ERROR_CONNECTION_MASK | status;
|
||||
}
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
final BluetoothGattCharacteristic packetCharacteristic = gatt.getService(getDfuServiceUUID()).getCharacteristic(getPacketCharacteristicUUID());
|
||||
try {
|
||||
mPacketsSentSinceNotification = 0;
|
||||
|
||||
waitIfPaused();
|
||||
// The writing might have been aborted (mAborted = true), an error might have occurred.
|
||||
// In that case quit sending.
|
||||
if (mAborted || mError != 0 || mRemoteErrorOccurred || mResetRequestSent) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_WARNING, "Upload terminated");
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] buffer = mBuffer;
|
||||
final int size = mFirmwareStream.read(buffer);
|
||||
writePacket(gatt, packetCharacteristic, buffer, size);
|
||||
} catch (final HexFileValidationException e) {
|
||||
loge("Invalid HEX file");
|
||||
mError = DfuBaseService.ERROR_FILE_INVALID;
|
||||
} catch (final IOException e) {
|
||||
loge("Error while reading the input stream", e);
|
||||
mError = DfuBaseService.ERROR_FILE_IO_EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
BaseCustomDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream);
|
||||
mPacketsBeforeNotification = packetsBeforeNotification;
|
||||
}
|
||||
|
||||
protected abstract UUID getControlPointCharacteristicUUID();
|
||||
|
||||
protected abstract UUID getPacketCharacteristicUUID();
|
||||
|
||||
protected abstract UUID getDfuServiceUUID();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeInitPacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
byte[] locBuffer = buffer;
|
||||
if (buffer.length != size) {
|
||||
locBuffer = new byte[size];
|
||||
System.arraycopy(buffer, 0, locBuffer, 0, size);
|
||||
}
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mInitPacketSent = 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);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mInitPacketSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 DfuException Thrown if DFU error occur
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected byte[] uploadFirmwareImage(final BluetoothGatt gatt, final BluetoothGattCharacteristic packetCharacteristic) throws DeviceDisconnectedException,
|
||||
DfuException, UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mFirmwareUploadStarted = 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);
|
||||
} catch (final HexFileValidationException e) {
|
||||
throw new DfuException("HEX file not valid", DfuBaseService.ERROR_FILE_INVALID);
|
||||
} catch (final IOException e) {
|
||||
throw new DfuException("Error while reading file", DfuBaseService.ERROR_FILE_IO_EXCEPTION);
|
||||
}
|
||||
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mFirmwareUploadStarted && mReceivedData == null && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
|
||||
if (mAborted)
|
||||
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);
|
||||
|
||||
return mReceivedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected void writePacket(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] buffer, final int size) {
|
||||
byte[] locBuffer = buffer;
|
||||
if (size <= 0) // This should never happen
|
||||
return;
|
||||
if (buffer.length != size) {
|
||||
locBuffer = new byte[size];
|
||||
System.arraycopy(buffer, 0, locBuffer, 0, size);
|
||||
}
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(locBuffer);
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
// FIXME BLE buffer overflow
|
||||
// after writing to the device with WRITE_NO_RESPONSE property the onCharacteristicWrite callback is received immediately after writing data to a buffer.
|
||||
// The real sending is much slower than adding to the buffer. This method does not return false if writing didn't succeed.. just the callback is not invoked.
|
||||
//
|
||||
// More info: this works fine on Nexus 5 (Android 4.4) (4.3 seconds) and on Samsung S4 (Android 4.3) (20 seconds) so this is a driver issue.
|
||||
// Nexus 4 and 7 uses Qualcomm chip, Nexus 5 and Samsung uses Broadcom chips.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,802 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
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 {
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
/** 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 request was completed or not
|
||||
*/
|
||||
protected boolean mRequestCompleted;
|
||||
/**
|
||||
* Flag sent when a request has been sent that will cause the DFU target to reset. Often, after sending such command, Android throws a connection state error. If this flag is set the error will be
|
||||
* ignored.
|
||||
*/
|
||||
protected boolean mResetRequestSent;
|
||||
/**
|
||||
* The number of the last error that has occurred or 0 if there was no error
|
||||
*/
|
||||
protected int mError;
|
||||
/**
|
||||
* Latest data received from device using notification.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
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))
|
||||
return;
|
||||
|
||||
// Read bond state
|
||||
final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
|
||||
if (bondState == BluetoothDevice.BOND_BONDING)
|
||||
return;
|
||||
|
||||
mRequestCompleted = true;
|
||||
notifyLock();
|
||||
}
|
||||
};
|
||||
|
||||
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 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;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
/*
|
||||
* This method is called when the DFU Version characteristic has been read.
|
||||
*/
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_INFO, "Read Response received from " + characteristic.getUuid() + ", value (0x): " + parse(characteristic));
|
||||
mReceivedData = characteristic.getValue();
|
||||
mRequestCompleted = true;
|
||||
} else {
|
||||
loge("Characteristic read error: " + status);
|
||||
mError = DfuBaseService.ERROR_CONNECTION_MASK | status;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) {
|
||||
if (SERVICE_CHANGED_UUID.equals(descriptor.getCharacteristic().getUuid())) {
|
||||
// We have enabled indications for the Service Changed characteristic
|
||||
mRequestCompleted = true;
|
||||
} else {
|
||||
// reading other descriptor is not supported
|
||||
loge("Unknown descriptor read"); // this have to be implemented if needed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loge("Descriptor read error: " + status);
|
||||
mError = DfuBaseService.ERROR_CONNECTION_MASK | status;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) {
|
||||
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());
|
||||
} else {
|
||||
// We have enabled notifications for this characteristic
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Notifications enabled for " + descriptor.getCharacteristic().getUuid());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loge("Descriptor write error: " + status);
|
||||
mError = DfuBaseService.ERROR_CONNECTION_MASK | status;
|
||||
}
|
||||
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();
|
||||
if (data == null)
|
||||
return "";
|
||||
final int length = data.length;
|
||||
if (length == 0)
|
||||
return "";
|
||||
|
||||
final char[] out = new char[length * 3 - 1];
|
||||
for (int j = 0; j < length; j++) {
|
||||
int v = data[j] & 0xFF;
|
||||
out[j * 3] = HEX_ARRAY[v >>> 4];
|
||||
out[j * 3 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
if (j != length - 1)
|
||||
out[j * 3 + 2] = '-';
|
||||
}
|
||||
return new String(out);
|
||||
}
|
||||
};
|
||||
|
||||
/* package */ BaseDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream) {
|
||||
mService = service;
|
||||
mFirmwareStream = firmwareStream;
|
||||
mInitPacketStream = initPacketStream;
|
||||
int size;
|
||||
try {
|
||||
size = firmwareStream.available();
|
||||
} catch (final IOException e) {
|
||||
size = 0;
|
||||
// not possible
|
||||
}
|
||||
mProgressInfo = new DfuProgressInfo(size);
|
||||
|
||||
final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
service.registerReceiver(mConnectionStateBroadcastReceiver, filter);
|
||||
|
||||
final IntentFilter bondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
service.registerReceiver(mBondStateBroadcastReceiver, bondFilter);
|
||||
}
|
||||
|
||||
/* package */ void unregister() {
|
||||
mService.unregisterReceiver(mConnectionStateBroadcastReceiver);
|
||||
mService.unregisterReceiver(mBondStateBroadcastReceiver);
|
||||
mService = null;
|
||||
}
|
||||
|
||||
/* package */ void pause() {
|
||||
mPaused = true;
|
||||
}
|
||||
|
||||
/* package */ void resume() {
|
||||
mPaused = false;
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
/* package */ void abort() {
|
||||
mPaused = false;
|
||||
mAborted = true;
|
||||
notifyLock();
|
||||
}
|
||||
|
||||
protected void notifyLock() {
|
||||
// Notify waiting thread
|
||||
synchronized (mLock) {
|
||||
mLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitIfPaused() {
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 {
|
||||
final String debugString = type == NOTIFICATIONS ? "notifications" : "indications";
|
||||
if (mConnectionState != STATE_CONNECTED_AND_READY)
|
||||
throw new DeviceDisconnectedException("Unable to set " + debugString + " state", mConnectionState);
|
||||
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
|
||||
boolean cccdEnabled = descriptor.getValue() != null && descriptor.getValue().length == 2 && descriptor.getValue()[0] > 0 && descriptor.getValue()[1] == 0;
|
||||
if (cccdEnabled)
|
||||
return;
|
||||
|
||||
logi("Enabling " + debugString + "...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Enabling " + debugString + " for " + characteristic.getUuid());
|
||||
|
||||
// enable notifications locally
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.setCharacteristicNotification(" + characteristic.getUuid() + ", true)");
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
|
||||
// enable notifications on the device
|
||||
descriptor.setValue(type == NOTIFICATIONS ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.writeDescriptor(" + descriptor.getUuid() + (type == NOTIFICATIONS ? ", value=0x01-00)" : ", value=0x02-00)"));
|
||||
gatt.writeDescriptor(descriptor);
|
||||
|
||||
// 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) {
|
||||
mLock.wait();
|
||||
// Check the value of the CCCD
|
||||
cccdEnabled = descriptor.getValue() != null && descriptor.getValue().length == 2 && descriptor.getValue()[0] > 0 && descriptor.getValue()[1] == 0;
|
||||
}
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// If the Service Changed characteristic or the CCCD is not available we return false.
|
||||
final BluetoothGattService genericAttributeService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE_UUID);
|
||||
if (genericAttributeService == null)
|
||||
return false;
|
||||
|
||||
final BluetoothGattCharacteristic serviceChangedCharacteristic = genericAttributeService.getCharacteristic(SERVICE_CHANGED_UUID);
|
||||
if (serviceChangedCharacteristic == null)
|
||||
return false;
|
||||
|
||||
final BluetoothGattDescriptor descriptor = serviceChangedCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
|
||||
if (descriptor == null)
|
||||
return false;
|
||||
|
||||
mRequestCompleted = false;
|
||||
mError = 0;
|
||||
|
||||
logi("Reading Service Changed CCCD value...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Reading Service Changed CCCD value...");
|
||||
|
||||
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)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
|
||||
// Return true if the CCCD value is
|
||||
return descriptor.getValue() != null && descriptor.getValue().length == 2
|
||||
&& descriptor.getValue()[0] == BluetoothGattDescriptor.ENABLE_INDICATION_VALUE[0]
|
||||
&& descriptor.getValue()[1] == BluetoothGattDescriptor.ENABLE_INDICATION_VALUE[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected void writeOpCode(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final byte[] value, final boolean reset) throws DeviceDisconnectedException, DfuException,
|
||||
UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mRequestCompleted = false;
|
||||
/*
|
||||
* Sending a command that will make the DFU target to reboot may cause an error 133 (0x85 - Gatt Error). If so, with this flag set, the error will not be shown to the user
|
||||
* as the peripheral is disconnected anyway. See: mGattCallback#onCharacteristicWrite(...) method
|
||||
*/
|
||||
mResetRequestSent = reset;
|
||||
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
|
||||
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);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((!mRequestCompleted && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
protected boolean createBond(final BluetoothDevice device) {
|
||||
if (device.getBondState() == BluetoothDevice.BOND_BONDED)
|
||||
return true;
|
||||
|
||||
boolean result;
|
||||
mRequestCompleted = false;
|
||||
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Starting pairing...");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond()");
|
||||
result = device.createBond();
|
||||
} else {
|
||||
result = createBondApi18(device);
|
||||
}
|
||||
|
||||
// We have to wait until device is bounded
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (!mRequestCompleted && !mAborted)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean createBondApi18(final BluetoothDevice device) {
|
||||
/*
|
||||
* There is a createBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections. It has been revealed in KitKat (Api19)
|
||||
*/
|
||||
try {
|
||||
final Method createBond = device.getClass().getMethod("createBond");
|
||||
if (createBond != null) {
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond() (hidden)");
|
||||
return (Boolean) createBond.invoke(device);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "An exception occurred while creating bond", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (device.getBondState() == BluetoothDevice.BOND_NONE)
|
||||
return true;
|
||||
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Removing bond information...");
|
||||
boolean result = false;
|
||||
/*
|
||||
* There is a removeBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections.
|
||||
*/
|
||||
try {
|
||||
final Method removeBond = device.getClass().getMethod("removeBond");
|
||||
if (removeBond != null) {
|
||||
mRequestCompleted = false;
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().removeBond() (hidden)");
|
||||
result = (Boolean) removeBond.invoke(device);
|
||||
|
||||
// We have to wait until device is unbounded
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while (!mRequestCompleted && !mAborted)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, "An exception occurred while removing bond information", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the value returned by the Control Point notification
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
protected byte[] readNotificationResponse() throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
// do not clear the mReceiveData here. The response might already be obtained. Clear it in write request instead.
|
||||
mError = 0;
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mReceivedData == null && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
return mReceivedData;
|
||||
}
|
||||
|
||||
protected String parse(final byte[] data) {
|
||||
if (data == null)
|
||||
return "";
|
||||
|
||||
final int length = data.length;
|
||||
if (length == 0)
|
||||
return "";
|
||||
|
||||
final char[] out = new char[length * 3 - 1];
|
||||
for (int j = 0; j < length; j++) {
|
||||
int v = data[j] & 0xFF;
|
||||
out[j * 3] = HEX_ARRAY[v >>> 4];
|
||||
out[j * 3 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
if (j != length - 1)
|
||||
out[j * 3 + 2] = '-';
|
||||
}
|
||||
return new String(out);
|
||||
}
|
||||
|
||||
protected void loge(final String message) {
|
||||
Log.e(TAG, message);
|
||||
}
|
||||
|
||||
protected void loge(final String message, final Throwable e) {
|
||||
Log.e(TAG, message, e);
|
||||
}
|
||||
|
||||
protected void logw(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
Log.w(TAG, message);
|
||||
}
|
||||
|
||||
protected void logi(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
Log.i(TAG, message);
|
||||
}
|
||||
|
||||
protected void logd(final String message) {
|
||||
// if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, message);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,108 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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;
|
||||
|
||||
/* package */ class DfuProgressInfo {
|
||||
private int progress;
|
||||
private int bytesSent;
|
||||
private int bytesReceived;
|
||||
private int imageSizeInBytes;
|
||||
private int maxObjectSizeInBytes;
|
||||
private int currentPart;
|
||||
private int totalParts;
|
||||
|
||||
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 setPart(final int currentPart, final int totalParts) {
|
||||
this.currentPart = currentPart;
|
||||
this.totalParts = totalParts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo setProgress(final int progress) {
|
||||
this.progress = progress;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo setBytesSent(final int bytesSent) {
|
||||
this.bytesSent = bytesSent;
|
||||
this.progress = (int) (100.0f * bytesSent / imageSizeInBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DfuProgressInfo addBytesSent(final int increment) {
|
||||
return setBytesSent(bytesSent + increment);
|
||||
}
|
||||
|
||||
public DfuProgressInfo 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) {
|
||||
this.maxObjectSizeInBytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return bytesSent == imageSizeInBytes;
|
||||
}
|
||||
|
||||
public boolean isObjectComplete() {
|
||||
return (bytesSent % maxObjectSizeInBytes) == 0;
|
||||
}
|
||||
|
||||
public int getAvailableObjectSizeIsBytes() {
|
||||
return maxObjectSizeInBytes - (bytesSent % maxObjectSizeInBytes);
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public int getBytesSent() {
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
public int getBytesReceived() {
|
||||
return bytesReceived;
|
||||
}
|
||||
|
||||
public int getImageSizeInBytes() {
|
||||
return imageSizeInBytes;
|
||||
}
|
||||
|
||||
public int getCurrentPart() {
|
||||
return currentPart;
|
||||
}
|
||||
|
||||
public int getTotalParts() {
|
||||
return totalParts;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,323 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
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.UnknownResponseException;
|
||||
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
|
||||
|
||||
/* 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
|
||||
private static final int OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY = 0x03; // 3
|
||||
private static final int OP_CODE_VALIDATE_KEY = 0x04; // 4
|
||||
private static final int OP_CODE_ACTIVATE_AND_RESET_KEY = 0x05; // 5
|
||||
private static final int OP_CODE_RESET_KEY = 0x06; // 6
|
||||
//private static final int OP_CODE_PACKET_REPORT_RECEIVED_IMAGE_SIZE_KEY = 0x07; // 7
|
||||
private static final int OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY = 0x08; // 8
|
||||
private static final int OP_CODE_RESPONSE_CODE_KEY = 0x10; // 16
|
||||
private static final int OP_CODE_PACKET_RECEIPT_NOTIF_KEY = 0x11; // 11
|
||||
private static final byte[] OP_CODE_START_DFU = new byte[]{OP_CODE_START_DFU_KEY, 0x00};
|
||||
private static final byte[] OP_CODE_INIT_DFU_PARAMS_START = new byte[]{OP_CODE_INIT_DFU_PARAMS_KEY, 0x00};
|
||||
private static final byte[] OP_CODE_INIT_DFU_PARAMS_COMPLETE = new byte[]{OP_CODE_INIT_DFU_PARAMS_KEY, 0x01};
|
||||
private static final byte[] OP_CODE_RECEIVE_FIRMWARE_IMAGE = new byte[]{OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY};
|
||||
private static final byte[] OP_CODE_VALIDATE = new byte[]{OP_CODE_VALIDATE_KEY};
|
||||
private static final byte[] OP_CODE_ACTIVATE_AND_RESET = new byte[]{OP_CODE_ACTIVATE_AND_RESET_KEY};
|
||||
private static final byte[] OP_CODE_RESET = new byte[]{OP_CODE_RESET_KEY};
|
||||
//private static final byte[] OP_CODE_REPORT_RECEIVED_IMAGE_SIZE = new byte[] { OP_CODE_PACKET_REPORT_RECEIVED_IMAGE_SIZE_KEY };
|
||||
private static final byte[] OP_CODE_PACKET_RECEIPT_NOTIF_REQ = new byte[]{OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY, 0x00, 0x00};
|
||||
|
||||
// UUIDs used by the DFU
|
||||
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;
|
||||
/**
|
||||
* Flag indicating whether the image size has been already transferred or not
|
||||
*/
|
||||
private boolean mImageSizeSent;
|
||||
|
||||
protected class LegacyBluetoothCallback extends BaseCustomBluetoothCallback {
|
||||
@Override
|
||||
protected void onPacketCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
|
||||
if (mImageSizeSent) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
|
||||
final int responseType = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
|
||||
|
||||
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:
|
||||
default:
|
||||
/*
|
||||
* If the DFU target device is in invalid state (f.e. the Init Packet is required but has not been selected), the target will send DFU_STATUS_INVALID_STATE error
|
||||
* for each firmware packet that was send. We are interested may ignore all but the first one.
|
||||
* After obtaining a remote DFU error the OP_CODE_RESET_KEY will be sent.
|
||||
*/
|
||||
if (mRemoteErrorOccurred)
|
||||
break;
|
||||
final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 2);
|
||||
if (status != DFU_STATUS_SUCCESS)
|
||||
mRemoteErrorOccurred = true;
|
||||
|
||||
handleNotification(gatt, characteristic);
|
||||
break;
|
||||
}
|
||||
notifyLock();
|
||||
}
|
||||
}
|
||||
|
||||
LegacyDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream, packetsBeforeNotification);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BluetoothGattCallback getGattCallback() {
|
||||
return new LegacyBluetoothCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getControlPointCharacteristicUUID() {
|
||||
return DFU_CONTROL_POINT_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getPacketCharacteristicUUID() {
|
||||
return DFU_PACKET_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getDfuServiceUUID() {
|
||||
return DFU_SERVICE_UUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets number of data packets that will be send before the notification will be received.
|
||||
*
|
||||
* @param data control point data packet
|
||||
* @param value number of packets before receiving notification. If this value is 0, then the notification of packet receipt will be disabled by the DFU target.
|
||||
*/
|
||||
private void setNumberOfPackets(final byte[] data, final int value) {
|
||||
data[1] = (byte) (value & 0xFF);
|
||||
data[2] = (byte) ((value >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the response received is valid and returns the status code.
|
||||
*
|
||||
* @param response the response received from the DFU device.
|
||||
* @param request the expected Op Code
|
||||
* @return the status code
|
||||
* @throws UnknownResponseException if response was not valid
|
||||
*/
|
||||
private int getStatusCode(final byte[] response, final int request) throws UnknownResponseException {
|
||||
if (response == null || response.length != 3 || response[0] != OP_CODE_RESPONSE_CODE_KEY || response[1] != request || response[2] < 1 || response[2] > 6)
|
||||
throw new UnknownResponseException("Invalid response received", response, request);
|
||||
return response[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// If the DFU Version characteristic is not available we return 0.
|
||||
if (characteristic == null)
|
||||
return 0;
|
||||
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
|
||||
logi("Reading DFU version number...");
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Reading DFU version number...");
|
||||
|
||||
characteristic.setValue((byte[]) null);
|
||||
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.readCharacteristic(" + characteristic.getUuid() + ")");
|
||||
gatt.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) {
|
||||
mRequestCompleted = false;
|
||||
mLock.wait();
|
||||
}
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
|
||||
// The version is a 16-bit unsigned int
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 {
|
||||
final boolean reset = value[0] == OP_CODE_RESET_KEY || value[0] == OP_CODE_ACTIVATE_AND_RESET_KEY;
|
||||
writeOpCode(gatt, 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.
|
||||
*
|
||||
* @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,
|
||||
UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mImageSizeSent = 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);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mImageSizeSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Writes the Soft Device, Bootloader and Application image sizes to the characteristic. Soft Device and Bootloader update is supported since Soft Device s110 v7.0.0.
|
||||
* 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.
|
||||
* </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
|
||||
* @param appImageSize the Application image size in bytes
|
||||
* @throws DeviceDisconnectedException
|
||||
* @throws DfuException
|
||||
* @throws UploadAbortedException
|
||||
*/
|
||||
private void writeImageSize(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int softDeviceImageSize, final int bootloaderImageSize, final int appImageSize)
|
||||
throws DeviceDisconnectedException, DfuException, UploadAbortedException {
|
||||
mReceivedData = null;
|
||||
mError = 0;
|
||||
mImageSizeSent = true;
|
||||
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
characteristic.setValue(new byte[12]);
|
||||
characteristic.setValue(softDeviceImageSize, BluetoothGattCharacteristic.FORMAT_UINT32, 0);
|
||||
characteristic.setValue(bootloaderImageSize, BluetoothGattCharacteristic.FORMAT_UINT32, 4);
|
||||
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);
|
||||
|
||||
// We have to wait for confirmation
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
while ((mImageSizeSent && mConnectionState == STATE_CONNECTED_AND_READY && mError == 0 && !mAborted) || mPaused)
|
||||
mLock.wait();
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
loge("Sleeping interrupted", e);
|
||||
}
|
||||
if (mAborted)
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*************************************************************************************************************************************************
|
||||
* 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.BluetoothGattCallback;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/* package */ class SecureDfuImpl extends BaseCustomDfuImpl {
|
||||
|
||||
SecureDfuImpl(final DfuBaseService service, final InputStream firmwareStream, final InputStream initPacketStream, final int packetsBeforeNotification) {
|
||||
super(service, firmwareStream, initPacketStream, packetsBeforeNotification);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BluetoothGattCallback getGattCallback() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -170,7 +170,7 @@ 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:
|
||||
return "REMOTE DFU INVALID STATE";
|
||||
|
@ -183,7 +183,7 @@ public class GattError {
|
|||
case DfuBaseService.DFU_STATUS_OPERATION_FAILED:
|
||||
return "REMOTE DFU OPERATION FAILED";
|
||||
}
|
||||
}
|
||||
}*/ // TODO
|
||||
return "UNKNOWN (" + error + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
<string name="dfu_status_connecting_msg">Connecting to %s…</string>
|
||||
<string name="dfu_status_starting_msg">Initializing DFU process…</string>
|
||||
<string name="dfu_status_switching_to_dfu_msg">Waiting for bootloader…</string>
|
||||
<string name="dfu_status_uploading_components_msg">Transmitting components to %s…</string>
|
||||
<string name="dfu_status_uploading_msg">Transmitting application to %s…</string>
|
||||
<string name="dfu_status_uploading_msg">Transmitting firmware to %s…</string>
|
||||
<string name="dfu_status_validating_msg">Validating…</string>
|
||||
<string name="dfu_status_completed_msg">Application has been send successfully.</string>
|
||||
<string name="dfu_status_aborted_msg">Application upload has been canceled.</string>
|
||||
|
|
Loading…
Reference in New Issue