diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 51114e7d..bcb1c4e7 100755
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -128,6 +128,22 @@
"message": "Failed to close serial port"
},
+ "usbDeviceOpened": {
+ "message": "USB device successfully opened with ID: $1"
+ },
+ "usbDeviceOpenFail": {
+ "message": "Failed to open USB device!"
+ },
+ "usbDeviceClosed": {
+ "message": "USB device successfully closed"
+ },
+ "usbDeviceCloseFail": {
+ "message": "Failed to close USB device"
+ },
+ "usbDeviceUdevNotice": {
+ "message": "Are udev rules installed correctly? See docs for instructions"
+ },
+
"noConfigurationReceived": {
"message": "No configuration received within 10 seconds, communication failed"
},
@@ -190,26 +206,17 @@
"message": "Cycle Time:"
},
- "please_grant_usb_permissions": {
- "message": "Please click on \"Request Optional Permissions\" button to grant application required USB access"
+ "dfu_connect_message": {
+ "message": "Please use the Firmware Flasher to access DFU devices"
},
- "usb_permissions_granted": {
- "message": "Optional USB permissions granted"
+ "dfu_erased_kilobytes": {
+ "message": "Erased $1 kB of flash successfully"
},
"eeprom_saved_ok": {
"message": "EEPROM saved"
},
- "default_optional_permissions_head": {
- "message": "Optional USB Permissions"
- },
- "default_optional_permissions_text": {
- "message": "Due to addition of Naze32PRO to the supported hardware family, Configurator requires USB access to allow firmware flashing via DFU"
- },
- "default_request_optional_permissions": {
- "message": "Request Optional Permissions"
- },
"defaultWelcomeText": {
"message": "Welcome to Cleanflight - Configurator, a utility designed to simplify updating, configuring and tuning of your flight controller.
The application supports all hardware that can run cleanflight (SPRacingF3, Vortex, Sparky, DoDo, CC3D/EVO, Air Hero 32, Flip32/+/Deluxe, DragonFly32, CJMCU Microquad, Chebuzz F3, STM32F3Discovery, Hermit, Naze32 Tricopter Frame, Skyline32, Naze/32/Mini/Pro/Blackbox etc)
The firmware source code can be downloaded from here
The newest binary firmware image is available here, development builds available here
Latest CP210x Drivers can be downloaded from here
"
},
@@ -1192,4 +1199,4 @@
"controlAxisAux8": {
"message": "AUX 8"
}
-}
\ No newline at end of file
+}
diff --git a/js/gui.js b/js/gui.js
index 0f21ed35..bf7c142b 100644
--- a/js/gui.js
+++ b/js/gui.js
@@ -10,7 +10,6 @@ var GUI_control = function () {
this.active_tab;
this.tab_switch_in_progress = false;
this.operating_system;
- this.optional_usb_permissions = false; // controlled by usb permissions code
this.interval_array = [];
this.timeout_array = [];
this.defaultAllowedTabsWhenDisconnected = [
diff --git a/js/port_handler.js b/js/port_handler.js
index 88e022b3..6a053cad 100755
--- a/js/port_handler.js
+++ b/js/port_handler.js
@@ -1,9 +1,14 @@
'use strict';
+var usbDevices = {
+ STM32DFU: {'vendorId': 1155, 'productId': 57105}
+};
+
var PortHandler = new function () {
this.initial_ports = false;
this.port_detected_callbacks = [];
this.port_removed_callbacks = [];
+ this.dfu_available = false;
};
PortHandler.initialize = function () {
@@ -131,30 +136,32 @@ PortHandler.check = function () {
self.initial_ports = current_ports;
}
- if (GUI.optional_usb_permissions) {
- check_usb_devices();
- }
+ self.check_usb_devices();
GUI.updateManualPortVisibility();
setTimeout(function () {
self.check();
}, 250);
});
+};
- function check_usb_devices() {
- chrome.usb.getDevices(usbDevices.STM32DFU, function (result) {
- if (result.length) {
- if (!$("div#port-picker #port [value='DFU']").length) {
- $('div#port-picker #port').append('');
- $('div#port-picker #port').val('DFU');
- }
- } else {
- if ($("div#port-picker #port [value='DFU']").length) {
- $("div#port-picker #port [value='DFU']").remove();
- }
+PortHandler.check_usb_devices = function (callback) {
+ chrome.usb.getDevices(usbDevices.STM32DFU, function (result) {
+ if (result.length) {
+ if (!$("div#port-picker #port [value='DFU']").length) {
+ $('div#port-picker #port').append($('', {value: "DFU", text: "DFU", data: {isDFU: true}}));
+ $('div#port-picker #port').val('DFU');
}
- });
- }
+ self.dfu_available = true;
+ } else {
+ if ($("div#port-picker #port [value='DFU']").length) {
+ $("div#port-picker #port [value='DFU']").remove();
+ }
+ self.dfu_available = false;
+ }
+
+ if(callback) callback(self.dfu_available);
+ });
};
PortHandler.update_port_select = function (ports) {
diff --git a/js/protocols/stm32.js b/js/protocols/stm32.js
index e9bdfb5b..c9d4da96 100644
--- a/js/protocols/stm32.js
+++ b/js/protocols/stm32.js
@@ -100,14 +100,25 @@ STM32_protocol.prototype.connect = function (port, baud, hex, options, callback)
serial.send(bufferOut, function () {
serial.disconnect(function (result) {
if (result) {
- serial.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
- if (openInfo) {
- self.initialize();
- } else {
- GUI.connect_lock = false;
- GUI.log('Failed to open serial port');
- }
- });
+ // delay to allow board to boot in bootloader mode
+ // required to detect if a DFU device appears
+ setTimeout(function() {
+ // refresh device list
+ PortHandler.check_usb_devices(function(dfu_available) {
+ if(dfu_available) {
+ STM32DFU.connect(usbDevices.STM32DFU, hex, options);
+ } else {
+ serial.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
+ if (openInfo) {
+ self.initialize();
+ } else {
+ GUI.connect_lock = false;
+ GUI.log('Failed to open serial port');
+ }
+ });
+ }
+ });
+ }, 1000);
} else {
GUI.connect_lock = false;
}
diff --git a/js/protocols/stm32usbdfu.js b/js/protocols/stm32usbdfu.js
index 90c60ad0..604394bc 100644
--- a/js/protocols/stm32usbdfu.js
+++ b/js/protocols/stm32usbdfu.js
@@ -61,13 +61,26 @@ var STM32DFU_protocol = function () {
dfuUPLOAD_IDLE: 9, // The device is processing an upload operation. Expecting DFU_UPLOAD requests.
dfuERROR: 10 // An error has occurred. Awaiting the DFU_CLRSTATUS request.
};
+
+ // Assume 2 kB page size (STM32F303)
+ // Cannot read chip ID using DFU protocol and Chrome doesn't provide the interface
+ // description string with flash page size information (at least on Linux anyway)
+ this.page_size = 2048;
};
-STM32DFU_protocol.prototype.connect = function (device, hex, callback) {
+STM32DFU_protocol.prototype.connect = function (device, hex, options, callback) {
var self = this;
self.hex = hex;
self.callback = callback;
+ self.options = {
+ erase_chip: false
+ };
+
+ if (options.erase_chip) {
+ self.options.erase_chip = true;
+ }
+
// reset and set some variables before we start
self.upload_time_start = new Date().getTime();
self.verify_hex = [];
@@ -89,12 +102,35 @@ STM32DFU_protocol.prototype.connect = function (device, hex, callback) {
});
};
+STM32DFU_protocol.prototype.checkChromeError = function() {
+ if (chrome.runtime.lastError) {
+ if(chrome.runtime.lastError.message)
+ console.log(chrome.runtime.lastError.message);
+ else
+ console.log(chrome.runtime.lastError);
+
+ return true;
+ }
+
+ return false;
+}
+
STM32DFU_protocol.prototype.openDevice = function (device) {
var self = this;
chrome.usb.openDevice(device, function (handle) {
+ if(self.checkChromeError()) {
+ console.log('Failed to open USB device!');
+ GUI.log(chrome.i18n.getMessage('usbDeviceOpenFail'));
+ if(GUI.operating_system === 'Linux') {
+ GUI.log(chrome.i18n.getMessage('usbDeviceUdevNotice'));
+ }
+ return;
+ }
+
self.handle = handle;
+ GUI.log(chrome.i18n.getMessage('usbDeviceOpened', handle.handle.toString()));
console.log('Device opened with Handle ID: ' + handle.handle);
self.claimInterface(0);
});
@@ -104,6 +140,12 @@ STM32DFU_protocol.prototype.closeDevice = function () {
var self = this;
chrome.usb.closeDevice(this.handle, function closed() {
+ if(self.checkChromeError()) {
+ console.log('Failed to close USB device!');
+ GUI.log(chrome.i18n.getMessage('usbDeviceCloseFail'));
+ }
+
+ GUI.log(chrome.i18n.getMessage('usbDeviceClosed'));
console.log('Device closed with Handle ID: ' + self.handle.handle);
self.handle = null;
@@ -139,6 +181,8 @@ STM32DFU_protocol.prototype.resetDevice = function (callback) {
};
STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback) {
+ var self = this;
+
if (direction == 'in') {
// data is ignored
chrome.usb.controlTransfer(this.handle, {
@@ -150,6 +194,9 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu
'index': _interface,
'length': length
}, function (result) {
+ if(self.checkChromeError()) {
+ console.log('USB transfer failed!');
+ }
if (result.resultCode) console.log(result.resultCode);
var buf = new Uint8Array(result.data);
@@ -174,6 +221,9 @@ STM32DFU_protocol.prototype.controlTransfer = function (direction, request, valu
'index': _interface,
'data': arrayBuf
}, function (result) {
+ if(self.checkChromeError()) {
+ console.log('USB transfer failed!');
+ }
if (result.resultCode) console.log(result.resultCode);
callback(result);
@@ -217,7 +267,7 @@ STM32DFU_protocol.prototype.loadAddress = function (address, callback) {
if (data[4] == self.state.dfuDNLOAD_IDLE) {
callback(data);
} else {
- console.log('Failed to execure address load');
+ console.log('Failed to execute address load');
self.upload_procedure(99);
}
});
@@ -256,32 +306,86 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
});
break;
case 2:
- // full chip erase
- console.log('Executing global chip erase');
- $('span.progressLabel').text('Erasing ...');
+ // erase
+ if (self.options.erase_chip) {
+ // full chip erase
+ console.log('Executing global chip erase');
+ $('span.progressLabel').text('Erasing ...');
- self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x41], function () {
- self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
- if (data[4] == self.state.dfuDNBUSY) { // completely normal
- var delay = data[1] | (data[2] << 8) | (data[3] << 16);
+ self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x41], function () {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
+ if (data[4] == self.state.dfuDNBUSY) { // completely normal
+ var delay = data[1] | (data[2] << 8) | (data[3] << 16);
- setTimeout(function () {
- self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
- if (data[4] == self.state.dfuDNLOAD_IDLE) {
- self.upload_procedure(4);
- } else {
- console.log('Failed to execute global chip erase');
- self.upload_procedure(99);
- }
- });
- }, delay);
- } else {
- console.log('Failed to initiate global chip erase');
- self.upload_procedure(99);
- }
+ setTimeout(function () {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
+ if (data[4] == self.state.dfuDNLOAD_IDLE) {
+ self.upload_procedure(4);
+ } else {
+ console.log('Failed to execute global chip erase');
+ self.upload_procedure(99);
+ }
+ });
+ }, delay);
+ } else {
+ console.log('Failed to initiate global chip erase');
+ self.upload_procedure(99);
+ }
+ });
});
- });
+ } else {
+ // local erase
+
+ var max_address = self.hex.data[self.hex.data.length - 1].address + self.hex.data[self.hex.data.length - 1].bytes - 0x8000000,
+ erase_pages_n = Math.ceil(max_address / self.page_size),
+ page = 0;
+
+ $('span.progressLabel').text('Erasing ...');
+ console.log('Executing local chip erase');
+ console.log('Erasing. page: 0x00 - 0x' + erase_pages_n.toString(16));
+
+ var erase_page = function() {
+ var page_addr = page * self.page_size + 0x8000000;
+ var cmd = [0x41, page_addr & 0xff, (page_addr >> 8) & 0xff, (page_addr >> 16) & 0xff, (page_addr >> 24) & 0xff];
+
+ self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, cmd, function () {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
+ if (data[4] == self.state.dfuDNBUSY) { // completely normal
+ var delay = data[1] | (data[2] << 8) | (data[3] << 16);
+
+ setTimeout(function () {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function (data) {
+ if (data[4] == self.state.dfuDNLOAD_IDLE) {
+ // update progress bar
+ self.progress_bar_e.val((page + 1) / erase_pages_n * 100);
+ page++;
+
+ if(page == erase_pages_n) {
+ console.log("Erase: complete");
+ GUI.log(chrome.i18n.getMessage('dfu_erased_kilobytes', (erase_pages_n * self.page_size / 1024).toString()));
+ self.upload_procedure(4);
+ }
+ else
+ erase_page();
+ } else {
+ console.log('Failed to erase page 0x' + self.current_page.toString(16));
+ self.upload_procedure(99);
+ }
+ });
+ }, delay);
+ } else {
+ console.log('Failed to initiate page erase, page 0x' + self.current_page.toString(16));
+ self.upload_procedure(99);
+ }
+ });
+ });
+ };
+
+ // start
+ erase_page();
+ }
break;
+
case 4:
// upload
// we dont need to clear the state as we are already using DFU_DNLOAD
@@ -296,9 +400,6 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
var bytes_flashed_total = 0; // used for progress bar
var wBlockNum = 2; // required by DFU
- // start
- self.loadAddress(address, write);
-
var write = function () {
if (bytes_flashed < self.hex.data[flashing_block].bytes) {
var bytes_to_write = ((bytes_flashed + 2048) <= self.hex.data[flashing_block].bytes) ? 2048 : (self.hex.data[flashing_block].bytes - bytes_flashed);
@@ -353,6 +454,10 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
}
}
}
+
+ // start
+ self.loadAddress(address, write);
+
break;
case 5:
// verify
@@ -463,13 +568,13 @@ STM32DFU_protocol.prototype.upload_procedure = function (step) {
});
}
- // start
- clear_before_leave();
break;
case 99:
// cleanup
self.releaseInterface(0);
+ GUI.connect_lock = false;
+
var timeSpent = new Date().getTime() - self.upload_time_start;
console.log('Script finished after: ' + (timeSpent / 1000) + ' seconds');
diff --git a/js/serial_backend.js b/js/serial_backend.js
index cc0d1e33..28e95866 100755
--- a/js/serial_backend.js
+++ b/js/serial_backend.js
@@ -3,12 +3,19 @@
$(document).ready(function () {
GUI.updateManualPortVisibility = function(){
- if ($('div#port-picker #port option:selected').data().isManual) {
+ var selected_port = $('div#port-picker #port option:selected');
+ if (selected_port.data().isManual) {
$('#port-override-option').show();
}
else {
$('#port-override-option').hide();
}
+ if (selected_port.data().isDFU) {
+ $('select#baud').hide();
+ }
+ else {
+ $('select#baud').show();
+ }
};
GUI.updateManualPortVisibility();
@@ -33,8 +40,10 @@ $(document).ready(function () {
var selected_port = $('div#port-picker #port option:selected').data().isManual ?
$('#port-override').val() :
String($('div#port-picker #port').val());
-
- if (selected_port != '0' && selected_port != 'DFU') {
+ if (selected_port === 'DFU') {
+ GUI.log(chrome.i18n.getMessage('dfu_connect_message'));
+ }
+ else if (selected_port != '0') {
if (!clicks) {
console.log('Connecting to: ' + selected_port);
GUI.connecting_to = selected_port;
diff --git a/js/usb.js b/js/usb.js
deleted file mode 100644
index 4833cce4..00000000
--- a/js/usb.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-
-var usbDevices = {
- STM32DFU: {'vendorId': 1155, 'productId': 57105}
-};
-var usbPermissions = {permissions: [{'usbDevices': [usbDevices.STM32DFU]}]};
-
-function check_usb_permissions(callback) {
- chrome.permissions.contains(usbPermissions, function (result) {
- if (result) {
- GUI.optional_usb_permissions = true;
- } else {
- console.log('Optional USB permissions: missing');
- GUI.log(chrome.i18n.getMessage('please_grant_usb_permissions'));
-
- // display optional usb permissions request box
- $('div.optional_permissions').show();
-
- // UI hooks
- document.getElementById("requestOptionalPermissions").addEventListener('click', function () {
- chrome.permissions.request(usbPermissions, function (result) {
- if (result) {
- GUI.log(chrome.i18n.getMessage('usb_permissions_granted'));
- $('div.optional_permissions').hide();
-
- GUI.optional_usb_permissions = true;
- }
- });
- });
- }
-
- if (callback) {
- callback();
- }
- });
-}
\ No newline at end of file
diff --git a/main.html b/main.html
index fb9bc4b4..93d2cc0b 100755
--- a/main.html
+++ b/main.html
@@ -44,7 +44,6 @@
-
diff --git a/manifest.json b/manifest.json
index 3051e9bd..3d84fc91 100644
--- a/manifest.json
+++ b/manifest.json
@@ -32,10 +32,7 @@
"fileSystem.write",
"fileSystem.retainEntries",
"notifications",
- "alwaysOnTopWindows"
- ],
-
- "optional_permissions": [
+ "alwaysOnTopWindows",
{"usbDevices": [
{"vendorId": 1155, "productId": 57105}
]}
diff --git a/tabs/firmware_flasher.js b/tabs/firmware_flasher.js
index a7e7e940..cc2ab8ae 100755
--- a/tabs/firmware_flasher.js
+++ b/tabs/firmware_flasher.js
@@ -307,10 +307,15 @@ TABS.firmware_flasher.initialize = function (callback) {
if (!$(this).hasClass('locked')) {
if (!GUI.connect_lock) { // button disabled while flashing is in progress
if (parsed_hex != false) {
+ var options = {};
+
+ if ($('input.erase_chip').is(':checked')) {
+ options.erase_chip = true;
+ }
+
if (String($('div#port-picker #port').val()) != 'DFU') {
if (String($('div#port-picker #port').val()) != '0') {
- var options = {},
- port = String($('div#port-picker #port').val()),
+ var port = String($('div#port-picker #port').val()),
baud;
switch (GUI.operating_system) {
@@ -332,10 +337,6 @@ TABS.firmware_flasher.initialize = function (callback) {
options.reboot_baud = parseInt($('div#port-picker #baud').val());
}
- if ($('input.erase_chip').is(':checked')) {
- options.erase_chip = true;
- }
-
if ($('input.flash_manual_baud').is(':checked')) {
baud = parseInt($('#flash_manual_baud_rate').val());
}
@@ -347,7 +348,7 @@ TABS.firmware_flasher.initialize = function (callback) {
GUI.log('Please select valid serial port');
}
} else {
- STM32DFU.connect(usbDevices.STM32DFU, parsed_hex);
+ STM32DFU.connect(usbDevices.STM32DFU, parsed_hex, options);
}
} else {
$('span.progressLabel').text(chrome.i18n.getMessage('firmwareFlasherFirmwareNotLoaded'));
diff --git a/tabs/landing.html b/tabs/landing.html
index 6184c4b6..1c1a45b6 100644
--- a/tabs/landing.html
+++ b/tabs/landing.html
@@ -1,11 +1,4 @@