diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index f202d4b30a..c9e9ef72d6 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -51,7 +51,7 @@
"message": "GPS"
},
"tab7": {
- "message": "Motor/Servo Outputs"
+ "message": "Motor Testing"
},
"tab8": {
"message": "Raw Sensor Data"
@@ -108,16 +108,35 @@
"statusbar_packet_error": {
"message": "Packet error:"
},
+ "statusbar_i2c_error": {
+ "message": "I2C error:"
+ },
"statusbar_cycle_time": {
"message": "Cycle Time:"
},
+ "please_grant_usb_permissions": {
+ "message": "Please click on \"Request Optional Permissions\" button to grant application required USB access"
+ },
+ "usb_permissions_granted": {
+ "message": "Optional USB permissions granted"
+ },
+
"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": "This application is a configuration utility for cleanflight, a 32 bit fork of the popular open source RC flight control firmware project MultiWii.
Application supports hardware that run cleanflight exclusively (acro naze, naze, afromini, flip32, flip32+, chebuzz f3, stm32f3discovery, naze32pro, etc)
The firmware source code can be downloaded from here
The newest binary firmware image is available here
"
+ "message": "This application is a configuration utility for cleanflight, a 32 bit fork of the popular open source RC flight control firmware project MultiWii.
Application supports hardware that run cleanflight (acro naze, naze, afromini, flip32, flip32+, chebuzz f3, stm32f3discovery, naze32pro, etc)
The firmware source code can be downloaded from here
The newest binary firmware image is available here
Latest CP210x Drivers can be downloaded from here
"
},
"defaultChangelogHead": {
"message": "Configurator - Changelog"
@@ -428,9 +447,6 @@
"message": "Signal Strength"
},
- "motorsBarInfo": {
- "message": "Bars on the left are representing raw PWM signal for ESCs.
Bars on the right are representing raw PWM signal for Servos.
"
- },
"motorsMaster": {
"message": "Master"
},
@@ -464,9 +480,6 @@
"loggingLogSize": {
"message": "Log Size:"
},
- "loggingKB": {
- "message": "$1 kB"
- },
"loggingButtonLogFile": {
"message": "Select Log File"
},
@@ -476,6 +489,12 @@
"loggingStop": {
"message": "Stop Logging"
},
+ "loggingBack": {
+ "message": "Leave Logging / Disconnect"
+ },
+ "loggingErrorNotConnected": {
+ "message": "You need to connect first"
+ },
"loggingErrorLogFile": {
"message": "Please select log file"
},
@@ -499,7 +518,7 @@
"message": "Progress:"
},
"firmwareFlasherNote": {
- "message": "If you are flashing board with baseflight already flashed (updating), leave this checkbox unchecked.
If you are flashing \"bare\" board with no firmware preloaded or you have bootloader pins shorted, check this box.
"
+ "message": "If you are flashing board with bootloader pins shorted/connected, check No reboot sequence.
If you want configuration data to be wiped, check Full Chip Erase
"
},
"firmwareFlasherNoReboot": {
"message": "No reboot sequence"
@@ -543,8 +562,8 @@
"firmwareFlasherFirmwareNotLoaded": {
"message": "Firmware not loaded"
},
- "firmwareFlasherFirmwareLoaded": {
- "message": "Firmware loaded, ready for flashing"
+ "firmwareFlasherLocalFirmwareLoaded": {
+ "message": "Local Firmware loaded, ready for flashing"
},
"firmwareFlasherHexCorrupted": {
"message": "HEX file appears to be corrupted"
diff --git a/background.js b/background.js
index cc73727ec2..a4c7bbdf93 100644
--- a/background.js
+++ b/background.js
@@ -1,12 +1,8 @@
/*
- resizable: false - Keep in mind this only disables the side/corner resizing via mouse, nothing more
- maxWidth / maxHeight - is defined to prevent application reaching maximized state through window manager
-
- We are setting Bounds through setBounds method after window was created because on linux setting Bounds as
- window.create property seemed to fail, probably because "previous" bounds was used instead according to docs.
-
- bounds - Size and position of the content in the window (excluding the titlebar).
If an id is also specified and a window with a matching id has been shown before, the remembered bounds of the window will be used instead.
+
+ Size calculation for innerBounds seems to be faulty, app was designed for 960x625, using arbitrary values to make innerBounds happy for now
+ arbitrary values do match the windows ui, how it will affect other OSs is currently unknown
*/
function start_app() {
chrome.app.window.create('main.html', {
@@ -19,6 +15,62 @@ function start_app() {
// connectionId is passed from the script side through the chrome.runtime.getBackgroundPage refference
// allowing us to automatically close the port when application shut down
+ // save connectionId in separate variable before app_window is destroyed
+ var connectionId = app_window.serial.connectionId;
+ var valid_connection = app_window.configuration_received;
+ var mincommand = app_window.MISC.mincommand;
+
+ if (connectionId > 0 && valid_connection) {
+ // code below is handmade MSP message (without pretty JS wrapper), it behaves exactly like MSP.send_message
+ // reset motors to default (mincommand)
+ var bufferOut = new ArrayBuffer(22);
+ var bufView = new Uint8Array(bufferOut);
+ var checksum = 0;
+
+ bufView[0] = 36; // $
+ bufView[1] = 77; // M
+ bufView[2] = 60; // <
+ bufView[3] = 16; // data length
+ bufView[4] = 214; // MSP_SET_MOTOR
+
+ checksum = bufView[3] ^ bufView[4];
+
+ for (var i = 0; i < 16; i += 2) {
+ bufView[i + 5] = mincommand & 0x00FF;
+ bufView[i + 6] = mincommand >> 8;
+
+ checksum ^= bufView[i + 5];
+ checksum ^= bufView[i + 6];
+ }
+
+ bufView[5 + 16] = checksum;
+
+ chrome.serial.send(connectionId, bufferOut, function(sendInfo) {
+ chrome.serial.disconnect(connectionId, function(result) {
+ console.log('SERIAL: Connection closed - ' + result);
+ });
+ });
+ } else if (connectionId > 0) {
+ chrome.serial.disconnect(connectionId, function(result) {
+ console.log('SERIAL: Connection closed - ' + result);
+ });
+ }
+ });
+ });
+
+ /* code belowis chrome 36+ ready, till this is enforced in manifest we have to use the old version
+ chrome.app.window.create('main.html', {
+ id: 'main-window',
+ frame: 'chrome',
+ innerBounds: {
+ minWidth: 974,
+ minHeight: 632
+ }
+ }, function(createdWindow) {
+ createdWindow.onClosed.addListener(function() {
+ // connectionId is passed from the script side through the chrome.runtime.getBackgroundPage refference
+ // allowing us to automatically close the port when application shut down
+
// save connectionId in separate variable before app_window is destroyed
var connectionId = app_window.serial.connectionId;
@@ -29,6 +81,7 @@ function start_app() {
}
});
});
+ */
}
chrome.app.runtime.onLaunched.addListener(function() {
diff --git a/changelog.html b/changelog.html
index 91ed65cb67..e54197ecdf 100644
--- a/changelog.html
+++ b/changelog.html
@@ -1,3 +1,34 @@
+07.06.2014
+
+ - Servos tab updated to support Cleanflight's cleaner implementation of channel forwarding
+
+ - Configurator reached 5000+ users on 07.03.2014
+ - Updated various text notes to make things clearer
+ - UI polish
+ - Various bugfixes
+
+ - Added more scale factors in the motor testing tab
+ - If application closes without disconnecting motors should spin down
+ - Bugfixes for CLI, Motor Testing & Logging tabs
+
+ - Experimental passthrough support for logging
+ - MSP_ANALOG support for logging
+ - Allow running motors (testing) while monitoring sensors
+ - Major UI changes in Motor Testing tab
+ - Tiny cosmetic changes
+ - Initial set of UI bugfixes for Chrome 36+
+
+ - Added I2C Error indicator to status bar
+ - Optimizations & bugfixes
+
- Configurator reached 4000+ users on 05.29.2014
diff --git a/js/data_storage.js b/js/data_storage.js
index 3af5498ac1..d97b743f93 100644
--- a/js/data_storage.js
+++ b/js/data_storage.js
@@ -38,20 +38,18 @@ var RC_tuning = {
throttle_EXPO: 0,
};
-var AUX_CONFIG = new Array();
-var AUX_CONFIG_values = new Array();
+var AUX_CONFIG = [];
+var AUX_CONFIG_values = [];
-var SERVO_CONFIG = new Array();
+var SERVO_CONFIG = [];
var SENSOR_DATA = {
gyroscope: [0, 0, 0],
accelerometer: [0, 0, 0],
magnetometer: [0, 0, 0],
altitude: 0,
- kinematicsX: 0.0,
- kinematicsY: 0.0,
- kinematicsZ: 0.0,
- debug: [0, 0, 0, 0]
+ kinematics: [0.0, 0.0, 0.0],
+ debug: [0, 0, 0, 0]
};
var MOTOR_DATA = new Array(8);
@@ -70,10 +68,10 @@ var GPS_DATA = {
update: 0,
// baseflight specific gps stuff
- chn: new Array(),
- svid: new Array(),
- quality: new Array(),
- cno: new Array()
+ chn: [],
+ svid: [],
+ quality: [],
+ cno: []
};
var ANALOG = {
diff --git a/js/gui.js b/js/gui.js
index 0933c3e6db..52b927c44a 100644
--- a/js/gui.js
+++ b/js/gui.js
@@ -4,6 +4,7 @@ var GUI_control = function() {
this.connected_to = false;
this.active_tab;
this.operating_system;
+ this.optional_usb_permissions = false; // controlled by usb permissions code
this.interval_array = [];
this.timeout_array = [];
@@ -226,38 +227,9 @@ GUI_control.prototype.tab_switch_cleanup = function(callback) {
if (callback) callback();
break;
case 'motor_outputs':
- GUI.interval_remove('motor_pull');
- GUI.interval_remove('status_pull');
+ GUI.interval_kill_all();
- // only enforce mincommand if necessary
- if (MOTOR_DATA != undefined) {
- var update = false;
-
- for (var i = 0; i < MOTOR_DATA.length; i++) {
- if (MOTOR_DATA[i] > MISC.mincommand) {
- update = true;
- break;
- }
- }
-
- if (update) {
- // send data to mcu
- var buffer_out = [];
-
- for (var i = 0; i < 8; i++) {
- buffer_out.push(lowByte(MISC.mincommand));
- buffer_out.push(highByte(MISC.mincommand));
- }
-
- MSP.send_message(MSP_codes.MSP_SET_MOTOR, buffer_out, false, function() {
- if (callback) callback();
- });
- } else {
- if (callback) callback();
- }
- } else {
- if (callback) callback();
- }
+ if (callback) callback();
break;
case 'sensors':
GUI.interval_kill_all();
@@ -292,7 +264,11 @@ GUI_control.prototype.tab_switch_cleanup = function(callback) {
}, 5000); // if we dont allow enough time to reboot, CRC of "first" command sent will fail, keep an eye for this one
});
break;
+ case 'logging':
+ GUI.interval_kill_all();
+ if (callback) callback();
+ break;
case 'firmware_flasher':
// this.interval_remove('factory_mode');
PortHandler.flush_callbacks();
@@ -302,7 +278,6 @@ GUI_control.prototype.tab_switch_cleanup = function(callback) {
if (callback) callback();
break;
-
default:
if (callback) callback();
}
diff --git a/js/msp.js b/js/msp.js
index e477e344f9..7f90b10de4 100644
--- a/js/msp.js
+++ b/js/msp.js
@@ -175,6 +175,7 @@ MSP.process_data = function(code, message_buffer, message_length) {
CONFIG.profile = data.getUint8(10);
sensor_status(CONFIG.activeSensors);
+ $('span.i2c-error').text(CONFIG.i2cError);
$('span.cycle-time').text(CONFIG.cycleTime);
break;
case MSP_codes.MSP_RAW_IMU:
@@ -232,9 +233,9 @@ MSP.process_data = function(code, message_buffer, message_length) {
GPS_DATA.update = data.getUint8(4);
break;
case MSP_codes.MSP_ATTITUDE:
- SENSOR_DATA.kinematicsX = data.getInt16(0, 1) / 10.0;
- SENSOR_DATA.kinematicsY = data.getInt16(2, 1) / 10.0;
- SENSOR_DATA.kinematicsZ = data.getInt16(4, 1);
+ SENSOR_DATA.kinematics[0] = data.getInt16(0, 1) / 10.0; // x
+ SENSOR_DATA.kinematics[1] = data.getInt16(2, 1) / 10.0; // y
+ SENSOR_DATA.kinematics[2] = data.getInt16(4, 1); // z
break;
case MSP_codes.MSP_ALTITUDE:
SENSOR_DATA.altitude = parseFloat((data.getInt32(0, 1) / 100.0).toFixed(2)); // correct scale factor
@@ -285,8 +286,7 @@ MSP.process_data = function(code, message_buffer, message_length) {
}
break;
case MSP_codes.MSP_BOX:
- // dump previous data (if there was any)
- AUX_CONFIG_values = new Array();
+ AUX_CONFIG_values = []; // empty the array as new data is coming in
var boxItems = data.byteLength / 2; // AUX 1-4, 2 bytes per boxItem
if (bit_check(CONFIG.capability, 5)) {
@@ -359,8 +359,7 @@ MSP.process_data = function(code, message_buffer, message_length) {
console.log(data);
break;
case MSP_codes.MSP_SERVO_CONF:
- // drop previous data
- SERVO_CONFIG = [];
+ SERVO_CONFIG = []; // empty the array as new data is coming in
for (var i = 0; i < 56; i += 7) {
var arr = {
diff --git a/js/port_handler.js b/js/port_handler.js
index e1efa5ce8d..f685b203eb 100644
--- a/js/port_handler.js
+++ b/js/port_handler.js
@@ -65,7 +65,7 @@ port_handler.prototype.check = function() {
if (port == result.last_used_port) {
console.log('Selecting last used port: ' + result.last_used_port);
- $('div#port-picker .port select').val(result.last_used_port);
+ $('div#port-picker #port').val(result.last_used_port);
}
});
} else {
@@ -98,9 +98,9 @@ port_handler.prototype.check = function() {
// select / highlight new port, if connected -> select connected port
if (!GUI.connected_to) {
- $('div#port-picker .port select').val(new_ports[0]);
+ $('div#port-picker #port').val(new_ports[0]);
} else {
- $('div#port-picker .port select').val(GUI.connected_to);
+ $('div#port-picker #port').val(GUI.connected_to);
}
// start connect procedure (if statement is valid)
@@ -131,21 +131,40 @@ port_handler.prototype.check = function() {
self.initial_ports = current_ports;
}
+ if (GUI.optional_usb_permissions) {
+ check_usb_devices();
+ }
+
self.main_timeout_reference = 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();
+ }
+ }
+ });
+ }
};
port_handler.prototype.update_port_select = function(ports) {
- $('div#port-picker .port select').html(''); // drop previous one
+ $('div#port-picker #port').html(''); // drop previous one
if (ports.length > 0) {
for (var i = 0; i < ports.length; i++) {
- $('div#port-picker .port select').append($("", {value: ports[i], text: ports[i]}));
+ $('div#port-picker #port').append($("", {value: ports[i], text: ports[i]}));
}
} else {
- $('div#port-picker .port select').append($("", {value: 0, text: 'No Ports'}));
+ $('div#port-picker #port').append($("", {value: 0, text: 'No Ports'}));
}
};
diff --git a/js/serial.js b/js/serial.js
index 3e7baafc0c..155b073427 100644
--- a/js/serial.js
+++ b/js/serial.js
@@ -1,8 +1,8 @@
var serial = {
- connectionId: -1,
- bitrate: 0,
- bytes_received: 0,
- bytes_sent: 0,
+ connectionId: -1,
+ bitrate: 0,
+ bytes_received: 0,
+ bytes_sent: 0,
transmitting: false,
output_buffer: [],
@@ -21,12 +21,54 @@ var serial = {
self.bytes_received += info.data.byteLength;
});
+ self.onReceiveError.addListener(function watch_for_on_receive_errors(info) {
+ console.error(info);
+ ga_tracker.sendEvent('Error', 'Serial', info.error);
+
+ switch (info.error) {
+ case 'system_error': // we might be able to recover from this one
+ chrome.serial.setPaused(self.connectionId, false, get_status);
+
+ function get_status() {
+ self.getInfo(crunch_status);
+ }
+
+ function crunch_status(info) {
+ if (!info.paused) {
+ console.log('SERIAL: Connection recovered from last onReceiveError');
+ ga_tracker.sendEvent('Error', 'Serial', 'recovered');
+ } else {
+ console.log('SERIAL: Connection did not recover from last onReceiveError, disconnecting');
+ GUI.log('Unrecoverable failure of serial connection, disconnecting...');
+ ga_tracker.sendEvent('Error', 'Serial', 'unrecoverable');
+
+ if (GUI.connected_to || GUI.connecting_to) {
+ $('a.connect').click();
+ } else {
+ self.disconnect();
+ }
+ }
+ }
+ break;
+ case 'timeout':
+ // TODO
+ break;
+ case 'device_lost':
+ // TODO
+ break;
+ case 'disconnected':
+ // TODO
+ break;
+ }
+ });
+
console.log('SERIAL: Connection opened with ID: ' + connectionInfo.connectionId + ', Baud: ' + connectionInfo.bitrate);
- callback(connectionInfo);
+ if (callback) callback(connectionInfo);
} else {
console.log('SERIAL: Failed to open serial port');
- callback(false);
+ ga_tracker.sendEvent('Error', 'Serial', 'FailedToOpen');
+ if (callback) callback(false);
}
});
},
@@ -40,11 +82,16 @@ var serial = {
self.onReceive.removeListener(self.onReceive.listeners[i]);
}
+ for (var i = (self.onReceiveError.listeners.length - 1); i >= 0; i--) {
+ self.onReceiveError.removeListener(self.onReceiveError.listeners[i]);
+ }
+
chrome.serial.disconnect(this.connectionId, function(result) {
if (result) {
console.log('SERIAL: Connection with ID: ' + self.connectionId + ' closed');
} else {
console.log('SERIAL: Failed to close connection with ID: ' + self.connectionId + ' closed');
+ ga_tracker.sendEvent('Error', 'Serial', 'FailedToClose');
}
console.log('SERIAL: Statistics - Sent: ' + self.bytes_sent + ' bytes, Received: ' + self.bytes_received + ' bytes');
@@ -52,7 +99,7 @@ var serial = {
self.connectionId = -1;
self.bitrate = 0;
- callback(result);
+ if (callback) callback(result);
});
},
getDevices: function(callback) {
@@ -65,6 +112,9 @@ var serial = {
callback(devices);
});
},
+ getInfo: function(callback) {
+ chrome.serial.getInfo(this.connectionId, callback);
+ },
setControlSignals: function(signals, callback) {
chrome.serial.setControlSignals(this.connectionId, signals, callback);
},
@@ -124,6 +174,25 @@ var serial = {
}
}
},
+ onReceiveError: {
+ listeners: [],
+
+ addListener: function(function_reference) {
+ var listener = chrome.serial.onReceiveError.addListener(function_reference);
+
+ this.listeners.push(function_reference);
+ },
+ removeListener: function(function_reference) {
+ for (var i = (this.listeners.length - 1); i >= 0; i--) {
+ if (this.listeners[i] == function_reference) {
+ chrome.serial.onReceiveError.removeListener(function_reference);
+
+ this.listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
empty_output_buffer: function() {
this.output_buffer = [];
this.transmitting = false;
diff --git a/js/serial_backend.js b/js/serial_backend.js
index f03d96a04d..9ddff2033d 100644
--- a/js/serial_backend.js
+++ b/js/serial_backend.js
@@ -1,16 +1,14 @@
var configuration_received = false;
-var CLI_active = false;
-var CLI_valid = false;
$(document).ready(function() {
$('div#port-picker a.connect').click(function() {
if (GUI.connect_lock != true) { // GUI control overrides the user control
var clicks = $(this).data('clicks');
- var selected_port = String($('div#port-picker .port select').val());
+ var selected_port = String($('div#port-picker #port').val());
var selected_baud = parseInt($('div#port-picker #baud').val());
- if (selected_port != '0') {
+ if (selected_port != '0' && selected_port != 'DFU') {
if (!clicks) {
console.log('Connecting to: ' + selected_port);
GUI.connecting_to = selected_port;
@@ -21,23 +19,22 @@ $(document).ready(function() {
serial.connect(selected_port, {bitrate: selected_baud}, onOpen);
} else {
- // Disable any active "data pulling" timer
+ GUI.timeout_kill_all();
GUI.interval_kill_all();
-
GUI.tab_switch_cleanup();
- GUI.timeout_remove('connecting');
serial.disconnect(onClosed);
GUI.connected_to = false;
// Reset various UI elements
- $('.software-version').html('0.0');
- $('span.cycle-time').html('0');
+ $('span.i2c-error').text(0);
+ $('span.cycle-time').text(0);
MSP.disconnect_cleanup();
PortUsage.reset();
configuration_received = false; // reset valid config received variable (used to block tabs while not connected properly)
+ MSP_pass_through = false;
// unlock port select & baud
$('div#port-picker #port').prop('disabled', false);
@@ -127,34 +124,39 @@ function onOpen(openInfo) {
serial.onReceive.addListener(read_serial);
- // disconnect after 10 seconds with error if we don't get IDENT data
- GUI.timeout_add('connecting', function() {
- if (!configuration_received) {
- GUI.log(chrome.i18n.getMessage('noConfigurationReceived'));
+ if (!MSP_pass_through) {
+ // disconnect after 10 seconds with error if we don't get IDENT data
+ GUI.timeout_add('connecting', function() {
+ if (!configuration_received) {
+ GUI.log(chrome.i18n.getMessage('noConfigurationReceived'));
- $('div#port-picker a.connect').click(); // disconnect
- }
- }, 10000);
-
- // request configuration data
- MSP.send_message(MSP_codes.MSP_UID, false, false, function() {
- GUI.log(chrome.i18n.getMessage('uniqueDeviceIdReceived', [CONFIG.uid[0].toString(16) + CONFIG.uid[1].toString(16) + CONFIG.uid[2].toString(16)]));
- MSP.send_message(MSP_codes.MSP_IDENT, false, false, function() {
- GUI.timeout_remove('connecting'); // kill connecting timer
-
- GUI.log(chrome.i18n.getMessage('firmwareVersion', [CONFIG.version]));
-
- if (CONFIG.version >= firmware_version_accepted) {
- configuration_received = true;
-
- $('div#port-picker a.connect').text(chrome.i18n.getMessage('disconnect')).addClass('active');
- $('#tabs li a:first').click();
- } else {
- GUI.log(chrome.i18n.getMessage('firmwareVersionNotSupported', [firmware_version_accepted]));
$('div#port-picker a.connect').click(); // disconnect
}
+ }, 10000);
+
+ // request configuration data
+ MSP.send_message(MSP_codes.MSP_UID, false, false, function() {
+ GUI.log(chrome.i18n.getMessage('uniqueDeviceIdReceived', [CONFIG.uid[0].toString(16) + CONFIG.uid[1].toString(16) + CONFIG.uid[2].toString(16)]));
+ MSP.send_message(MSP_codes.MSP_IDENT, false, false, function() {
+ GUI.timeout_remove('connecting'); // kill connecting timer
+
+ GUI.log(chrome.i18n.getMessage('firmwareVersion', [CONFIG.version]));
+
+ if (CONFIG.version >= firmware_version_accepted) {
+ configuration_received = true;
+
+ $('div#port-picker a.connect').text(chrome.i18n.getMessage('disconnect')).addClass('active');
+ $('#tabs li a:first').click();
+ } else {
+ GUI.log(chrome.i18n.getMessage('firmwareVersionNotSupported', [firmware_version_accepted]));
+ $('div#port-picker a.connect').click(); // disconnect
+ }
+ });
});
- });
+ } else {
+ $('div#port-picker a.connect').text(chrome.i18n.getMessage('disconnect')).addClass('active');
+ GUI.log('Connection opened in pass-through mode');
+ }
} else {
console.log('Failed to open serial port');
GUI.log(chrome.i18n.getMessage('serialPortOpenFail'));
@@ -179,10 +181,12 @@ function onClosed(result) {
}
function read_serial(info) {
- if (!CLI_active) {
+ if (!CLI_active && !MSP_pass_through) {
MSP.read(info);
- } else {
+ } else if (CLI_active) {
handle_CLI(info);
+ } else if (MSP_pass_through) { // needs to be verified, might be removed after pass_through is 100% deployed
+ MSP.read(info);
}
}
diff --git a/js/stm32.js b/js/stm32.js
index 004df70355..ca95a37629 100644
--- a/js/stm32.js
+++ b/js/stm32.js
@@ -42,17 +42,12 @@ var STM32_protocol = function() {
this.page_size = 0;
};
-// string = string .. duh
-STM32_protocol.prototype.GUI_status = function(string) {
- $('span.status').html(string);
-};
-
// no input parameters
STM32_protocol.prototype.connect = function(hex) {
var self = this;
self.hex = hex;
- var selected_port = String($('div#port-picker .port select').val());
+ var selected_port = String($('div#port-picker #port').val());
var baud = parseInt($('div#port-picker #baud').val());
if (selected_port != '0') {
@@ -61,11 +56,7 @@ STM32_protocol.prototype.connect = function(hex) {
switch (GUI.operating_system) {
case 'Windows':
- flashing_bitrate = 921600;
- break;
case 'MacOS':
- flashing_bitrate = 921600;
- break;
case 'ChromeOS':
case 'Linux':
case 'UNIX':
@@ -122,7 +113,7 @@ STM32_protocol.prototype.connect = function(hex) {
}
} else {
console.log('Please select valid serial port');
- STM32.GUI_status('Please select valid serial port');
+ GUI.log('Please select valid serial port');
}
};
@@ -151,7 +142,8 @@ STM32_protocol.prototype.initialize = function() {
self.upload_process_alive = false;
} else {
console.log('STM32 - timed out, programming failed ...');
- STM32.GUI_status('STM32 - timed out, programming: FAILED');
+ GUI.log('STM32 - timed out, programming: FAILED');
+ ga_tracker.sendEvent('Flashing', 'Programming', 'timeout');
// protocol got stuck, clear timer and disconnect
GUI.interval_remove('STM32_timeout');
@@ -159,7 +151,7 @@ STM32_protocol.prototype.initialize = function() {
// exit
self.upload_procedure(99);
}
- }, 1000);
+ }, 2000);
self.upload_procedure(1);
};
@@ -230,7 +222,7 @@ STM32_protocol.prototype.send = function(Array, bytes_to_read, callback) {
STM32_protocol.prototype.verify_response = function(val, data) {
if (val != data[0]) {
console.log('STM32 Communication failed, wrong response, expected: ' + val + ' received: ' + data[0]);
- STM32.GUI_status('STM32 Communication failed, wrong response, expected: ' + val + ' received: ' + data[0]);
+ GUI.log('STM32 Communication failed, wrong response, expected: ' + val + ' received: ' + data[0]);
// disconnect
this.upload_procedure(99);
@@ -338,6 +330,8 @@ STM32_protocol.prototype.upload_procedure = function(step) {
switch (step) {
case 1:
// initialize serial interface on the MCU side, auto baud rate settings
+ GUI.log('Contacting bootloader ...');
+
var send_counter = 0;
GUI.interval_add('stm32_initialize_mcu', function() { // 200 ms interval (just in case mcu was already initialized), we need to break the 2 bytes command requirement
self.send([0x7F], 1, function(reply) {
@@ -349,7 +343,7 @@ STM32_protocol.prototype.upload_procedure = function(step) {
self.upload_procedure(2);
} else {
GUI.interval_remove('stm32_initialize_mcu');
- STM32.GUI_status('STM32 Communication with bootloader failed');
+ GUI.log('Communication with bootloader failed');
// disconnect
self.upload_procedure(99);
@@ -396,10 +390,11 @@ STM32_protocol.prototype.upload_procedure = function(step) {
break;
case 4:
// erase memory
+ GUI.log('Erasing ...');
+
if (!$('input.erase_chip').is(':checked')) {
// EXPERIMENTAL
console.log('Executing local erase (only needed pages)');
- STM32.GUI_status('Erasing');
self.send([self.command.erase, 0xBC], 1, function(reply) { // 0x43 ^ 0xFF
if (self.verify_response(self.status.ACK, reply)) {
@@ -419,9 +414,6 @@ STM32_protocol.prototype.upload_procedure = function(step) {
self.send(buff, 1, function(reply) {
if (self.verify_response(self.status.ACK, reply)) {
console.log('Erasing: done');
- console.log('Writing data ...');
- STM32.GUI_status('Flashing ...');
-
// proceed to next step
self.upload_procedure(5);
}
@@ -430,16 +422,12 @@ STM32_protocol.prototype.upload_procedure = function(step) {
});
} else {
console.log('Executing global chip erase');
- STM32.GUI_status('Erasing');
self.send([self.command.erase, 0xBC], 1, function(reply) { // 0x43 ^ 0xFF
if (self.verify_response(self.status.ACK, reply)) {
self.send([0xFF, 0x00], 1, function(reply) {
if (self.verify_response(self.status.ACK, reply)) {
console.log('Erasing: done');
- console.log('Writing data ...');
- STM32.GUI_status('Flashing ...');
-
// proceed to next step
self.upload_procedure(5);
}
@@ -450,6 +438,9 @@ STM32_protocol.prototype.upload_procedure = function(step) {
break;
case 5:
// upload
+ console.log('Writing data ...');
+ GUI.log('Flashing ...');
+
var blocks = self.hex.data.length - 1;
var flashing_block = 0;
var address = self.hex.data[flashing_block].address;
@@ -484,7 +475,7 @@ STM32_protocol.prototype.upload_procedure = function(step) {
array_out[array_out.length - 1] = checksum; // checksum (last byte in the array_out array)
address += bytes_to_write;
- bytes_flashed_total += bytes_to_write
+ bytes_flashed_total += bytes_to_write;
self.send(array_out, 1, function(reply) {
if (self.verify_response(self.status.ACK, reply)) {
@@ -511,8 +502,6 @@ STM32_protocol.prototype.upload_procedure = function(step) {
} else {
// all blocks flashed
console.log('Writing: done');
- console.log('Verifying data ...');
- STM32.GUI_status('Verifying ...');
// proceed to next step
self.upload_procedure(6);
@@ -525,6 +514,9 @@ STM32_protocol.prototype.upload_procedure = function(step) {
break;
case 6:
// verify
+ console.log('Verifying data ...');
+ GUI.log('Verifying ...');
+
var blocks = self.hex.data.length - 1;
var reading_block = 0;
var address = self.hex.data[reading_block].address;
@@ -596,7 +588,8 @@ STM32_protocol.prototype.upload_procedure = function(step) {
if (verify) {
console.log('Programming: SUCCESSFUL');
- STM32.GUI_status('Programming: SUCCESSFUL');
+ GUI.log('Programming: SUCCESSFUL');
+ ga_tracker.sendEvent('Flashing', 'Programming', 'success');
// update progress bar
self.progress_bar_e.addClass('valid');
@@ -605,7 +598,8 @@ STM32_protocol.prototype.upload_procedure = function(step) {
self.upload_procedure(7);
} else {
console.log('Programming: FAILED');
- STM32.GUI_status('Programming: FAILED');
+ GUI.log('Programming: FAILED');
+ ga_tracker.sendEvent('Flashing', 'Programming', 'fail');
// update progress bar
self.progress_bar_e.addClass('invalid');
diff --git a/js/stm32dfu.js b/js/stm32dfu.js
new file mode 100644
index 0000000000..b66f552aee
--- /dev/null
+++ b/js/stm32dfu.js
@@ -0,0 +1,475 @@
+/*
+ USB DFU uses:
+ control transfers for communicating
+ recipient is interface
+ request type is class
+
+ Descriptors seems to be broken in current chrome.usb API implementation (writing this while using canary 37.0.2040.0
+
+ General rule to remember is that DFU doesn't like running specific operations while the device isn't in idle state
+ that being said, it seems that certain level of CLRSTATUS is required before running another type of operation for
+ example switching from DNLOAD to UPLOAD, etc, clearning the state so device is in dfuIDLE is highly recommended.
+*/
+
+var STM32DFU_protocol = function() {
+ this.hex; // ref
+ this.verify_hex;
+
+ this.handle = null; // connection handle
+
+ this.request = {
+ DETACH: 0x00, // OUT, Requests the device to leave DFU mode and enter the application.
+ DNLOAD: 0x01, // OUT, Requests data transfer from Host to the device in order to load them into device internal Flash. Includes also erase commands
+ UPLOAD: 0x02, // IN, Requests data transfer from device to Host in order to load content of device internal Flash into a Host file.
+ GETSTATUS: 0x03, // IN, Requests device to send status report to the Host (including status resulting from the last request execution and the state the device will enter immediately after this request).
+ CLRSTATUS: 0x04, // OUT, Requests device to clear error status and move to next step
+ GETSTATE: 0x05, // IN, Requests the device to send only the state it will enter immediately after this request.
+ ABORT: 0x06 // OUT, Requests device to exit the current state/operation and enter idle state immediately.
+ };
+
+ this.status = {
+ OK: 0x00, // No error condition is present.
+ errTARGET: 0x01, // File is not targeted for use by this device.
+ errFILE: 0x02, // File is for this device but fails some vendor-specific verification test
+ errWRITE: 0x03, // Device is unable to write memory.
+ errERASE: 0x04, // Memory erase function failed.
+ errCHECK_ERASED: 0x05, // Memory erase check failed.
+ errPROG: 0x06, // Program memory function failed.
+ errVERIFY: 0x07, // Programmed memory failed verification.
+ errADDRESS: 0x08, // Cannot program memory due to received address that is out of range.
+ errNOTDONE: 0x09, // Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
+ errFIRMWARE: 0x0A, // Device's firmware is corrupt. It cannot return to run-time (non-DFU) operations.
+ errVENDOR: 0x0B, // iString indicates a vendor-specific error.
+ errUSBR: 0x0C, // Device detected unexpected USB reset signaling.
+ errPOR: 0x0D, // Device detected unexpected power on reset.
+ errUNKNOWN: 0x0E, // Something went wrong, but the device does not know what it was.
+ errSTALLEDPKT: 0x0F // Device stalled an unexpected request.
+ };
+
+ this.state = {
+ appIDLE: 0, // Device is running its normal application.
+ appDETACH: 1, // Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset.
+ dfuIDLE: 2, // Device is operating in the DFU mode and is waiting for requests.
+ dfuDNLOAD_SYNC: 3, // Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
+ dfuDNBUSY: 4, // Device is programming a control-write block into its nonvolatile memories.
+ dfuDNLOAD_IDLE: 5, // Device is processing a download operation. Expecting DFU_DNLOAD requests.
+ dfuMANIFEST_SYNC: 6, // Device has received the final block of firmware from the host and is waiting for receipt of DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation phase and is waiting for receipt of DFU_GETSTATUS.
+ dfuMANIFEST: 7, // Device is in the Manifestation phase. (Not all devices will be able to respond to DFU_GETSTATUS when in this state.)
+ dfuMANIFEST_WAIT_RESET: 8, // Device has programmed its memories and is waiting for a USB reset or a power on reset. (Devices that must enter this state clear bitManifestationTolerant to 0.)
+ 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.
+ };
+};
+
+STM32DFU_protocol.prototype.connect = function(hex) {
+ var self = this;
+ self.hex = hex;
+
+ // reset and set some variables before we start
+ self.upload_time_start = microtime();
+ self.verify_hex = [];
+
+ // reset progress bar to initial state
+ self.progress_bar_e = $('.progress');
+ self.progress_bar_e.val(0);
+ self.progress_bar_e.removeClass('valid invalid');
+
+ chrome.usb.getDevices(usbDevices.STM32DFU, function(result) {
+ if (result.length) {
+ console.log('USB DFU detected with ID: ' + result[0].device);
+
+ self.openDevice(result[0]);
+ } else {
+ // TODO: throw some error
+ }
+ });
+};
+
+STM32DFU_protocol.prototype.openDevice = function(device) {
+ var self = this;
+
+ chrome.usb.openDevice(device, function(handle) {
+ self.handle = handle;
+
+ console.log('Device opened with Handle ID: ' + handle.handle);
+ self.claimInterface(0);
+ });
+};
+
+STM32DFU_protocol.prototype.closeDevice = function() {
+ var self = this;
+
+ chrome.usb.closeDevice(this.handle, function closed() {
+ console.log('Device closed with Handle ID: ' + self.handle.handle);
+
+ self.handle = null;
+ });
+};
+
+STM32DFU_protocol.prototype.claimInterface = function(interfaceNumber) {
+ var self = this;
+
+ chrome.usb.claimInterface(this.handle, interfaceNumber, function claimed() {
+ console.log('Claimed interface: ' + interfaceNumber);
+
+ self.upload_procedure(1);
+ });
+};
+
+STM32DFU_protocol.prototype.releaseInterface = function(interfaceNumber) {
+ var self = this;
+
+ chrome.usb.releaseInterface(this.handle, interfaceNumber, function released() {
+ console.log('Released interface: ' + interfaceNumber);
+
+ self.closeDevice();
+ });
+};
+
+STM32DFU_protocol.prototype.resetDevice = function(callback) {
+ chrome.usb.resetDevice(this.handle, function(result) {
+ console.log('Reset Device: ' + result);
+
+ if (callback) callback();
+ });
+};
+
+STM32DFU_protocol.prototype.controlTransfer = function(direction, request, value, interface, length, data, callback) {
+ if (direction == 'in') {
+ // data is ignored
+ chrome.usb.controlTransfer(this.handle, {
+ 'direction': 'in',
+ 'recipient': 'interface',
+ 'requestType': 'class',
+ 'request': request,
+ 'value': value,
+ 'index': interface,
+ 'length': length
+ }, function(result) {
+ if (result.resultCode) console.log(result.resultCode);
+
+ var buf = new Uint8Array(result.data);
+ callback(buf, result.resultCode);
+ });
+ } else {
+ // length is ignored
+ if (data) {
+ var arrayBuf = new ArrayBuffer(data.length);
+ var arrayBufView = new Uint8Array(arrayBuf);
+ arrayBufView.set(data);
+ } else {
+ var arrayBuf = new ArrayBuffer(0);
+ }
+
+ chrome.usb.controlTransfer(this.handle, {
+ 'direction': 'out',
+ 'recipient': 'interface',
+ 'requestType': 'class',
+ 'request': request,
+ 'value': value,
+ 'index': interface,
+ 'data': arrayBuf
+ }, function(result) {
+ if (result.resultCode) console.log(result.resultCode);
+
+ callback(result);
+ });
+ }
+};
+
+// routine calling DFU_CLRSTATUS until device is in dfuIDLE state
+STM32DFU_protocol.prototype.clearStatus = function(callback) {
+ var self = this;
+
+ function check_status() {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
+ if (data[4] == self.state.dfuIDLE) {
+ callback(data);
+ } else {
+ var delay = data[1] | (data[2] << 8) | (data[3] << 16);
+
+ setTimeout(clear_status, delay);
+ }
+ });
+ }
+
+ function clear_status() {
+ self.controlTransfer('out', self.request.CLRSTATUS, 0, 0, 0, 0, check_status);
+ }
+
+ check_status();
+};
+
+STM32DFU_protocol.prototype.loadAddress = function(address, callback) {
+ var self = this;
+
+ self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, [0x21, address, (address >> 8), (address >> 16), (address >> 24)], function() {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
+ if (data[4] == self.state.dfuDNBUSY) {
+ 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) {
+ callback(data);
+ } else {
+ console.log('Failed to execure address load');
+ self.upload_procedure(99);
+ }
+ });
+ }, delay);
+ } else {
+ console.log('Failed to request address load');
+ self.upload_procedure(99);
+ }
+ });
+ });
+};
+
+// first_array = usually hex_to_flash array
+// second_array = usually verify_hex array
+// result = true/false
+STM32DFU_protocol.prototype.verify_flash = function(first_array, second_array) {
+ for (var i = 0; i < first_array.length; i++) {
+ if (first_array[i] != second_array[i]) {
+ console.log('Verification failed on byte: ' + i + ' expected: 0x' + first_array[i].toString(16) + ' received: 0x' + second_array[i].toString(16));
+ return false;
+ }
+ }
+
+ console.log('Verification successful, matching: ' + first_array.length + ' bytes');
+
+ return true;
+};
+
+STM32DFU_protocol.prototype.upload_procedure = function(step) {
+ var self = this;
+
+ switch (step) {
+ case 1:
+ self.clearStatus(function() {
+ self.upload_procedure(2);
+ });
+ break;
+ case 2:
+ // full chip erase
+ console.log('Executing global chip erase');
+ GUI.log('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);
+
+ 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);
+ }
+ });
+ });
+ break;
+ case 4:
+ // upload
+ // we dont need to clear the state as we are already using DFU_DNLOAD
+ console.log('Writing data ...');
+ GUI.log('Flashing ...');
+
+ var blocks = self.hex.data.length - 1;
+ var flashing_block = 0;
+ var address = self.hex.data[flashing_block].address;
+
+ var bytes_flashed = 0;
+ var bytes_flashed_total = 0; // used for progress bar
+ var wBlockNum = 2; // required by DFU
+
+ // start
+ self.loadAddress(address, write);
+
+ function write() {
+ 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);
+
+ var data_to_flash = self.hex.data[flashing_block].data.slice(bytes_flashed, bytes_flashed + bytes_to_write);
+
+ address += bytes_to_write;
+ bytes_flashed += bytes_to_write;
+ bytes_flashed_total += bytes_to_write;
+
+ self.controlTransfer('out', self.request.DNLOAD, wBlockNum++, 0, 0, data_to_flash, function() {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
+ if (data[4] == self.state.dfuDNBUSY) {
+ 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(bytes_flashed_total / (self.hex.bytes_total * 2) * 100);
+
+ // flash another page
+ write();
+ } else {
+ console.log('Failed to write ' + bytes_to_write + 'bytes to 0x' + address.toString(16));
+ self.upload_procedure(99);
+ }
+ });
+ }, delay);
+ } else {
+ console.log('Failed to initiate write ' + bytes_to_write + 'bytes to 0x' + address.toString(16));
+ self.upload_procedure(99);
+ }
+ });
+ })
+ } else {
+ if (flashing_block < blocks) {
+ // move to another block
+ flashing_block++;
+
+ address = self.hex.data[flashing_block].address;
+ bytes_flashed = 0;
+ wBlockNum = 2;
+
+ self.loadAddress(address, write);
+ } else {
+ // all blocks flashed
+ console.log('Writing: done');
+
+ // proceed to next step
+ self.upload_procedure(5);
+ }
+ }
+ }
+ break;
+ case 5:
+ // verify
+ console.log('Verifying data ...');
+ GUI.log('Verifying ...');
+
+ var blocks = self.hex.data.length - 1;
+ var reading_block = 0;
+ var address = self.hex.data[reading_block].address;
+
+ var bytes_verified = 0;
+ var bytes_verified_total = 0; // used for progress bar
+ var wBlockNum = 2; // required by DFU
+
+ // initialize arrays
+ for (var i = 0; i <= blocks; i++) {
+ self.verify_hex.push([]);
+ }
+
+ // start
+ self.clearStatus(function() {
+ self.loadAddress(address, function() {
+ self.clearStatus(read);
+ });
+ });
+
+ function read() {
+ if (bytes_verified < self.hex.data[reading_block].bytes) {
+ var bytes_to_read = ((bytes_verified + 2048) <= self.hex.data[reading_block].bytes) ? 2048 : (self.hex.data[reading_block].bytes - bytes_verified);
+
+ self.controlTransfer('in', self.request.UPLOAD, wBlockNum++, 0, bytes_to_read, 0, function(data, code) {
+ for (var i = 0; i < data.length; i++) {
+ self.verify_hex[reading_block].push(data[i]);
+ }
+
+ address += bytes_to_read;
+ bytes_verified += bytes_to_read;
+ bytes_verified_total += bytes_to_read;
+
+ // update progress bar
+ self.progress_bar_e.val((self.hex.bytes_total + bytes_verified_total) / (self.hex.bytes_total * 2) * 100);
+
+ // verify another page
+ read();
+ });
+ } else {
+ if (reading_block < blocks) {
+ // move to another block
+ reading_block++;
+
+ address = self.hex.data[reading_block].address;
+ bytes_verified = 0;
+ wBlockNum = 2;
+
+ self.clearStatus(function() {
+ self.loadAddress(address, function() {
+ self.clearStatus(read);
+ });
+ });
+ } else {
+ // all blocks read, verify
+
+ var verify = true;
+ for (var i = 0; i <= blocks; i++) {
+ verify = self.verify_flash(self.hex.data[i].data, self.verify_hex[i]);
+
+ if (!verify) break;
+ }
+
+ if (verify) {
+ console.log('Programming: SUCCESSFUL');
+ GUI.log('Programming: SUCCESSFUL');
+ ga_tracker.sendEvent('Flashing', 'Programming', 'success');
+
+ // update progress bar
+ self.progress_bar_e.addClass('valid');
+
+ // proceed to next step
+ self.upload_procedure(6);
+ } else {
+ console.log('Programming: FAILED');
+ GUI.log('Programming: FAILED');
+ ga_tracker.sendEvent('Flashing', 'Programming', 'fail');
+
+ // update progress bar
+ self.progress_bar_e.addClass('invalid');
+
+ // disconnect
+ self.upload_procedure(99);
+ }
+ }
+ }
+ }
+ break;
+ case 6:
+ // jump to application code
+ var address = self.hex.data[0].address;
+
+ self.clearStatus(function() {
+ self.loadAddress(address, leave);
+ });
+
+ function leave() {
+ self.controlTransfer('out', self.request.DNLOAD, 0, 0, 0, 0, function() {
+ self.controlTransfer('in', self.request.GETSTATUS, 0, 0, 6, 0, function(data) {
+ self.upload_procedure(99);
+ });
+ });
+ }
+
+ // start
+ clear_before_leave();
+ break;
+ case 99:
+ // cleanup
+ console.log('Script finished after: ' + (microtime() - self.upload_time_start).toFixed(4) + ' seconds');
+
+ self.releaseInterface(0);
+ break;
+ }
+};
+
+// initialize object
+var STM32DFU = new STM32DFU_protocol();
\ No newline at end of file
diff --git a/js/usb.js b/js/usb.js
new file mode 100644
index 0000000000..1134658ae4
--- /dev/null
+++ b/js/usb.js
@@ -0,0 +1,32 @@
+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 5d857a29ae..05628ff1b8 100644
--- a/main.html
+++ b/main.html
@@ -25,6 +25,7 @@
+
@@ -32,6 +33,7 @@
+
@@ -121,6 +123,9 @@
- | AUX 1 | -AUX 2 | -AUX 3 | -AUX 4 | -AUX 5 | -AUX 6 | -AUX 7 | -AUX 8 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
-