1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-23 16:25:22 +03:00

Merge branch 'master' into rc_smoothing

This commit is contained in:
Sean M 2018-08-11 20:59:00 -04:00
commit f4ea605685
45 changed files with 18196 additions and 463 deletions

View file

@ -255,3 +255,62 @@
#changelog .log p {
margin-bottom: 20px;
}
/* privacy policy block */
#privacy_policy {
width: 500px;
height: 100%;
position: fixed;
right: -495px;
top: 0px;
}
#privacy_policy .wrapper {
height: 100%;
padding: 0 20px;
border-left: 5px solid #ffbb00;
overflow-y: auto;
display: none;
}
#privacy_policy .button {
transform: rotate(270deg);
top: 170px;
right: 450px;
position: absolute;
background: #ffbb00;
border-radius: 5px 5px 0 0;
border-bottom: none;
height: 30px;
}
#privacy_policy .button a {
display: block;
padding: 5px 10px;
width: 100px;
text-align: center;
color: #000;
}
#content.policy_open #privacy_policy {
right: 0px;
background: white;
}
#content.policy_open #privacy_policy .wrapper {
display: block;
}
/* privacy policy content */
#privacy_policy .policy ul {
margin: 5px 0 20px 10px;
}
#privacy_policy .policy li {
font-weight: normal;
margin-bottom: 5px;
}
#privacy_policy .policy p {
margin-bottom: 20px;
}

View file

@ -206,6 +206,22 @@
display: none;
}
.require-msc-supported {
display: none;
}
.tab-onboard_logging.msc-supported .require-msc-supported {
display: block;
}
.require-msc-not-ready {
display: none;
}
.tab-onboard_logging.msc-not-ready .require-msc-not-ready {
display: block;
}
@media only screen and (max-width: 1055px) , only screen and (max-device-width: 1055px) {
.tab-onboard_logging table thead tr:first-child {
font-size: 12px;
@ -300,4 +316,4 @@
pointer-events: none;
text-shadow: none;
opacity: 0.5;
}
}

View file

@ -402,13 +402,17 @@ button {
cursor: pointer;
}
.fontbuttons {
.fontpresets_wrapper {
display: inline-block;
position: absolute;
right: 1.2em;
top: .8em;
}
.fontpresets {
border: 1px solid #cccccc;
}
.tab-osd .switchable-field {
padding: 3px;
border: 1px solid transparent;

View file

@ -222,6 +222,7 @@
margin: 0px;
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
.tab-pid_tuning .gui_box {
@ -244,12 +245,10 @@
padding: 5px;
text-align: left;
border-right: 1px solid #ccc;
width: 12.5%;
}
.tab-pid_tuning .pid_titlebar th:first-child {
text-align: left;
width: 12.5%;
}
.tab-pid_tuning .pid_titlebar th:last-child {
@ -281,6 +280,10 @@
width: 33%;
}
.tab-pid_tuning table.compensation tr {
height: 25px;
}
.tab-pid_tuning table.compensation td {
width: 60%;
padding-left: 5px;
@ -289,12 +292,24 @@
.tab-pid_tuning table.compensation td:first-child {
width: 10%;
text-align: right;
}
.tab-pid_tuning table.compensation td:last-child {
width: 30%;
}
.tab-pid_tuning table.compensation .suboption {
margin-left: 40px;
}
.tab-pid_tuning table.compensation .suboption select{
text-align-last: left;
font-size: 1.1em;
color: darkslategrey;
padding-left: 5px;
}
.tab-pid_tuning .pidTuningFeatures td {
padding: 5px;
}
@ -320,7 +335,6 @@
.tab-pid_tuning table td {
padding: 1px;
padding-left: 5px;
width: 12.5%;
border-right: 1px solid #ccc;
}

156
src/js/Analytics.js Normal file
View file

@ -0,0 +1,156 @@
'use strict';
var Analytics = function (trackingId, userId, appName, appVersion, buildType, optOut, debugMode) {
this._trackingId = trackingId;
this.setOptOut(optOut);
this._googleAnalytics = googleAnalytics;
this._googleAnalytics.initialize(this._trackingId, {
storage: 'none',
clientId: userId,
debug: !!debugMode
});
// Make it work for the Chrome App:
this._googleAnalytics.set('forceSSL', true);
this._googleAnalytics.set('transport', 'xhr');
// Make it work for NW.js:
this._googleAnalytics.set('checkProtocolTask', null);
this._googleAnalytics.set('appName', appName);
this._googleAnalytics.set('appVersion', debugMode ? appVersion + '-debug' : appVersion);
this.EVENT_CATEGORIES = {
APPLICATION: 'Application',
FLIGHT_CONTROLLER: 'FlightController',
FIRMWARE: 'Firmware',
};
this.DATA = {
BOARD_TYPE: 'boardType',
API_VERSION: 'apiVersion',
FIRMWARE_TYPE: 'firmwareType',
FIRMWARE_VERSION: 'firmwareVersion',
FIRMWARE_NAME: 'firmwareName',
FIRMWARE_CHECKSUM: 'firmwareChecksum',
FIRMWARE_SOURCE: 'firmwareSource',
FIRMWARE_CHANNEL: 'firmwareChannel',
FIRMWARE_ERASE_ALL: 'firmwareEraseAll',
FIRMWARE_SIZE: 'firmwareSize',
MCU_ID: 'mcuId',
LOGGING_STATUS: 'loggingStatus',
LOG_SIZE: 'logSize',
};
this.DIMENSIONS = {
CONFIGURATOR_BUILD_TYPE: 1,
BOARD_TYPE: 2,
FIRMWARE_TYPE: 3,
FIRMWARE_VERSION: 4,
API_VERSION: 5,
FIRMWARE_NAME: 6,
FIRMWARE_SOURCE: 7,
FIRMWARE_ERASE_ALL: 8,
CONFIGURATOR_EXPERT_MODE: 9,
FIRMWARE_CHANNEL: 10,
LOGGING_STATUS: 11,
MCU_ID: 12,
};
this.METRICS = {
FIRMWARE_SIZE: 1,
LOG_SIZE: 2,
};
this.setDimension(this.DIMENSIONS.CONFIGURATOR_BUILD_TYPE, buildType);
this.resetFlightControllerData();
this.resetFirmwareData();
};
Analytics.prototype.setDimension = function (dimension, value) {
var dimensionName = 'dimension' + dimension;
this._googleAnalytics.custom(dimensionName, value);
}
Analytics.prototype.setMetric = function (metric, value) {
var metricName = 'metric' + metric;
this._googleAnalytics.custom(metricName, value);
}
Analytics.prototype.sendEvent = function (category, action, options) {
this._googleAnalytics.event(category, action, options);
}
Analytics.prototype.sendChangeEvents = function (category, changeList) {
for (var actionName in changeList) {
if (changeList.hasOwnProperty(actionName)) {
var actionValue = changeList[actionName];
if (actionValue !== undefined) {
this.sendEvent(category, actionName, { eventLabel: actionValue });
}
}
}
}
Analytics.prototype.sendAppView = function (viewName) {
this._googleAnalytics.screenview(viewName);
}
Analytics.prototype.sendTiming = function (category, timing, value) {
this._googleAnalytics.timing(category, timing, value);
}
Analytics.prototype.sendException = function (message) {
this._googleAnalytics.exception(message);
}
Analytics.prototype.setOptOut = function (optOut) {
window['ga-disable-' + this._trackingId] = !!optOut;
}
Analytics.prototype._rebuildFlightControllerEvent = function () {
this.setDimension(this.DIMENSIONS.BOARD_TYPE, this._flightControllerData[this.DATA.BOARD_TYPE]);
this.setDimension(this.DIMENSIONS.FIRMWARE_TYPE, this._flightControllerData[this.DATA.FIRMWARE_TYPE]);
this.setDimension(this.DIMENSIONS.FIRMWARE_VERSION, this._flightControllerData[this.DATA.FIRMWARE_VERSION]);
this.setDimension(this.DIMENSIONS.API_VERSION, this._flightControllerData[this.DATA.API_VERSION]);
this.setDimension(this.DIMENSIONS.LOGGING_STATUS, this._flightControllerData[this.DATA.LOGGING_STATUS]);
this.setDimension(this.DIMENSIONS.MCU_ID, this._flightControllerData[this.DATA.MCU_ID]);
this.setMetric(this.METRICS.LOG_SIZE, this._flightControllerData[this.DATA.LOG_SIZE]);
}
Analytics.prototype.setFlightControllerData = function (property, value) {
this._flightControllerData[property] = value;
this._rebuildFlightControllerEvent();
}
Analytics.prototype.resetFlightControllerData = function () {
this._flightControllerData = {};
this._rebuildFlightControllerEvent();
}
Analytics.prototype._rebuildFirmwareEvent = function () {
this.setDimension(this.DIMENSIONS.FIRMWARE_NAME, this._firmwareData[this.DATA.FIRMWARE_NAME]);
this.setDimension(this.DIMENSIONS.FIRMWARE_SOURCE, this._firmwareData[this.DATA.FIRMWARE_SOURCE]);
this.setDimension(this.DIMENSIONS.FIRMWARE_ERASE_ALL, this._firmwareData[this.DATA.FIRMWARE_ERASE_ALL]);
this.setDimension(this.DIMENSIONS.FIRMWARE_CHANNEL, this._firmwareData[this.DATA.FIRMWARE_CHANNEL]);
this.setMetric(this.METRICS.FIRMWARE_SIZE, this._firmwareData[this.DATA.FIRMWARE_SIZE]);
this._googleAnalytics.set('eventLabel', this._firmwareData[this.DATA.FIRMWARE_CHECKSUM]);
}
Analytics.prototype.setFirmwareData = function (property, value) {
this._firmwareData[property] = value;
this._rebuildFirmwareEvent();
}
Analytics.prototype.resetFirmwareData = function () {
this._firmwareData = {};
this._rebuildFirmwareEvent();
}

View file

@ -99,11 +99,16 @@ var Features = function (config) {
self._features = features;
self._featureMask = 0;
self._analyticsChanges = {};
};
Features.prototype.getMask = function () {
var self = this;
analytics.sendChangeEvents(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, self._analyticsChanges);
self._analyticsChanges = {};
return self._featureMask;
};
@ -127,6 +132,8 @@ Features.prototype.isEnabled = function (featureName) {
Features.prototype.generateElements = function (featuresElements) {
var self = this;
self._featureChanges = {};
var listElements = [];
for (var i = 0; i < self._features.length; i++) {
@ -191,29 +198,48 @@ Features.prototype.generateElements = function (featuresElements) {
}
};
Features.prototype.findFeatureByBit = function (bit) {
var self = this;
for (var i = 0; i < self._features.length; i++) {
if (self._features[i].bit == bit) {
return self._features[i];
}
}
}
Features.prototype.updateData = function (featureElement) {
var self = this;
if (featureElement.attr('type') === 'checkbox') {
var bit = featureElement.data('bit');
var featureValue;
if (featureElement.is(':checked')) {
self._featureMask = bit_set(self._featureMask, bit);
featureValue = 'On';
} else {
self._featureMask = bit_clear(self._featureMask, bit);
featureValue = 'Off';
}
self._analyticsChanges['Feature' + self.findFeatureByBit(bit).name] = featureValue;
} else if (featureElement.prop('localName') === 'select') {
var controlElements = featureElement.children();
var selectedBit = featureElement.val();
if (selectedBit !== -1) {
var selectedFeature;
for (var i = 0; i < controlElements.length; i++) {
var bit = controlElements[i].value;
if (selectedBit === bit) {
self._featureMask = bit_set(self._featureMask, bit);
selectedFeature = self.findFeatureByBit(bit);
} else {
self._featureMask = bit_clear(self._featureMask, bit);
}
}
if (selectedFeature) {
self._analyticsChanges['FeatureGroup-' + selectedFeature.group] = selectedFeature.name;
}
}
}
};

View file

@ -26,7 +26,7 @@ var LogoManager = LogoManager || {
},
// config for logo image selection dialog
acceptFileTypes: [
{ extensions: ['png', 'bmp'] },
{ description: 'images', extensions: ['png', 'bmp'] },
],
};

View file

@ -141,6 +141,11 @@ function configuration_backup(callback) {
configuration.SERIAL_CONFIG = jQuery.extend(true, {}, SERIAL_CONFIG);
configuration.LED_STRIP = jQuery.extend(true, [], LED_STRIP);
configuration.LED_COLORS = jQuery.extend(true, [], LED_COLORS);
configuration.BOARD_ALIGNMENT_CONFIG = jQuery.extend(true, {}, BOARD_ALIGNMENT_CONFIG);
configuration.CRAFT_NAME = CONFIG.name;
configuration.MIXER_CONFIG = jQuery.extend(true, {}, MIXER_CONFIG);
configuration.SENSOR_CONFIG = jQuery.extend(true, {}, SENSOR_CONFIG);
configuration.PID_ADVANCED_CONFIG = jQuery.extend(true, {}, PID_ADVANCED_CONFIG);
if (semver.gte(CONFIG.apiVersion, "1.19.0")) {
configuration.LED_MODE_COLORS = jQuery.extend(true, [], LED_MODE_COLORS);
@ -165,13 +170,31 @@ function configuration_backup(callback) {
configuration.GPS_CONFIG = jQuery.extend(true, {}, GPS_CONFIG);
configuration.COMPASS_CONFIG = jQuery.extend(true, {}, COMPASS_CONFIG);
}
if (semver.gte(CONFIG.apiVersion, "1.36.0")) {
configuration.BEEPER_CONFIG = jQuery.extend(true, {}, BEEPER_CONFIG);
}
save();
}
}
// start fetching
fetch_unique_data_item();
if (GUI.configuration_loaded === true) {
return fetch_unique_data_item();
}
MSP.promise(MSPCodes.MSP_ADVANCED_CONFIG).then(function() {
return MSP.promise(MSPCodes.MSP_SENSOR_CONFIG);
}).then(function() {
return MSP.promise(MSPCodes.MSP_NAME);
}).then(function() {
return MSP.promise(MSPCodes.MSP_BOARD_ALIGNMENT_CONFIG);
}).then(function() {
return MSP.promise(MSPCodes.MSP_MIXER_CONFIG);
}).then(function() {
return MSP.promise(MSPCodes.MSP_BEEPER_CONFIG);
}).then(function() {
return fetch_unique_data_item();
});
}
function save() {
@ -183,7 +206,7 @@ function configuration_backup(callback) {
var filename = generateFilename(prefix, suffix);
var accepts = [{
extensions: [suffix]
description: suffix.toUpperCase() + ' files', extensions: [suffix]
}];
// create or load the file
@ -231,6 +254,7 @@ function configuration_backup(callback) {
return;
}
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Backup');
console.log('Write SUCCESSFUL');
if (callback) callback();
};
@ -254,7 +278,7 @@ function configuration_restore(callback) {
var chosenFileEntry = null;
var accepts = [{
extensions: ['json']
description: 'JSON files', extensions: ['json']
}];
// load up the file
@ -303,26 +327,23 @@ function configuration_restore(callback) {
// validate
if (typeof configuration.generatedBy !== 'undefined' && compareVersions(configuration.generatedBy, CONFIGURATOR.backupFileMinVersionAccepted)) {
if (typeof configuration.generatedBy !== 'undefined' && compareVersions(configuration.generatedBy, CONFIGURATOR.backupFileMinVersionAccepted)) {
if (!compareVersions(configuration.generatedBy, "1.14.0") && !migrate(configuration)) {
GUI.log(i18n.getMessage('backupFileUnmigratable'));
return;
}
if (configuration.FEATURE_CONFIG.features._featureMask) {
var features = new Features(CONFIG);
features.setMask(configuration.FEATURE_CONFIG.features._featureMask);
configuration.FEATURE_CONFIG.features = features;
}
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Restore');
configuration_upload(configuration, callback);
} else {
GUI.log(i18n.getMessage('backupFileIncompatible'));
}
}
};
@ -763,6 +784,13 @@ function configuration_restore(callback) {
];
function update_unique_data_list() {
uniqueData.push(MSPCodes.MSP_SET_NAME);
uniqueData.push(MSPCodes.MSP_SET_SENSOR_CONFIG);
uniqueData.push(MSPCodes.MSP_SET_MIXER_CONFIG);
uniqueData.push(MSPCodes.MSP_SET_BEEPER_CONFIG);
uniqueData.push(MSPCodes.MSP_SET_BOARD_ALIGNMENT_CONFIG);
uniqueData.push(MSPCodes.MSP_SET_ADVANCED_CONFIG);
if (semver.gte(CONFIG.apiVersion, "1.8.0")) {
uniqueData.push(MSPCodes.MSP_SET_LOOP_TIME);
uniqueData.push(MSPCodes.MSP_SET_ARMING_CONFIG);
@ -803,6 +831,17 @@ function configuration_restore(callback) {
GPS_CONFIG = configuration.GPS_CONFIG;
COMPASS_CONFIG = configuration.COMPASS_CONFIG;
RSSI_CONFIG = configuration.RSSI_CONFIG;
BOARD_ALIGNMENT_CONFIG = configuration.BOARD_ALIGNMENT_CONFIG;
CONFIG.name = configuration.CRAFT_NAME;
MIXER_CONFIG = configuration.MIXER_CONFIG;
SENSOR_CONFIG = configuration.SENSOR_CONFIG;
PID_ADVANCED_CONFIG = configuration.PID_ADVANCED_CONFIG;
BEEPER_CONFIG.beepers = new Beepers(CONFIG);
BEEPER_CONFIG.beepers.setMask(configuration.BEEPER_CONFIG.beepers._beeperMask);
BEEPER_CONFIG.dshotBeaconTone = configuration.BEEPER_CONFIG.dshotBeaconTone;
BEEPER_CONFIG.dshotBeaconConditions = new Beepers(CONFIG, [ "RX_LOST", "RX_SET" ]);
BEEPER_CONFIG.dshotBeaconConditions.setMask(configuration.BEEPER_CONFIG.dshotBeaconConditions._beeperMask);
}
function send_unique_data_item() {

View file

@ -369,6 +369,18 @@ var FC = {
levelSensitivity: 0,
itermThrottleThreshold: 0,
itermAcceleratorGain: 0,
itermRotation: 0,
smartFeedforward: 0,
itermRelax: 0,
itermRelaxType: 0,
absoluteControlGain: 0,
throttleBoost: 0,
acroTrainerAngleLimit: 0,
feedforwardRoll: 0,
feedforwardPitch: 0,
feedforwardYaw: 0,
feedforwardTransition: 0,
antiGravityMode: 0,
};
SENSOR_CONFIG = {

View file

@ -321,5 +321,17 @@ GUI_control.prototype.content_ready = function (callback) {
if (callback) callback();
}
GUI_control.prototype.selectDefaultTabWhenConnected = function() {
chrome.storage.local.get(['rememberLastTab', 'lastTab'], function (result) {
if (!(result.rememberLastTab
&& !!result.lastTab
&& result.lastTab.substring(4) != "cli")) {
$('#tabs ul.mode-connected .tab_setup a').click();
return;
}
$("#tabs ul.mode-connected ." + result.lastTab + " a").click();
});
};
// initialize object into GUI variable
var GUI = new GUI_control();

View file

@ -1,66 +1,124 @@
'use strict;'
var JenkinsLoader = function (url, jobName) {
var self = this;
self._url = url;
self._jobName = jobName;
self._jobUrl = self._url + '/job/' + self._jobName;
self._buildsRequest = '/api/json?tree=builds[number,result,timestamp,artifacts[relativePath],changeSet[items[commitId,msg]]]';
self._builds = {};
var JenkinsLoader = function (url) {
this._url = url;
this._jobs = [];
this._cacheExpirationPeriod = 3600 * 1000;
self._buildsDataTag = `${self._jobUrl}BuildsData`;
self._cacheLastUpdateTag = `${self._jobUrl}BuildsLastUpdate`
this._jobsRequest = '/api/json?tree=jobs[name]';
this._buildsRequest = '/api/json?tree=builds[number,result,timestamp,artifacts[relativePath],changeSet[items[commitId,msg]]]';
}
JenkinsLoader.prototype.loadBuilds = function (callback) {
JenkinsLoader.prototype.loadJobs = function (viewName, callback) {
var self = this;
chrome.storage.local.get([self._cacheLastUpdateTag, self._buildsDataTag], function (result) {
var buildsDataTimestamp = $.now();
var cachedBuildsData = result[self._buildsDataTag];
var cachedBuildsLastUpdate = result[self._cacheLastUpdateTag];
var viewUrl = `${self._url}/view/${viewName}`;
var jobsDataTag = '${viewUrl}JobsData';
var cacheLastUpdateTag = '${viewUrl}JobsLastUpdate';
if (!cachedBuildsData || !cachedBuildsLastUpdate || buildsDataTimestamp - cachedBuildsLastUpdate > 3600 * 1000) {
var request = self._jobUrl + self._buildsRequest;
var wrappedCallback = jobs => {
self._jobs = jobs;
callback(jobs);
};
$.get(request, function (buildsInfo) {
// filter successful builds
self._builds = buildsInfo.builds.filter(build => build.result == 'SUCCESS')
.map(build => ({
number: build.number,
artifacts: build.artifacts.map(artifact => artifact.relativePath),
changes: build.changeSet.items.map(item => '* ' + item.msg).join('<br>\n'),
date: new Date(build.timestamp)
}));
chrome.storage.local.get([cacheLastUpdateTag, jobsDataTag], function (result) {
var jobsDataTimestamp = $.now();
var cachedJobsData = result[jobsDataTag];
var cachedJobsLastUpdate = result[cacheLastUpdateTag];
self._parseBuilds(callback);
}).fail(function (data) {
GUI.log(i18n.getMessage('releaseCheckFailed', [self._jobName, 'failed to load builds']));
self._builds = cachedBuildsData;
self._parseBuilds(callback);
});
} else {
if (cachedBuildsData) {
GUI.log(i18n.getMessage('releaseCheckCached', [self._jobName]));
var cachedCallback = () => {
if (cachedJobsData) {
GUI.log(i18n.getMessage('buildServerUsingCached', ['jobs']));
}
self._builds = cachedBuildsData;
self._parseBuilds(callback);
wrappedCallback(cachedJobsData ? cachedJobsData : []);
};
if (!cachedJobsData || !cachedJobsLastUpdate || jobsDataTimestamp - cachedJobsLastUpdate > self._cacheExpirationPeriod) {
var url = `${viewUrl}${self._jobsRequest}`;
$.get(url, jobsInfo => {
GUI.log(i18n.getMessage('buildServerLoaded', ['jobs']));
// remove Betaflight prefix, rename Betaflight job to Development
var jobs = jobsInfo.jobs.map(job => {
return { title: job.name.replace('Betaflight ', '').replace('Betaflight', 'Development'), name: job.name };
})
// cache loaded info
object = {}
object[jobsDataTag] = jobs;
object[cacheLastUpdateTag] = $.now();
chrome.storage.local.set(object);
wrappedCallback(jobs);
}).error(xhr => {
GUI.log(i18n.getMessage('buildServerLoadFailed', ['jobs', `HTTP ${xhr.status}`]));
}).fail(cachedCallback);
} else {
cachedCallback();
}
});
}
JenkinsLoader.prototype._parseBuilds = function (callback) {
JenkinsLoader.prototype.loadBuilds = function (jobName, callback) {
var self = this;
var jobUrl = `${self._url}/job/${jobName}`;
var buildsDataTag = `${jobUrl}BuildsData`;
var cacheLastUpdateTag = `${jobUrl}BuildsLastUpdate`
chrome.storage.local.get([cacheLastUpdateTag, buildsDataTag], function (result) {
var buildsDataTimestamp = $.now();
var cachedBuildsData = result[buildsDataTag];
var cachedBuildsLastUpdate = result[cacheLastUpdateTag];
var cachedCallback = () => {
if (cachedBuildsData) {
GUI.log(i18n.getMessage('buildServerUsingCached', [jobName]));
}
self._parseBuilds(jobUrl, jobName, cachedBuildsData ? cachedBuildsData : [], callback);
};
if (!cachedBuildsData || !cachedBuildsLastUpdate || buildsDataTimestamp - cachedBuildsLastUpdate > self._cacheExpirationPeriod) {
var url = `${jobUrl}${self._buildsRequest}`;
$.get(url, function (buildsInfo) {
GUI.log(i18n.getMessage('buildServerLoaded', [jobName]));
// filter successful builds
var builds = buildsInfo.builds.filter(build => build.result == 'SUCCESS')
.map(build => ({
number: build.number,
artifacts: build.artifacts.map(artifact => artifact.relativePath),
changes: build.changeSet.items.map(item => '* ' + item.msg).join('<br>\n'),
timestamp: build.timestamp
}));
// cache loaded info
object = {}
object[buildsDataTag] = builds;
object[cacheLastUpdateTag] = $.now();
chrome.storage.local.set(object);
self._parseBuilds(jobUrl, jobName, builds, callback);
}).error(xhr => {
GUI.log(i18n.getMessage('buildServerLoadFailed', [jobName, `HTTP ${xhr.status}`]));
}).fail(cachedCallback);
} else {
cachedCallback();
}
});
}
JenkinsLoader.prototype._parseBuilds = function (jobUrl, jobName, builds, callback) {
// convert from `build -> targets` to `target -> builds` mapping
var targetBuilds = {};
var targetFromFilenameExpression = /betaflight_([\d.]+)?_?(\w+)(\-.*)?\.(.*)/;
self._builds.forEach(build => {
builds.forEach(build => {
build.artifacts.forEach(relativePath => {
var match = targetFromFilenameExpression.exec(relativePath);
@ -70,15 +128,16 @@ JenkinsLoader.prototype._parseBuilds = function (callback) {
var version = match[1];
var target = match[2];
var date = new Date(build.timestamp);
var formattedDate = ("0" + build.date.getDate()).slice(-2) + "-" + ("0" + (build.date.getMonth()+1)).slice(-2) + "-" +
build.date.getFullYear() + " " + ("0" + build.date.getHours()).slice(-2) + ":" + ("0" + build.date.getMinutes()).slice(-2);
var formattedDate = ("0" + date.getDate()).slice(-2) + "-" + ("0" + (date.getMonth()+1)).slice(-2) + "-" +
date.getFullYear() + " " + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2);
var descriptor = {
'releaseUrl': self._jobUrl + '/' + build.number,
'name' : self._jobName + ' #' + build.number,
'releaseUrl': jobUrl + '/' + build.number,
'name' : jobName + ' #' + build.number,
'version' : version + ' #' + build.number,
'url' : self._jobUrl + '/' + build.number + '/artifact/' + relativePath,
'url' : jobUrl + '/' + build.number + '/artifact/' + relativePath,
'file' : relativePath.split('/').slice(-1)[0],
'target' : target,
'date' : formattedDate,

View file

@ -1,9 +1,10 @@
'use strict';
var googleAnalytics = analytics;
var analytics = undefined;
openNewWindowsInExternalBrowser();
//Asynchronous configuration to be done.
//When finish the startProcess() function must be called
$(document).ready(function () {
i18n.init(function() {
startProcess();
@ -11,9 +12,67 @@ $(document).ready(function () {
});
});
function checkSetupAnalytics(callback) {
if (!analytics) {
setTimeout(function () {
chrome.storage.local.get(['userId', 'analyticsOptOut'], function (result) {
if (!analytics) {
setupAnalytics(result);
}
callback(analytics);
});
});
} else if (callback) {
callback(analytics);
}
};
function setupAnalytics(result) {
var userId;
if (result.userId) {
userId = result.userId;
} else {
var uid = new ShortUniqueId();
userId = uid.randomUUID(13);
chrome.storage.local.set({ 'userId': userId });
}
var optOut = !!result.analyticsOptOut;
var debugMode = process.versions['nw-flavor'] === 'sdk';
analytics = new Analytics('UA-123002063-1', userId, 'Betaflight Configurator', getManifestVersion(), GUI.operating_system, optOut, debugMode);
function logException(exception) {
analytics.sendException(exception.stack);
}
process.on('uncaughtException', logException);
analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'AppStart', { sessionControl: 'start' });
function sendCloseEvent() {
analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'AppClose', { sessionControl: 'end' })
}
try {
var gui = require('nw.gui');
var win = gui.Window.get();
win.on('close', function () {
sendCloseEvent();
this.close(true);
});
} catch (ex) {
// Looks like we're in Chrome - but the event does not actually get fired
chrome.runtime.onSuspend.addListener(sendCloseEvent);
}
}
//Process to execute to real start the app
function startProcess() {
// translate to user-selected language
i18n.localizePage();
@ -86,6 +145,13 @@ function startProcess() {
return;
}
$("#tabs ul.mode-connected li").click(function() {
// store the first class of the current tab (omit things like ".active")
chrome.storage.local.set({
lastTab: $(this).attr("class").split(' ')[0]
});
});
GUI.tab_switch_in_progress = true;
GUI.tab_switch_cleanup(function () {
@ -106,6 +172,10 @@ function startProcess() {
GUI.tab_switch_in_progress = false;
}
checkSetupAnalytics(function (analytics) {
analytics.sendAppView(tab);
});
switch (tab) {
case 'landing':
TABS.landing.initialize(content_ready);
@ -210,13 +280,16 @@ function startProcess() {
chrome.storage.local.set({'permanentExpertMode': checked});
$('input[name="expertModeCheckbox"]').prop('checked', checked).change();
if (FEATURE_CONFIG) {
updateTabList(FEATURE_CONFIG.features);
}
}).change();
});
chrome.storage.local.get('rememberLastTab', function (result) {
$('div.rememberLastTab input')
.prop('checked', !!result.rememberLastTab)
.change(function() { chrome.storage.local.set({rememberLastTab: $(this).is(':checked')}) })
.change();
});
if (GUI.operating_system !== 'ChromeOS') {
chrome.storage.local.get('checkForConfiguratorUnstableVersions', function (result) {
if (result.checkForConfiguratorUnstableVersions) {
@ -235,6 +308,30 @@ function startProcess() {
$('div.checkForConfiguratorUnstableVersions').hide();
}
chrome.storage.local.get('analyticsOptOut', function (result) {
if (result.analyticsOptOut) {
$('div.analyticsOptOut input').prop('checked', true);
}
$('div.analyticsOptOut input').change(function () {
var checked = $(this).is(':checked');
chrome.storage.local.set({'analyticsOptOut': checked});
checkSetupAnalytics(function (analytics) {
if (checked) {
analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'OptOut');
}
analytics.setOptOut(checked);
if (!checked) {
analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'OptIn');
}
});
}).change();
});
chrome.storage.local.get('userLanguageSelect', function (result) {
var userLanguage_e = $('div.userLanguage select');
@ -385,6 +482,11 @@ function startProcess() {
}
$('input[name="expertModeCheckbox"]').change(function () {
var checked = $(this).is(':checked');
checkSetupAnalytics(function (analytics) {
analytics.setDimension(analytics.DIMENSIONS.CONFIGURATOR_EXPERT_MODE, checked ? 'On' : 'Off');
});
if (FEATURE_CONFIG) {
updateTabList(FEATURE_CONFIG.features);
}
@ -642,4 +744,16 @@ function openNewWindowsInExternalBrowser() {
} catch (ex) {
console.log("require does not exist, maybe inside chrome");
}
}
}
function showErrorDialog(message) {
var dialog = $('.dialogError')[0];
$('.dialogError-content').html(message);
$('.dialogError-closebtn').click(function() {
dialog.close();
});
dialog.showModal();
}

View file

@ -27,6 +27,12 @@ function MspHelper () {
'RUNCAM_DEVICE_CONTROL': 14, // support communitate with RunCam Device
'LIDAR_TF': 15
};
self.REBOOT_TYPES = {
FIRMWARE: 0,
BOOTLOADER: 1,
MSC: 2
};
}
MspHelper.prototype.reorderPwmProtocols = function (protocol) {
@ -617,6 +623,17 @@ MspHelper.prototype.process_data = function(dataHandler) {
break;
case MSPCodes.MSP_SET_REBOOT:
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
var rebootType = data.read8();
if (rebootType === self.REBOOT_TYPES.MSC) {
if (data.read8() === 0) {
console.log('Storage device not ready.');
showErrorDialog(i18n.getMessage('storageDeviceNotReady'));
break;
}
}
}
console.log('Reboot request accepted');
break;
@ -871,7 +888,11 @@ MspHelper.prototype.process_data = function(dataHandler) {
ADVANCED_TUNING.deltaMethod = data.readU8();
ADVANCED_TUNING.vbatPidCompensation = data.readU8();
if (semver.gte(CONFIG.apiVersion, "1.20.0")) {
ADVANCED_TUNING.dtermSetpointTransition = data.readU8();
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
ADVANCED_TUNING.feedforwardTransition = data.readU8();
} else {
ADVANCED_TUNING.dtermSetpointTransition = data.readU8();
}
ADVANCED_TUNING.dtermSetpointWeight = data.readU8();
ADVANCED_TUNING.toleranceBand = data.readU8();
ADVANCED_TUNING.toleranceBandReduction = data.readU8();
@ -881,13 +902,29 @@ MspHelper.prototype.process_data = function(dataHandler) {
if (semver.gte(CONFIG.apiVersion, "1.24.0")) {
ADVANCED_TUNING.levelAngleLimit = data.readU8();
ADVANCED_TUNING.levelSensitivity = data.readU8();
}
if (semver.gte(CONFIG.apiVersion, "1.36.0")) {
ADVANCED_TUNING.itermThrottleThreshold = data.readU16();
ADVANCED_TUNING.itermAcceleratorGain = data.readU16();
}
if (semver.gte(CONFIG.apiVersion, "1.39.0")) {
ADVANCED_TUNING.dtermSetpointWeight = data.readU16();
if (semver.gte(CONFIG.apiVersion, "1.36.0")) {
ADVANCED_TUNING.itermThrottleThreshold = data.readU16();
ADVANCED_TUNING.itermAcceleratorGain = data.readU16();
if (semver.gte(CONFIG.apiVersion, "1.39.0")) {
ADVANCED_TUNING.dtermSetpointWeight = data.readU16();
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
ADVANCED_TUNING.itermRotation = data.readU8();
ADVANCED_TUNING.smartFeedforward = data.readU8();
ADVANCED_TUNING.itermRelax = data.readU8();
ADVANCED_TUNING.itermRelaxType = data.readU8();
ADVANCED_TUNING.absoluteControlGain = data.readU8();
ADVANCED_TUNING.throttleBoost = data.readU8();
ADVANCED_TUNING.acroTrainerAngleLimit = data.readU8();
ADVANCED_TUNING.feedforwardRoll = data.readU16();
ADVANCED_TUNING.feedforwardPitch = data.readU16();
ADVANCED_TUNING.feedforwardYaw = data.readU16();
ADVANCED_TUNING.antiGravityMode = data.readU8();
}
}
}
}
}
break;
@ -1119,7 +1156,9 @@ MspHelper.prototype.process_data = function(dataHandler) {
case MSPCodes.MSP_SET_ADJUSTMENT_RANGE:
console.log('Adjustment range saved');
break;
case MSPCodes.MSP_SET_BOARD_ALIGNMENT_CONFIG:
console.log('Board alignment saved');
break;
case MSPCodes.MSP_PID_CONTROLLER:
PID.controller = data.readU8();
break;
@ -1195,6 +1234,11 @@ MspHelper.prototype.process_data = function(dataHandler) {
console.log('Unknown code detected: ' + code);
} else {
console.log('FC reports unsupported message error: ' + code);
switch (code) {
case MSPCodes.MSP_SET_REBOOT:
showErrorDialog(i18n.getMessage('operationNotSupported'));
}
}
}
// trigger callbacks, cleanup/remove callback after trigger
@ -1217,7 +1261,6 @@ MspHelper.prototype.process_data = function(dataHandler) {
}
}
/**
* Encode the request body for the MSP request with the given code and return it as an array of bytes.
*/
@ -1526,24 +1569,47 @@ MspHelper.prototype.crunch = function(code) {
.push16(ADVANCED_TUNING.yawItermIgnoreRate)
.push16(ADVANCED_TUNING.yaw_p_limit)
.push8(ADVANCED_TUNING.deltaMethod)
.push8(ADVANCED_TUNING.vbatPidCompensation)
.push8(ADVANCED_TUNING.dtermSetpointTransition)
.push8(Math.min(ADVANCED_TUNING.dtermSetpointWeight, 254))
.push8(ADVANCED_TUNING.toleranceBand)
.push8(ADVANCED_TUNING.toleranceBandReduction)
.push8(ADVANCED_TUNING.itermThrottleGain)
.push16(ADVANCED_TUNING.pidMaxVelocity)
.push16(ADVANCED_TUNING.pidMaxVelocityYaw);
.push8(ADVANCED_TUNING.vbatPidCompensation);
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
buffer.push8(ADVANCED_TUNING.feedforwardTransition);
} else {
buffer.push8(ADVANCED_TUNING.dtermSetpointTransition);
}
buffer.push8(Math.min(ADVANCED_TUNING.dtermSetpointWeight, 254))
.push8(ADVANCED_TUNING.toleranceBand)
.push8(ADVANCED_TUNING.toleranceBandReduction)
.push8(ADVANCED_TUNING.itermThrottleGain)
.push16(ADVANCED_TUNING.pidMaxVelocity)
.push16(ADVANCED_TUNING.pidMaxVelocityYaw);
if (semver.gte(CONFIG.apiVersion, "1.24.0")) {
buffer.push8(ADVANCED_TUNING.levelAngleLimit)
.push8(ADVANCED_TUNING.levelSensitivity);
}
if (semver.gte(CONFIG.apiVersion, "1.36.0")) {
buffer.push16(ADVANCED_TUNING.itermThrottleThreshold)
.push16(ADVANCED_TUNING.itermAcceleratorGain);
}
if (semver.gte(CONFIG.apiVersion, "1.39.0")) {
buffer.push16(ADVANCED_TUNING.dtermSetpointWeight);
if (semver.gte(CONFIG.apiVersion, "1.36.0")) {
buffer.push16(ADVANCED_TUNING.itermThrottleThreshold)
.push16(ADVANCED_TUNING.itermAcceleratorGain);
if (semver.gte(CONFIG.apiVersion, "1.39.0")) {
buffer.push16(ADVANCED_TUNING.dtermSetpointWeight);
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
buffer.push8(ADVANCED_TUNING.itermRotation)
.push8(ADVANCED_TUNING.smartFeedforward)
.push8(ADVANCED_TUNING.itermRelax)
.push8(ADVANCED_TUNING.itermRelaxType)
.push8(ADVANCED_TUNING.absoluteControlGain)
.push8(ADVANCED_TUNING.throttleBoost)
.push8(ADVANCED_TUNING.acroTrainerAngleLimit)
.push16(ADVANCED_TUNING.feedforwardRoll)
.push16(ADVANCED_TUNING.feedforwardPitch)
.push16(ADVANCED_TUNING.feedforwardYaw)
.push8(ADVANCED_TUNING.antiGravityMode);
}
}
}
}
}
// only supports 1 version pre bf 3.0
@ -1894,7 +1960,6 @@ MspHelper.prototype.sendCurrentConfig = function(onCompleteCallback) {
}
MspHelper.prototype.sendLedStripConfig = function(onCompleteCallback) {
var nextFunction = send_next_led_strip_config;

View file

@ -1,6 +1,8 @@
'use strict';
var mspHelper;
var connectionTimestamp;
function initializeSerialBackend() {
GUI.updateManualPortVisibility = function(){
@ -43,6 +45,8 @@ function initializeSerialBackend() {
thisElement.data("clicks", !clicks);
};
GUI.configuration_loaded = false;
var selected_baud = parseInt($('div#port-picker #baud').val());
var selected_port = $('div#port-picker #port option:selected').data().isManual ?
$('#port-override').val() :
@ -122,6 +126,15 @@ function initializeSerialBackend() {
function finishClose(finishedCallback) {
var wasConnected = CONFIGURATOR.connectionValid;
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Disconnected');
if (connectionTimestamp) {
var connectedTime = Date.now() - connectionTimestamp;
analytics.sendTiming(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Connected', connectedTime);
connectedTime = undefined;
}
analytics.resetFlightControllerData();
serial.disconnect(onClosed);
MSP.disconnect_cleanup();
@ -198,13 +211,17 @@ function onOpen(openInfo) {
// request configuration data
MSP.send_message(MSPCodes.MSP_API_VERSION, false, false, function () {
analytics.setFlightControllerData(analytics.DATA.API_VERSION, CONFIG.apiVersion);
GUI.log(i18n.getMessage('apiVersionReceived', [CONFIG.apiVersion]));
if (semver.gte(CONFIG.apiVersion, CONFIGURATOR.apiVersionAccepted)) {
MSP.send_message(MSPCodes.MSP_FC_VARIANT, false, false, function () {
analytics.setFlightControllerData(analytics.DATA.FIRMWARE_TYPE, CONFIG.flightControllerIdentifier);
if (CONFIG.flightControllerIdentifier === 'BTFL') {
MSP.send_message(MSPCodes.MSP_FC_VERSION, false, false, function () {
analytics.setFlightControllerData(analytics.DATA.FIRMWARE_VERSION, CONFIG.flightControllerVersion);
GUI.log(i18n.getMessage('fcInfoReceived', [CONFIG.flightControllerIdentifier, CONFIG.flightControllerVersion]));
updateStatusBarVersion(CONFIG.flightControllerVersion, CONFIG.flightControllerIdentifier);
@ -215,13 +232,19 @@ function onOpen(openInfo) {
GUI.log(i18n.getMessage('buildInfoReceived', [CONFIG.buildInfo]));
MSP.send_message(MSPCodes.MSP_BOARD_INFO, false, false, function () {
analytics.setFlightControllerData(analytics.DATA.BOARD_TYPE, CONFIG.boardIdentifier);
GUI.log(i18n.getMessage('boardInfoReceived', [CONFIG.boardIdentifier, CONFIG.boardVersion]));
updateStatusBarVersion(CONFIG.flightControllerVersion, CONFIG.flightControllerIdentifier, CONFIG.boardIdentifier);
updateTopBarVersion(CONFIG.flightControllerVersion, CONFIG.flightControllerIdentifier, CONFIG.boardIdentifier);
MSP.send_message(MSPCodes.MSP_UID, false, false, function () {
GUI.log(i18n.getMessage('uniqueDeviceIdReceived', [CONFIG.uid[0].toString(16) + CONFIG.uid[1].toString(16) + CONFIG.uid[2].toString(16)]));
var uniqueDeviceIdentifier = CONFIG.uid[0].toString(16) + CONFIG.uid[1].toString(16) + CONFIG.uid[2].toString(16);
analytics.setFlightControllerData(analytics.DATA.MCU_ID, objectHash.sha1(uniqueDeviceIdentifier));
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'Connected');
connectionTimestamp = Date.now();
GUI.log(i18n.getMessage('uniqueDeviceIdReceived', [uniqueDeviceIdentifier]));
if (semver.gte(CONFIG.apiVersion, "1.20.0")) {
MSP.send_message(MSPCodes.MSP_NAME, false, false, function () {
@ -238,6 +261,8 @@ function onOpen(openInfo) {
});
});
} else {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'ConnectionRefused');
var dialog = $('.dialogConnectWarning')[0];
$('.dialogConnectWarning-content').html(i18n.getMessage('firmwareTypeNotSupported'));
@ -252,6 +277,8 @@ function onOpen(openInfo) {
}
});
} else {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'ConnectionRefused');
var dialog = $('.dialogConnectWarning')[0];
$('.dialogConnectWarning-content').html(i18n.getMessage('firmwareVersionNotSupported', [CONFIGURATOR.apiVersionAccepted]));
@ -266,6 +293,8 @@ function onOpen(openInfo) {
}
});
} else {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'SerialPortFailed');
console.log('Failed to open serial port');
GUI.log(i18n.getMessage('serialPortOpenFail'));
@ -297,7 +326,7 @@ function finishOpen() {
onConnect();
$('#tabs ul.mode-connected .tab_setup a').click();
GUI.selectDefaultTabWhenConnected();
}
function connectCli() {

View file

@ -20,7 +20,6 @@ TABS.adjustments.initialize = function (callback) {
}
function load_html() {
self.adjust_template();
$('#content').load("./tabs/adjustments.html", process_html);
}
@ -153,6 +152,8 @@ TABS.adjustments.initialize = function (callback) {
function process_html() {
self.adjust_template();
var auxChannelCount = RC.active_channels - 4;
var modeTableBodyElement = $('.tab-adjustments .adjustments tbody');
@ -272,14 +273,36 @@ TABS.adjustments.cleanup = function (callback) {
};
TABS.adjustments.adjust_template = function () {
var availableFunctionCount;
if (semver.lt(CONFIG.apiVersion, "1.31.0")) {
availableFunctionCount = 21; // Available in betaflight 2.9
var selectFunction = $('#functionSelectionSelect');
var elementsNumber;
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
elementsNumber = 29; // PID Audio
} else if (semver.gte(CONFIG.apiVersion, "1.39.0")) {
elementsNumber = 26; // PID Audio
} else if (semver.gte(CONFIG.apiVersion, "1.37.0")) {
elementsNumber = 25; // Horizon Strength
} else {
availableFunctionCount = 24; // RC rate Yaw / D setpoint / D setpoint transition added to 3.1.0
elementsNumber = 24; // Setpoint transition
}
for (let i = 0; i < elementsNumber; i++) {
selectFunction.append(new Option(i18n.getMessage('adjustmentsFunction' + i), i));
}
// For 1.40, the D Setpoint has been replaced, so we replace it with the correct values
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
var element22 = selectFunction.find("option[value='22']");
var element23 = selectFunction.find("option[value='23']");
// Change the "text"
element22.text(i18n.getMessage('adjustmentsFunction22_2'));
element23.text(i18n.getMessage('adjustmentsFunction23_2'));
// Reorder, we insert it with the other FF elements to be coherent...
element22.insertAfter(selectFunction.find("option[value='25']"));
element23.insertAfter(selectFunction.find("option[value='28']"));
}
var template = $('#tab-adjustments-templates .adjustments .adjustment');
var functionList = $(template).find('.functionSelection .function');
var functionListOptions = $(functionList).find('option').slice(0,availableFunctionCount);
functionList.empty().append(functionListOptions);
};

View file

@ -244,8 +244,9 @@ TABS.auxiliary.initialize = function (callback) {
}
function update_ui() {
for (var i = 0; i < AUX_CONFIG.length; i++) {
var modeElement = $('#mode-' + i);
let hasUsedMode = false;
for (let i = 0; i < AUX_CONFIG.length; i++) {
let modeElement = $('#mode-' + i);
if (modeElement.find(' .range').length == 0) {
// if the mode is unused, skip it
modeElement.removeClass('off').removeClass('on');
@ -257,8 +258,17 @@ TABS.auxiliary.initialize = function (callback) {
} else {
$('.mode .name').eq(i).data('modeElement').removeClass('on').addClass('off');
}
hasUsedMode = true;
}
let hideUnused = hideUnusedModes && hasUsedMode;
for (let i = 0; i < AUX_CONFIG.length; i++) {
let modeElement = $('#mode-' + i);
if (modeElement.find(' .range').length == 0) {
modeElement.toggle(!hideUnused);
}
}
auto_select_channel(RC.channels);
var auxChannelCount = RC.active_channels - 4;
@ -306,6 +316,18 @@ TABS.auxiliary.initialize = function (callback) {
return fillPrevChannelsValues();
}
let hideUnusedModes = false;
chrome.storage.local.get('hideUnusedModes', function (result) {
$("input#switch-toggle-unused")
.change(function() {
hideUnusedModes = $(this).prop("checked");
chrome.storage.local.set({ hideUnusedModes: hideUnusedModes });
update_ui();
})
.prop("checked", !!result.hideUnusedModes)
.change();
});
// update ui instantly on first load
update_ui();

View file

@ -60,14 +60,13 @@ TABS.cli.initialize = function (callback) {
var textarea = $('.tab-cli textarea');
$('.tab-cli .save').click(function() {
var prefix = 'cli';
var suffix = 'txt';
var filename = generateFilename(prefix, suffix);
var accepts = [{
extensions: [suffix],
description: suffix.toUpperCase() + ' files', extensions: [suffix],
}];
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, accepts: accepts}, function(entry) {
@ -87,9 +86,11 @@ TABS.cli.initialize = function (callback) {
};
writer.onwriteend = function () {
if (writer.length === 0) {
if (self.outputHistory.length > 0 && writer.length === 0) {
writer.write(new Blob([self.outputHistory], {type: 'text/plain'}));
} else {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'CliSave', self.outputHistory.length);
console.log('write complete');
}
};

View file

@ -2,7 +2,8 @@
TABS.configuration = {
DSHOT_PROTOCOL_MIN_VALUE: 5,
SHOW_OLD_BATTERY_CONFIG: false
SHOW_OLD_BATTERY_CONFIG: false,
analyticsChanges: {},
};
TABS.configuration.initialize = function (callback, scrollPosition) {
@ -10,6 +11,7 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
if (GUI.active_tab != 'configuration') {
GUI.active_tab = 'configuration';
GUI.configuration_loaded = true;
}
if (semver.lt(CONFIG.apiVersion, "1.36.0")) {
@ -194,6 +196,10 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
load_config();
function process_html() {
var self = this;
self.analyticsChanges = {};
var mixer_list_e = $('select.mixerList');
for (var selectIndex = 0; selectIndex < mixerList.length; selectIndex++) {
mixerList.forEach(function (mixerEntry, mixerIndex) {
@ -224,7 +230,15 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
reverseMotorSwitch_e.prop('checked', MIXER_CONFIG.reverseMotorDir != 0).change();
mixer_list_e.change(function () {
MIXER_CONFIG.mixer = parseInt($(this).val());
var mixerValue = parseInt($(this).val());
var newValue;
if (mixerValue !== MIXER_CONFIG.mixer) {
newValue = $(this).find('option:selected').text();
}
self.analyticsChanges['Mixer'] = newValue;
MIXER_CONFIG.mixer = mixerValue;
refreshMixerPreview();
});
@ -355,11 +369,31 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
esc_protocol_e.append('<option value="' + (i + 1) + '">'+ escprotocols[i] + '</option>');
}
esc_protocol_e.val(PID_ADVANCED_CONFIG.fast_pwm_protocol + 1);
$("input[id='unsyncedPWMSwitch']").change(function() {
if ($(this).is(':checked')) {
$('div.unsyncedpwmfreq').show();
} else {
$('div.unsyncedpwmfreq').hide();
}
});
$('input[id="unsyncedPWMSwitch"]').prop('checked', PID_ADVANCED_CONFIG.use_unsyncedPwm !== 0).change();
$('input[name="unsyncedpwmfreq"]').val(PID_ADVANCED_CONFIG.motor_pwm_rate);
$('input[name="digitalIdlePercent"]').val(PID_ADVANCED_CONFIG.digitalIdlePercent);
esc_protocol_e.val(PID_ADVANCED_CONFIG.fast_pwm_protocol + 1);
esc_protocol_e.change(function () {
var escProtocolValue = parseInt($(this).val()) - 1;
var newValue;
if (escProtocolValue !== PID_ADVANCED_CONFIG.fast_pwm_protocol) {
newValue = $(this).find('option:selected').text();
}
self.analyticsChanges['EscProtocol'] = newValue;
//hide not used setting for DSHOT protocol
if ($(this).val() - 1 >= self.DSHOT_PROTOCOL_MIN_VALUE) {
if (escProtocolValue >= self.DSHOT_PROTOCOL_MIN_VALUE) {
$('div.minthrottle').hide();
$('div.maxthrottle').hide();
$('div.mincommand').hide();
@ -379,9 +413,6 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
}
}).change();
$('input[id="unsyncedPWMSwitch"]').prop('checked', PID_ADVANCED_CONFIG.use_unsyncedPwm !== 0);
$('input[name="unsyncedpwmfreq"]').val(PID_ADVANCED_CONFIG.motor_pwm_rate);
$('input[name="digitalIdlePercent"]').val(PID_ADVANCED_CONFIG.digitalIdlePercent);
// Gyro and PID update
var gyroUse32kHz_e = $('input[id="gyroUse32kHz"]');
@ -611,7 +642,15 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
}
serialRX_e.change(function () {
RX_CONFIG.serialrx_provider = parseInt($(this).val());
var serialRxValue = parseInt($(this).val());
var newValue;
if (serialRxValue !== RX_CONFIG.serialrx_provider) {
newValue = $(this).find('option:selected').text();
}
self.analyticsChanges['SerialRx'] = newValue;
RX_CONFIG.serialrx_provider = serialRxValue;
});
// select current serial RX type
@ -921,14 +960,6 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
checkUpdateCurrentControls();
}
$("input[id='unsyncedPWMSwitch']").change(function() {
if ($(this).is(':checked')) {
$('div.unsyncedpwmfreq').show();
} else {
$('div.unsyncedpwmfreq').hide();
}
}).change();
$('a.save').click(function () {
// gather data that doesn't have automatic change event bound
BOARD_ALIGNMENT_CONFIG.roll = parseInt($('input[name="board_align_roll"]').val());
@ -987,6 +1018,9 @@ TABS.configuration.initialize = function (callback, scrollPosition) {
RX_CONFIG.fpvCamAngleDegrees = parseInt($('input[name="fpvCamAngleDegrees"]').val());
}
analytics.sendChangeEvents(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, self.analyticsChanges);
self.analyticsChanges = {};
function save_serial_config() {
var next_callback = save_feature_config;
MSP.send_message(MSPCodes.MSP_SET_CF_SERIAL_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_CF_SERIAL_CONFIG), false, next_callback);

View file

@ -2,7 +2,8 @@
TABS.firmware_flasher = {
releases: null,
releaseChecker: new ReleaseChecker('firmware', 'https://api.github.com/repos/betaflight/betaflight/releases')
releaseChecker: new ReleaseChecker('firmware', 'https://api.github.com/repos/betaflight/betaflight/releases'),
jenkinsLoader: new JenkinsLoader('https://ci.betaflight.tech')
};
TABS.firmware_flasher.initialize = function (callback) {
@ -28,7 +29,7 @@ TABS.firmware_flasher.initialize = function (callback) {
: "normal");
}
$('#content').load("./tabs/firmware_flasher.html", function () {
function onDocumentLoad() {
FirmwareCache.load();
FirmwareCache.onPutToCache(onFirmwareCacheUpdate);
FirmwareCache.onRemoveFromCache(onFirmwareCacheUpdate);
@ -49,16 +50,18 @@ TABS.firmware_flasher.initialize = function (callback) {
function process_hex(data, summary) {
intel_hex = data;
analytics.setFirmwareData(analytics.DATA.FIRMWARE_CHECKSUM, objectHash.sha1(intel_hex));
parse_hex(intel_hex, function (data) {
parsed_hex = data;
if (parsed_hex) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_SIZE, parsed_hex.bytes_total);
if (!FirmwareCache.has(summary)) {
FirmwareCache.put(summary, intel_hex);
}
var url;
$('span.progressLabel').html('<a class="save_firmware" href="#" title="Save Firmware">Loaded Online Firmware: (' + parsed_hex.bytes_total + ' bytes)</a>');
$('a.flash_firmware').removeClass('disabled');
@ -243,24 +246,21 @@ TABS.firmware_flasher.initialize = function (callback) {
{
tag: 'firmwareFlasherOptionLabelBuildTypeReleaseCandidate',
loader: () => self.releaseChecker.loadReleaseData(releaseData => buildBoardOptions(releaseData, true))
},
{
tag: 'firmwareFlasherOptionLabelBuildTypeDevelopment',
loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight').loadBuilds(buildJenkinsBoardOptions)
},
{
tag: 'firmwareFlasherOptionLabelBuildTypeAKK3_3',
loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight Maintenance 3.3 (AKK - RDQ VTX Patch)').loadBuilds(buildJenkinsBoardOptions)
},
{
tag: 'firmwareFlasherOptionLabelBuildTypeAKK3_4',
loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight Maintenance 3.4 (AKK - RDQ VTX Patch)').loadBuilds(buildJenkinsBoardOptions)
}
];
var ciBuildsTypes = self.jenkinsLoader._jobs.map(job => {
return {
title: job.title,
loader: () => self.jenkinsLoader.loadBuilds(job.name, buildJenkinsBoardOptions)
};
})
buildTypes = buildTypes.concat(ciBuildsTypes);
var buildType_e = $('select[name="build_type"]');
buildTypes.forEach((build, index) => {
buildType_e.append($("<option value='{0}' selected>{1}</option>".format(index, i18n.getMessage(build.tag))))
buildType_e.append($("<option value='{0}' selected>{1}</option>".format(index, build.tag ? i18n.getMessage(build.tag) : build.title)))
});
showOrHideBuildTypeSelect();
@ -270,6 +270,8 @@ TABS.firmware_flasher.initialize = function (callback) {
i18n.localizePage();
buildType_e.change(function() {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_CHANNEL, $(this).find('option:selected').text());
$("a.load_remote_file").addClass('disabled');
var build_type = $(this).val();
@ -325,7 +327,10 @@ TABS.firmware_flasher.initialize = function (callback) {
// UI Hooks
$('a.load_file').click(function () {
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['hex']}]}, function (fileEntry) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_CHANNEL, undefined);
analytics.setFirmwareData(analytics.DATA.FIRMWARE_SOURCE, 'file');
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{description: 'HEX files', extensions: ['hex']}]}, function (fileEntry) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
@ -339,6 +344,7 @@ TABS.firmware_flasher.initialize = function (callback) {
console.log('Loading file from: ' + path);
fileEntry.file(function (file) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_NAME, file.name);
var reader = new FileReader();
reader.onprogress = function (e) {
@ -355,10 +361,14 @@ TABS.firmware_flasher.initialize = function (callback) {
intel_hex = e.target.result;
analytics.setFirmwareData(analytics.DATA.FIRMWARE_CHECKSUM, objectHash.sha1(intel_hex));
parse_hex(intel_hex, function (data) {
parsed_hex = data;
if (parsed_hex) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_SIZE, parsed_hex.bytes_total);
$('a.flash_firmware').removeClass('disabled');
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFirmwareLocalLoaded', parsed_hex.bytes_total));
@ -385,7 +395,10 @@ TABS.firmware_flasher.initialize = function (callback) {
let isCached = FirmwareCache.has(release);
if (evt.target.value=="0" || isCached) {
if (isCached) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_SOURCE, 'cache');
FirmwareCache.get(release, cached => {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_NAME, release.file);
console.info("Release found in cache: " + release.file);
onLoadSuccess(cached.hexdata, release);
});
@ -398,6 +411,7 @@ TABS.firmware_flasher.initialize = function (callback) {
});
$('a.load_remote_file').click(function (evt) {
analytics.setFirmwareData(analytics.DATA.FIRMWARE_SOURCE, 'http');
if ($('select[name="firmware_version"]').val() == "0") {
GUI.log(i18n.getMessage('firmwareFlasherNoFirmwareSelected'));
@ -413,6 +427,7 @@ TABS.firmware_flasher.initialize = function (callback) {
var summary = $('select[name="firmware_version"] option:selected').data('summary');
if (summary) { // undefined while list is loading or while running offline
analytics.setFirmwareData(analytics.DATA.FIRMWARE_NAME, summary.file);
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonDownloading'));
$("a.load_remote_file").addClass('disabled');
$.get(summary.url, onLoadSuccess).fail(failed_to_load);
@ -427,14 +442,17 @@ TABS.firmware_flasher.initialize = function (callback) {
if (parsed_hex != false) {
var options = {};
var eraseAll = false;
if ($('input.erase_chip').is(':checked')) {
options.erase_chip = true;
eraseAll = true
}
analytics.setFirmwareData(analytics.DATA.FIRMWARE_ERASE_ALL, eraseAll.toString());
if (String($('div#port-picker #port').val()) != 'DFU') {
if (String($('div#port-picker #port').val()) != '0') {
var port = String($('div#port-picker #port').val()),
baud;
var port = String($('div#port-picker #port').val()), baud;
baud = 115200;
if ($('input.updating').is(':checked')) {
@ -447,6 +465,7 @@ TABS.firmware_flasher.initialize = function (callback) {
baud = parseInt($('#flash_manual_baud_rate').val());
}
analytics.sendEvent(analytics.EVENT_CATEGORIES.FIRMWARE, 'Flashing');
STM32.connect(port, baud, parsed_hex, options);
} else {
@ -454,6 +473,8 @@ TABS.firmware_flasher.initialize = function (callback) {
GUI.log(i18n.getMessage('firmwareFlasherNoValidPort'));
}
} else {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FIRMWARE, 'Flashing');
STM32DFU.connect(usbDevices.STM32DFU, parsed_hex, options);
}
} else {
@ -465,7 +486,7 @@ TABS.firmware_flasher.initialize = function (callback) {
$(document).on('click', 'span.progressLabel a.save_firmware', function () {
var summary = $('select[name="firmware_version"] option:selected').data('summary');
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: summary.file, accepts: [{extensions: ['hex']}]}, function (fileEntry) {
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: summary.file, accepts: [{description: 'HEX files', extensions: ['hex']}]}, function (fileEntry) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;
@ -626,6 +647,10 @@ TABS.firmware_flasher.initialize = function (callback) {
});
GUI.content_ready(callback);
}
self.jenkinsLoader.loadJobs('Firmware', () => {
$('#content').load("./tabs/firmware_flasher.html", onDocumentLoad);
});
};
@ -637,5 +662,7 @@ TABS.firmware_flasher.cleanup = function (callback) {
$(document).unbind('keypress');
$(document).off('click', 'span.progressLabel a');
analytics.resetFirmwareData();
if (callback) callback();
};

View file

@ -32,6 +32,26 @@ TABS.landing.initialize = function (callback) {
$(this).data('state2', state);
});
// load privacy policy content
$('#privacy_policy .policy').load('./tabs/privacy_policy.html');
/** changelog trigger **/
$("#privacy_policy_toggle").on('click', function() {
var state = $(this).data('state2');
if (state) {
$("#privacy_policy").animate({right: -495}, 200, function () {
$("#content").removeClass('policy_open');
});
state = false;
} else {
$("#privacy_policy").animate({right: 0}, 200);
$("#content").addClass('policy_open');
state = true;
}
$(this).text(state ? i18n.getMessage('close') : i18n.getMessage('defaultPrivacyPolicyAction'));
$(this).data('state2', state);
});
GUI.content_ready(callback);
});

View file

@ -236,7 +236,7 @@ TABS.logging.initialize = function (callback) {
var filename = generateFilename(prefix, suffix);
var accepts = [{
extensions: [suffix],
description: suffix.toUpperCase() + ' files', extensions: [suffix],
}];
// create or load the file

View file

@ -45,7 +45,11 @@ TABS.motors.initialize = function (callback) {
}
function load_motor_data() {
MSP.send_message(MSPCodes.MSP_MOTOR, false, false, load_html);
MSP.send_message(MSPCodes.MSP_MOTOR, false, false, load_mixer_config);
}
function load_mixer_config() {
MSP.send_message(MSPCodes.MSP_MIXER_CONFIG, false, false, load_html);
}
function load_html() {

View file

@ -145,6 +145,22 @@ TABS.onboard_logging.initialize = function (callback) {
$("div.blackboxRate").show();
}
}).change();
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
if (SDCARD.supported || DATAFLASH.supported) {
$(".tab-onboard_logging")
.toggleClass("msc-supported", true);
$('a.onboardLoggingRebootMsc').click(function () {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'RebootMsc');
var buffer = [];
buffer.push(2);
MSP.send_message(MSPCodes.MSP_SET_REBOOT, buffer, false);
});
}
}
update_html();
@ -284,6 +300,8 @@ TABS.onboard_logging.initialize = function (callback) {
}
function update_html() {
var dataflashPresent = DATAFLASH.totalSize > 0;
update_bar_width($(".tab-onboard_logging .dataflash-used"), DATAFLASH.usedSize, DATAFLASH.totalSize, i18n.getMessage('dataflashUsedSpace'), false);
update_bar_width($(".tab-onboard_logging .dataflash-free"), DATAFLASH.totalSize - DATAFLASH.usedSize, DATAFLASH.totalSize, i18n.getMessage('dataflashFreeSpace'), false);
@ -296,27 +314,51 @@ TABS.onboard_logging.initialize = function (callback) {
.toggleClass("sdcard-error", SDCARD.state === MSP.SDCARD_STATE_FATAL)
.toggleClass("sdcard-initializing", SDCARD.state === MSP.SDCARD_STATE_CARD_INIT || SDCARD.state === MSP.SDCARD_STATE_FS_INIT)
.toggleClass("sdcard-ready", SDCARD.state === MSP.SDCARD_STATE_READY);
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
var mscIsReady = dataflashPresent || (SDCARD.state === MSP.SDCARD_STATE_READY);
$(".tab-onboard_logging")
.toggleClass("msc-not-ready", !mscIsReady);
if (!mscIsReady) {
$('a.onboardLoggingRebootMsc').addClass('disabled');
} else {
$('a.onboardLoggingRebootMsc').removeClass('disabled');
}
}
var loggingStatus
switch (SDCARD.state) {
case MSP.SDCARD_STATE_NOT_PRESENT:
$(".sdcard-status").text(i18n.getMessage('sdcardStatusNoCard'));
loggingStatus = 'SdCard: NotPresent';
break;
case MSP.SDCARD_STATE_FATAL:
$(".sdcard-status").html(i18n.getMessage('sdcardStatusReboot'));
loggingStatus = 'SdCard: Error';
break;
case MSP.SDCARD_STATE_READY:
$(".sdcard-status").text(i18n.getMessage('sdcardStatusReady'));
loggingStatus = 'SdCard: Ready';
break;
case MSP.SDCARD_STATE_CARD_INIT:
$(".sdcard-status").text(i18n.getMessage('sdcardStatusStarting'));
loggingStatus = 'SdCard: Init';
break;
case MSP.SDCARD_STATE_FS_INIT:
$(".sdcard-status").text(i18n.getMessage('sdcardStatusFileSystem'));
loggingStatus = 'SdCard: FsInit';
break;
default:
$(".sdcard-status").text(i18n.getMessage('sdcardStatusUnknown',[SDCARD.state]));
}
if (dataflashPresent && SDCARD.state === MSP.SDCARD_STATE_NOT_PRESENT) {
loggingStatus = 'Dataflash';
analytics.setFlightControllerData(analytics.DATA.LOG_SIZE, DATAFLASH.usedSize);
}
analytics.setFlightControllerData(analytics.DATA.LOGGING_STATUS, loggingStatus);
if (SDCARD.supported && !sdcardTimer) {
// Poll for changes in SD card status
sdcardTimer = setTimeout(function() {
@ -348,6 +390,8 @@ TABS.onboard_logging.initialize = function (callback) {
}
function mark_saving_dialog_done(startTime, totalBytes, totalBytesCompressed) {
analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'SaveDataflash');
var totalTime = (new Date().getTime() - startTime) / 1000;
console.log('Received ' + totalBytes + ' bytes in ' + totalTime.toFixed(2) + 's ('
+ (totalBytes / totalTime / 1024).toFixed(2) + 'kB / s) with block size ' + self.blockSize + '.');
@ -445,7 +489,7 @@ TABS.onboard_logging.initialize = function (callback) {
var filename = generateFilename(prefix, suffix);
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename,
accepts: [{extensions: [suffix]}]}, function(fileEntry) {
accepts: [{description: suffix.toUpperCase() + ' files', extensions: [suffix]}]}, function(fileEntry) {
var error = chrome.runtime.lastError;
if (error) {
@ -513,6 +557,9 @@ TABS.onboard_logging.initialize = function (callback) {
};
TABS.onboard_logging.cleanup = function (callback) {
analytics.setFlightControllerData(analytics.DATA.LOGGING_STATUS, undefined);
analytics.setFlightControllerData(analytics.DATA.LOG_SIZE, undefined);
if (sdcardTimer) {
clearTimeout(sdcardTimer);
sdcardTimer = false;

View file

@ -129,7 +129,7 @@ FONT.parseMCMFontFile = function(data) {
FONT.openFontFile = function($preview) {
return new Promise(function(resolve) {
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['mcm']}]}, function (fileEntry) {
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{description: 'MCM files', extensions: ['mcm']}]}, function (fileEntry) {
FONT.data.loaded_font_file = fileEntry.name;
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
@ -342,7 +342,7 @@ OSD.constants = {
default_position: -9,
draw_order: 110,
positionable: true,
preview: FONT.symbol(SYM.THR) + FONT.symbol(SYM.THR1) + '69'
preview: FONT.symbol(SYM.THR) + FONT.symbol(SYM.THR1) + ' 69'
},
CPU_LOAD: {
name: 'CPU_LOAD',
@ -913,7 +913,8 @@ OSD.constants = {
{ file: "extra_large", name: "Extra Large" },
{ file: "betaflight", name: "Betaflight" },
{ file: "digital", name: "Digital" },
{ file: "clarity", name: "Clarity" }
{ file: "clarity", name: "Clarity" },
{ file: "vision", name: "Vision" }
]
};
@ -1470,17 +1471,18 @@ TABS.osd.initialize = function (callback) {
}
$('#content').load("./tabs/osd.html", function () {
// Generate font type buttons
var fontbuttons = $('.fontbuttons');
// Generate font type select element
var fontselect = $('.fontpresets');
OSD.constants.FONT_TYPES.forEach(function(e, i) {
var button = $('<button>', {
var option = $('<option>', {
"data-font-file": e.file,
value: e.file,
text: e.name
});
fontbuttons.append($(button));
fontselect.append($(option));
});
var fontbuttons = $('.fontpresets_wrapper');
fontbuttons.append($('<button>', { class: "load_font_file", i18n: "osdSetupOpenFont" }));
// must invoke before i18n.localizePage() since it adds translation keys for expected logo size
@ -1974,24 +1976,27 @@ TABS.osd.initialize = function (callback) {
// init structs once, also clears current font
FONT.initData();
var $fontPicker = $('.fontbuttons button');
$fontPicker.click(function(e) {
if (!$(this).data('font-file')) { return; }
$fontPicker.removeClass('active');
$(this).addClass('active');
$.get('./resources/osd/' + $(this).data('font-file') + '.mcm', function(data) {
var $fontpresets = $('.fontpresets')
$fontpresets.change(function(e) {
var $font = $('.fontpresets option:selected');
$.get('./resources/osd/' + $font.data('font-file') + '.mcm', function(data) {
FONT.parseMCMFontFile(data);
FONT.preview($preview);
LogoManager.drawPreview();
updateOsdView();
});
});
// load the first font when we change tabs
$fontPicker.first().click();
var $font = $('.fontpresets option:selected');
$.get('./resources/osd/' + $font.data('font-file') + '.mcm', function(data) {
FONT.parseMCMFontFile(data);
FONT.preview($preview);
LogoManager.drawPreview();
updateOsdView();
});
$('button.load_font_file').click(function() {
$fontPicker.removeClass('active');
FONT.openFontFile().then(function() {
FONT.preview($preview);
LogoManager.drawPreview();
@ -2047,7 +2052,7 @@ TABS.osd.initialize = function (callback) {
})
$(document).on('click', 'span.progressLabel a.save_font', function () {
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: 'baseflight', accepts: [{extensions: ['mcm']}]}, function (fileEntry) {
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: 'baseflight', accepts: [{description: 'MCM files', extensions: ['mcm']}]}, function (fileEntry) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
return;

441
src/js/tabs/pid_tuning.js Executable file → Normal file
View file

@ -39,9 +39,13 @@ TABS.pid_tuning.initialize = function (callback) {
}).then(function() {
return MSP.promise(MSPCodes.MSP_RC_DEADBAND);
}).then(function() {
$('#content').load("./tabs/pid_tuning.html", process_html);
MSP.send_message(MSPCodes.MSP_MIXER_CONFIG, false, false, load_html);
});
function load_html() {
$('#content').load("./tabs/pid_tuning.html", process_html);
}
function pid_and_rc_to_form() {
self.setProfile();
if (semver.gte(CONFIG.apiVersion, "1.20.0")) {
@ -49,142 +53,19 @@ TABS.pid_tuning.initialize = function (callback) {
}
// Fill in the data from PIDs array
var i = 0;
$('.pid_tuning .ROLL input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[0][i++]);
break;
case 1:
$(this).val(PIDs[0][i++]);
break;
case 2:
$(this).val(PIDs[0][i++]);
break;
}
});
i = 0;
$('.pid_tuning .PITCH input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[1][i++]);
break;
case 1:
$(this).val(PIDs[1][i++]);
break;
case 2:
$(this).val(PIDs[1][i++]);
break;
}
});
// For each pid name
PID_names.forEach(function(elementPid, indexPid) {
i = 0;
$('.pid_tuning .YAW input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[2][i++]);
break;
case 1:
$(this).val(PIDs[2][i++]);
break;
}
});
$('.pid_tuning .YAW_JUMP_PREVENTION input').each(function () {
switch (i) {
case 2:
$(this).val(PIDs[2][i++]);
break;
}
});
// Look into the PID table to a row with the name of the pid
var searchRow = $('.pid_tuning .' + elementPid + ' input');
i = 0;
$('.pid_tuning .ALT input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[3][i++]);
break;
case 1:
$(this).val(PIDs[3][i++]);
break;
case 2:
$(this).val(PIDs[3][i++]);
break;
}
});
i = 0;
$('.pid_tuning .Pos input').each(function () {
$(this).val(PIDs[4][i++]);
});
i = 0;
$('.pid_tuning .PosR input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[5][i++]);
break;
case 1:
$(this).val(PIDs[5][i++]);
break;
case 2:
$(this).val(PIDs[5][i++]);
break;
}
});
i = 0;
$('.pid_tuning .NavR input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[6][i++]);
break;
case 1:
$(this).val(PIDs[6][i++]);
break;
case 2:
$(this).val(PIDs[6][i++]);
break;
}
});
i = 0;
$('.pid_tuning .ANGLE input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[7][i++]);
break;
}
});
$('.pid_tuning .HORIZON input').each(function () {
switch (i) {
case 1:
$(this).val(PIDs[7][i++]);
break;
case 2:
$(this).val(PIDs[7][i++]);
break;
}
});
i = 0;
$('.pid_tuning .MAG input').each(function () {
$(this).val(PIDs[8][i++]);
});
i = 0;
$('.pid_tuning .Vario input').each(function () {
switch (i) {
case 0:
$(this).val(PIDs[9][i++]);
break;
case 1:
$(this).val(PIDs[9][i++]);
break;
case 2:
$(this).val(PIDs[9][i++]);
break;
}
// Assign each value
searchRow.each(function (indexInput) {
if (PIDs[indexPid][indexInput] !== undefined) {
$(this).val(PIDs[indexPid][indexInput]);
}
});
});
// Fill in data from RC_tuning object
@ -299,6 +180,129 @@ TABS.pid_tuning.initialize = function (callback) {
$('.dtermLowpass2').hide();
}
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
// I Term Rotation
$('input[id="itermrotation"]').prop('checked', ADVANCED_TUNING.itermRotation !== 0);
// Smart Feed Forward
$('input[id="smartfeedforward"]').prop('checked', ADVANCED_TUNING.smartFeedforward !== 0);
// I Term Relax
var itermRelaxCheck = $('input[id="itermrelax"]');
itermRelaxCheck.prop('checked', ADVANCED_TUNING.itermRelax !== 0);
$('select[id="itermrelaxAxes"]').val(ADVANCED_TUNING.itermRelax > 0 ? ADVANCED_TUNING.itermRelax : 1);
$('select[id="itermrelaxType"]').val(ADVANCED_TUNING.itermRelaxType);
itermRelaxCheck.change(function() {
var checked = $(this).is(':checked');
if (checked) {
$('.itermrelax .suboption').show();
} else {
$('.itermrelax .suboption').hide();
}
});
itermRelaxCheck.change();
// Absolute Control
var absoluteControlGainNumberElement = $('input[name="absoluteControlGain-number"]');
var absoluteControlGainRangeElement = $('input[name="absoluteControlGain-range"]');
absoluteControlGainNumberElement.change(function () {
absoluteControlGainRangeElement.val($(this).val());
});
absoluteControlGainRangeElement.change(function () {
absoluteControlGainNumberElement.val($(this).val());
});
absoluteControlGainNumberElement.val(ADVANCED_TUNING.absoluteControlGain).change();
// Throttle Boost
var throttleBoostNumberElement = $('input[name="throttleBoost-number"]');
var throttleBoostRangeElement = $('input[name="throttleBoost-range"]');
throttleBoostNumberElement.change(function () {
throttleBoostRangeElement.val($(this).val());
});
throttleBoostRangeElement.change(function () {
throttleBoostNumberElement.val($(this).val());
});
throttleBoostNumberElement.val(ADVANCED_TUNING.throttleBoost).change();
// Acro Trainer
var acroTrainerAngleLimitNumberElement = $('input[name="acroTrainerAngleLimit-number"]');
var acroTrainerAngleLimitRangeElement = $('input[name="acroTrainerAngleLimit-range"]');
acroTrainerAngleLimitNumberElement.change(function () {
acroTrainerAngleLimitRangeElement.val($(this).val());
});
acroTrainerAngleLimitRangeElement.change(function () {
acroTrainerAngleLimitNumberElement.val($(this).val());
});
acroTrainerAngleLimitNumberElement.val(ADVANCED_TUNING.acroTrainerAngleLimit).change();
// Yaw D
$('.pid_tuning .YAW input[name="d"]').val(PIDs[2][2]); // PID Yaw D
// Feedforward
$('.pid_tuning .ROLL input[name="f"]').val(ADVANCED_TUNING.feedforwardRoll);
$('.pid_tuning .PITCH input[name="f"]').val(ADVANCED_TUNING.feedforwardPitch);
$('.pid_tuning .YAW input[name="f"]').val(ADVANCED_TUNING.feedforwardYaw);
var feedforwardTransitionNumberElement = $('input[name="feedforwardTransition-number"]');
var feedforwardTransitionRangeElement = $('input[name="feedforwardTransition-range"]');
feedforwardTransitionNumberElement.val(ADVANCED_TUNING.feedforwardTransition / 100);
feedforwardTransitionRangeElement.val(ADVANCED_TUNING.feedforwardTransition / 100);
feedforwardTransitionNumberElement.change(function () {
feedforwardTransitionRangeElement.val($(this).val());
});
feedforwardTransitionRangeElement.change(function () {
feedforwardTransitionNumberElement.val($(this).val());
});
$('.helpicon[i18n_title="pidTuningPidTuningTip"]').hide();
// AntiGravity Mode
var antiGravityModeSelect = $('.antigravity select[id="antiGravityMode"]');
antiGravityModeSelect.change(function () {
var antiGravityModeValue = $('.antigravity select[id="antiGravityMode"]').val();
// Smooth
if (antiGravityModeValue == 0) {
$('.antigravity table th:nth-child(3)').hide();
$('.antigravity table td:nth-child(3)').hide();
} else {
$('.antigravity table th:nth-child(3)').show();
$('.antigravity table td:nth-child(3)').show();
}
});
antiGravityModeSelect.val(ADVANCED_TUNING.antiGravityMode).change();
} else {
$('.itermrotation').hide();
$('.smartfeedforward').hide();
$('.itermrelax').hide();
$('.absoluteControlGain').hide();
$('.throttleBoost').hide();
$('.acroTrainerAngleLimit').hide();
$('.pid_tuning .YAW input[name="d"]').hide();
// Feedforward column
$('#pid_main tr :nth-child(5)').hide();
$('#pid_main .pid_titlebar2 th').attr("colspan", 8);
$('.helpicon[i18n_title="pidTuningPidTuningTipFeedforward"]').hide();
$('#pid-tuning .feedforwardTransition').hide();
$('.antigravity table th:first-child').hide();
$('.antigravity table td:first-child').hide();
}
$('input[id="gyroNotch1Enabled"]').change(function() {
var checked = $(this).is(':checked');
var hz = FILTER_CONFIG.gyro_notch_hz > 0 ? FILTER_CONFIG.gyro_notch_hz : DEFAULT.gyro_notch_hz;
@ -409,60 +413,19 @@ TABS.pid_tuning.initialize = function (callback) {
function form_to_pid_and_rc() {
// Fill in the data from PIDs array
// Catch all the changes and stuff the inside PIDs array
var i = 0;
$('table.pid_tuning tr.ROLL .pid_data input').each(function () {
PIDs[0][i++] = parseFloat($(this).val());
});
i = 0;
$('table.pid_tuning tr.PITCH .pid_data input').each(function () {
PIDs[1][i++] = parseFloat($(this).val());
});
// For each pid name
PID_names.forEach(function(elementPid, indexPid) {
i = 0;
$('table.pid_tuning tr.YAW .pid_data input').each(function () {
PIDs[2][i++] = parseFloat($(this).val());
});
$('table.pid_tuning tr.YAW_JUMP_PREVENTION .pid_data input').each(function () {
PIDs[2][i++] = parseFloat($(this).val());
});
// Look into the PID table to a row with the name of the pid
var searchRow = $('.pid_tuning .' + elementPid + ' input');
i = 0;
$('table.pid_tuning tr.ALT input').each(function () {
PIDs[3][i++] = parseFloat($(this).val());
});
i = 0;
$('table.pid_tuning tr.Vario input').each(function () {
PIDs[9][i++] = parseFloat($(this).val());
});
i = 0;
$('table.pid_tuning tr.Pos input').each(function () {
PIDs[4][i++] = parseFloat($(this).val());
});
i = 0;
$('table.pid_tuning tr.PosR input').each(function () {
PIDs[5][i++] = parseFloat($(this).val());
});
i = 0;
$('table.pid_tuning tr.NavR input').each(function () {
PIDs[6][i++] = parseFloat($(this).val());
});
i = 0;
$('div.pid_tuning tr.ANGLE input').each(function () {
PIDs[7][i++] = parseFloat($(this).val());
});
$('div.pid_tuning tr.HORIZON input').each(function () {
PIDs[7][i++] = parseFloat($(this).val());
});
i = 0;
$('div.pid_tuning tr.MAG input').each(function () {
PIDs[8][i++] = parseFloat($(this).val());
// Assign each value
searchRow.each(function (indexInput) {
if ($(this).val()) {
PIDs[indexPid][indexInput] = parseFloat($(this).val());
}
});
});
// catch RC_tuning changes
@ -527,40 +490,80 @@ TABS.pid_tuning.initialize = function (callback) {
FILTER_CONFIG.gyro_lowpass2_type = parseInt($('.pid_filter select[name="gyroLowpass2Type"]').val());
FILTER_CONFIG.dterm_lowpass2_hz = parseInt($('.pid_filter input[name="dtermLowpass2Frequency"]').val());
}
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
ADVANCED_TUNING.itermRotation = $('input[id="itermrotation"]').is(':checked') ? 1 : 0;
ADVANCED_TUNING.smartFeedforward = $('input[id="smartfeedforward"]').is(':checked') ? 1 : 0;
ADVANCED_TUNING.itermRelax = $('input[id="itermrelax"]').is(':checked') ? $('select[id="itermrelaxAxes"]').val() : 0;
ADVANCED_TUNING.itermRelaxType = $('input[id="itermrelax"]').is(':checked') ? $('select[id="itermrelaxType"]').val() : 0;
ADVANCED_TUNING.absoluteControlGain = $('input[name="absoluteControlGain-number"]').val();
ADVANCED_TUNING.throttleBoost = $('input[name="throttleBoost-number"]').val();
ADVANCED_TUNING.acroTrainerAngleLimit = $('input[name="acroTrainerAngleLimit-number"]').val();
ADVANCED_TUNING.feedforwardRoll = parseInt($('.pid_tuning .ROLL input[name="f"]').val());
ADVANCED_TUNING.feedforwardPitch = parseInt($('.pid_tuning .PITCH input[name="f"]').val());
ADVANCED_TUNING.feedforwardYaw = parseInt($('.pid_tuning .YAW input[name="f"]').val());
ADVANCED_TUNING.feedforwardTransition = parseInt($('input[name="feedforwardTransition-number"]').val() * 100);
ADVANCED_TUNING.antiGravityMode = $('select[id="antiGravityMode"]').val();
}
}
function showAllPids() {
$('.tab-pid_tuning .pid_tuning').show();
// Hide all optional elements
$('.pid_optional tr').hide(); // Hide all rows
$('.pid_optional table').hide(); // Hide tables
$('.pid_optional').hide(); // Hide general div
// Only show rows supported by the firmware
PID_names.forEach(function(elementPid) {
// Show rows for the PID
$('.pid_tuning .' + elementPid).show();
// Show titles and other elements needed by the PID
$('.needed_by_' + elementPid).show();
});
// Special case
if (semver.lt(CONFIG.apiVersion, "1.24.0")) {
$('#pid_sensitivity').hide();
}
}
function hideUnusedPids() {
$('.tab-pid_tuning .pid_tuning').hide();
$('#pid_main').show();
if (have_sensor(CONFIG.activeSensors, 'acc')) {
$('#pid_accel').show();
$('#pid_level').show();
$('#pid_sensitivity').show();
if (!have_sensor(CONFIG.activeSensors, 'acc')) {
$('#pid_accel').hide();
}
var showTitle = false;
if (have_sensor(CONFIG.activeSensors, 'baro') ||
have_sensor(CONFIG.activeSensors, 'sonar')) {
$('#pid_baro').show();
showTitle = true;
}
if (have_sensor(CONFIG.activeSensors, 'mag')) {
$('#pid_mag').show();
showTitle = true;
}
if (FEATURE_CONFIG.features.isEnabled('GPS')) {
$('#pid_gps').show();
showTitle = true;
var hideSensorPid = function(element, sensorReady) {
var isVisible = element.is(":visible");
if (!isVisible || !sensorReady) {
element.hide();
isVisible = false;
}
return isVisible;
}
if (showTitle) {
$('#pid_optional').show();
var isVisibleBaroMagGps = false;
isVisibleBaroMagGps |= hideSensorPid($('#pid_baro'), have_sensor(CONFIG.activeSensors, 'baro') || have_sensor(CONFIG.activeSensors, 'sonar'));
isVisibleBaroMagGps |= hideSensorPid($('#pid_mag'), have_sensor(CONFIG.activeSensors, 'mag'));
isVisibleBaroMagGps |= hideSensorPid($('#pid_gps'), have_sensor(CONFIG.activeSensors, 'GPS'));
if (!isVisibleBaroMagGps) {
$('#pid_baro_mag_gps').hide();
}
}
@ -742,6 +745,7 @@ TABS.pid_tuning.initialize = function (callback) {
}
}
showAllPids();
updatePidDisplay();
showAllButton.on('click', function(){
@ -850,12 +854,16 @@ TABS.pid_tuning.initialize = function (callback) {
$('.tab-pid_tuning .note').hide();
}
// Add a name to each row of PIDs if empty
$('.pid_tuning tr').each(function(){
for(i = 0; i < PID_names.length; i++) {
if($(this).hasClass(PID_names[i])) {
$(this).find('td:first').text(PID_names[i]);
for(i = 0; i < PID_names.length; i++) {
if($(this).hasClass(PID_names[i])) {
var firstColumn = $(this).find('td:first');
if (!firstColumn.text()) {
firstColumn.text(PID_names[i]);
}
}
}
}
});
@ -1372,8 +1380,13 @@ TABS.pid_tuning.updatePidControllerParameters = function () {
} else {
$('.pid_tuning .YAW_JUMP_PREVENTION').hide();
$('#pid-tuning .dtermSetpointTransition').show();
$('#pid-tuning .dtermSetpoint').show();
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
$('#pid-tuning .dtermSetpointTransition').hide();
$('#pid-tuning .dtermSetpoint').hide();
} else {
$('#pid-tuning .dtermSetpointTransition').show();
$('#pid-tuning .dtermSetpoint').show();
}
$('#pid-tuning .delta').hide();
}

View file

@ -1,6 +1,8 @@
'use strict';
TABS.ports = {};
TABS.ports = {
analyticsChanges: {},
};
TABS.ports.initialize = function (callback, scrollPosition) {
var self = this;
@ -117,6 +119,9 @@ TABS.ports.initialize = function (callback, scrollPosition) {
}
function update_ui() {
self.analyticsChanges = {};
self.foo.bar = 1;
if (semver.lt(CONFIG.apiVersion, "1.6.0")) {
@ -240,6 +245,19 @@ TABS.ports.initialize = function (callback, scrollPosition) {
if (serialPort.functions.indexOf(functionName) >= 0) {
select_e.val(functionName);
}
if (column === 'telemetry') {
var initialValue = functionName;
select_e.change(function () {
var telemetryValue = $(this).val();
var newValue;
if (telemetryValue !== initialValue) {
newValue = $(this).find('option:selected').text();
}
self.analyticsChanges['Telemetry'] = newValue;
});
}
}
}
}
@ -249,6 +267,7 @@ TABS.ports.initialize = function (callback, scrollPosition) {
}
function on_tab_loaded_handler() {
var self = this;
i18n.localizePage();
@ -265,6 +284,9 @@ TABS.ports.initialize = function (callback, scrollPosition) {
}
function on_save_handler() {
analytics.sendChangeEvents(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, self.analyticsChanges);
self.analyticsChanges = {};
// update configuration based on current ui state
SERIAL_CONFIG.ports = [];

View file

@ -40,7 +40,7 @@ TABS.receiver.initialize = function (callback) {
}
function load_rx_config() {
var next_callback = load_html;
var next_callback = load_mixer_config;
if (semver.gte(CONFIG.apiVersion, "1.20.0")) {
MSP.send_message(MSPCodes.MSP_RX_CONFIG, false, false, next_callback);
} else {
@ -48,6 +48,10 @@ TABS.receiver.initialize = function (callback) {
}
}
function load_mixer_config() {
MSP.send_message(MSPCodes.MSP_MIXER_CONFIG, false, false, load_html);
}
function load_html() {
$('#content').load("./tabs/receiver.html", process_html);
}

View file

@ -59,9 +59,24 @@ TABS.setup.initialize = function (callback) {
self.initializeInstruments();
$('#arming-disable-flag-row').attr('title', i18n.getMessage('initialSetupArmingDisableFlagsTooltip'));
if (semver.gte(CONFIG.apiVersion, "1.40.0")) {
if (isExpertModeEnabled()) {
$('.initialSetupRebootBootloader').show();
} else {
$('.initialSetupRebootBootloader').hide();
}
$('a.rebootBootloader').click(function () {
var buffer = [];
buffer.push(1);
MSP.send_message(MSPCodes.MSP_SET_REBOOT, buffer, false);
});
} else {
$('.initialSetupRebootBootloader').hide();
}
// UI Hooks
$('a.calibrateAccel').click(function () {
var self = $(this);

View file

@ -39,6 +39,9 @@
<script type="text/javascript" src="./node_modules/i18next/i18next.js"></script>
<script type="text/javascript" src="./node_modules/i18next-xhr-backend/i18nextXHRBackend.js"></script>
<script type="text/javascript" src="./node_modules/marked/marked.min.js"></script>
<script type="text/javascript" src="./node_modules/universal-ga/lib/analytics.min.js"></script>
<script type="text/javascript" src="./node_modules/short-unique-id/dist/short-unique-id.min.js"></script>
<script type="text/javascript" src="./node_modules/object-hash/dist/object_hash.js"></script>
<script type="text/javascript" src="./js/libraries/q.js"></script>
<script type="text/javascript" src="./js/libraries/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="./js/libraries/jquery-ui-1.11.4.min.js"></script>
@ -54,6 +57,7 @@
<script type="text/javascript" src="./js/libraries/bluebird.min.js"></script>
<script type="text/javascript" src="./js/libraries/jquery.ba-throttle-debounce.min.js"></script>
<script type="text/javascript" src="./js/libraries/inflection.min.js"></script>
<script type="text/javascript" src="./js/libraries/analytics.js"></script>
<script type="text/javascript" src="./js/injected_methods.js"></script>
<script type="text/javascript" src="./js/data_storage.js"></script>
<script type="text/javascript" src="./js/fc.js"></script>
@ -79,6 +83,7 @@
<script type="text/javascript" src="./js/Beepers.js"></script>
<script type="text/javascript" src="./js/release_checker.js"></script>
<script type="text/javascript" src="./js/jenkins_loader.js"></script>
<script type="text/javascript" src="./js/Analytics.js"></script>
<script type="text/javascript" src="./js/main.js"></script>
<script type="text/javascript" src="./js/tabs/landing.js"></script>
<script type="text/javascript" src="./js/tabs/setup.js"></script>
@ -268,7 +273,9 @@
-->
</ul>
<ul class="mode-connected mode-connected-cli">
<li class="tab_cli"><a href="#" i18n="tabCLI" class="tabicon ic_cli" i18n_title="tabCLI"></a></li>
<li class="tab_cli">
<a href="#" i18n="tabCLI" class="tabicon ic_cli" i18n_title="tabCLI"></a>
</li>
</ul>
</div>
<div class="clear-both"></div>
@ -323,5 +330,15 @@
</div>
</dialog>
<dialog class="dialogError">
<h3 i18n="errorTitle"></h3>
<div class="content">
<div class="dialogError-content" style="margin-top: 10px"></div>
</div>
<div class="buttons">
<a href="#" class="dialogError-closebtn regular-button" i18n="close"></a>
</div>
</dialog>
</body>
</html>

View file

@ -66,34 +66,11 @@
<div class="marker"></div>
</div>
</td>
<td class="functionSelection"><select class="function">
<option value="0" i18n="adjustmentsFunction0"></option>
<option value="1" i18n="adjustmentsFunction1"></option>
<option value="2" i18n="adjustmentsFunction2"></option>
<option value="3" i18n="adjustmentsFunction3"></option>
<option value="4" i18n="adjustmentsFunction4"></option>
<option value="5" i18n="adjustmentsFunction5"></option>
<option value="6" i18n="adjustmentsFunction6"></option>
<option value="7" i18n="adjustmentsFunction7"></option>
<option value="8" i18n="adjustmentsFunction8"></option>
<option value="9" i18n="adjustmentsFunction9"></option>
<option value="10" i18n="adjustmentsFunction10"></option>
<option value="11" i18n="adjustmentsFunction11"></option>
<option value="12" i18n="adjustmentsFunction12"></option>
<option value="13" i18n="adjustmentsFunction13"></option>
<option value="14" i18n="adjustmentsFunction14"></option>
<option value="15" i18n="adjustmentsFunction15"></option>
<option value="16" i18n="adjustmentsFunction16"></option>
<option value="17" i18n="adjustmentsFunction17"></option>
<option value="18" i18n="adjustmentsFunction18"></option>
<option value="19" i18n="adjustmentsFunction19"></option>
<option value="20" i18n="adjustmentsFunction20"></option>
<option value="21" i18n="adjustmentsFunction21"></option>
<option value="22" i18n="adjustmentsFunction22"></option>
<option value="23" i18n="adjustmentsFunction23"></option>
<option value="24" i18n="adjustmentsFunction24"></option>
<option value="25" i18n="adjustmentsFunction25"></option>
</select></td>
<td class="functionSelection">
<select id="functionSelectionSelect" class="function">
<!-- Generated values go here -->
</select>
</td>
<td class="adjustmentSlot"><select class="slot">
<option value="0" i18n="adjustmentsSlot0"></option>
<option value="1" i18n="adjustmentsSlot1"></option>

View file

@ -7,6 +7,12 @@
<div class="note spacebottom">
<div class="note_spacer">
<p i18n="auxiliaryHelp"></p>
<p>
<form>
<input type="checkbox" id="switch-toggle-unused" name="switch-toggle-unused" class="togglesmall"></input>
<label for="switch-toggle-unused"><span i18n="auxiliaryToggleUnused" /></label>
</form>
</p>
</div>
</div>
<table class="modes">

View file

@ -54,4 +54,14 @@
</div>
</div>
</div>
<div id="privacy_policy">
<div class="button">
<a id="privacy_policy_toggle" href="#" i18n="defaultPrivacyPolicyAction"></a>
</div>
<div class="wrapper">
<div class="policy">
<!-- privacy policy content will be loaded here -->
</div>
</div>
</div>
</div>

View file

@ -133,6 +133,23 @@
</div>
</div>
</div>
<div class="gui_box grey require-msc-supported">
<div class="gui_box_titlebar" align="left">
<div class="spacer_box_title" i18n="onboardLoggingMsc">
</div>
</div>
<div class="spacer_box">
<div class="require-msc-supported">
<p i18n="onboardLoggingMscNote"></p>
<div>
<a class="require-msc-ready regular-button onboardLoggingRebootMsc" href="#" i18n="onboardLoggingRebootMscText"></a>
</div>
</div>
<p class="require-msc-not-ready" i18n="onboardLoggingMscNotReady"></p>
</div>
</div>
</div>
</div>
</div>

View file

@ -4,6 +4,12 @@
<div class="checkForConfiguratorUnstableVersions">
<label><input type="checkbox" /><span i18n="checkForConfiguratorUnstableVersions"></span></label>
</div>
<div class="rememberLastTab">
<label><input type="checkbox" /><span i18n="rememberLastTab"></span></label>
</div>
<div class="analyticsOptOut">
<label><input type="checkbox" /><span i18n="analyticsOptOut"></span></label>
</div>
<div class="separator"></div>
<div class="userLanguage">
<label>

View file

@ -124,7 +124,11 @@
<h1 class="tab_title" i18n="osdSetupFontPresets" />
<!-- Font preview and list -->
<div class="content_wrapper font-preview"></div>
<div class="fontbuttons"></div>
<div class="fontpresets_wrapper">
<label id="font-selector-label" i18n="osdSetupFontPresetsSelector"></label>
<select class="fontpresets"></select>
<span id="font-selector-span" i18n="osdSetupFontPresetsSelectorOr"> Or </span>
</div>
<!-- Boot logo setup -->
<h1 class="tab_title" i18n="osdSetupCustomLogoTitle" />
<div id="font-logo-preview-container" class="content_wrapper">

200
src/tabs/pid_tuning.html Executable file → Normal file
View file

@ -33,7 +33,7 @@
</div>
</div>
<div class="cf_column right">
<div class="default_btn show">
<div class="default_btn show showAllPids">
<a href="#" id="showAllPids" i18n="pidTuningShowAllPids"></a>
</div>
<div class="default_btn resetbt">
@ -66,24 +66,24 @@
<div class="clear-both"></div>
<div class="cf_column twothird">
<div class="gui_box grey topspacer" style="margin-top: 0px;">
<table class="pid_titlebar">
<tr>
<table id="pid_main" class="pid_tuning">
<tr class="pid_titlebar">
<th class="name"></th>
<th class="proportional" i18n="pidTuningProportional"></th>
<th class="integral" i18n="pidTuningIntegral"></th>
<th class="derivative" i18n="pidTuningDerivative"></th>
<th class="feedforward" i18n="pidTuningFeedforward"></th>
<th class="rc_rate" i18n="pidTuningRcRate"></th>
<th class="rate" i18n="pidTuningRate"></th>
<th class="new_rates maxVel" i18n="pidTuningMaxVel"></th>
<th class="rc_expo" i18n="pidTuningRcExpo"></th>
</tr>
</table>
<table id="pid_main" class="pid_tuning">
<tr>
<th colspan="8">
<tr class="pid_titlebar2">
<th colspan="9">
<div class="pid_mode">
<div i18n="pidTuningBasic" style="float:left;"></div>
<div class="helpicon cf_tip" i18n_title="pidTuningPidTuningTip"></div>
<div class="helpicon cf_tip" i18n_title="pidTuningPidTuningTipFeedforward"></div>
</div>
</th>
</tr>
@ -93,6 +93,7 @@
<td class="pid_data"><input type="number" name="p" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="i" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="d" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="f" step="1" min="0" max="255" /></td>
<td rowspan="2" style="background-color:white;">
<input type="number" name="rc_rate" step="0.01" min="0" max="2.55" />
<div class="bracket"></div>
@ -111,6 +112,7 @@
<td class="pid_data"><input type="number" name="p" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="i" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="d" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="f" step="1" min="0" max="255" /></td>
<td class="pitch_rate"><input type="number" name="pitch_rate" step="0.01" min="0" max="1.00" /></td>
<td class="new_rates maxAngularVelPitch"></td>
</tr>
@ -119,7 +121,8 @@
<td bgcolor="#8080FF"></td>
<td class="pid_data"><input type="number" name="p" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="i" step="1" min="0" max="255" /></td>
<td></td>
<td class="pid_data"><input type="number" name="d" step="1" min="0" max="255" /></td>
<td class="pid_data"><input type="number" name="f" step="1" min="0" max="255" /></td>
<td rowspan="1"><input type="number" name="rc_rate_yaw" step="0.01" min="0" max="2.55" /></td>
<td><input type="number" name="yaw_rate" step="0.01" min="0" max="2.55" /></td>
<td class="new_rates maxAngularVelYaw"></td>
@ -137,17 +140,17 @@
</tr>
</table>
</div>
<div id="pid_optional" class="gui_box grey topspacer pid_tuning">
<table class="pid_titlebar">
<tr>
<div id="pid_baro_mag_gps" class="pid_optional needed_by_ALT needed_by_VEL needed_by_MAG needed_by_Pos needed_by_PosR needed_by_NavR gui_box grey topspacer pid_tuning">
<table class="pid_titlebar needed_by_ALT needed_by_VEL needed_by_MAG">
<tr class="needed_by_ALT needed_by_VEL needed_by_MAG">
<th class="name"></th>
<th class="proportional" i18n="pidTuningProportional"></th>
<th class="integral" i18n="pidTuningIntegral"></th>
<th class="derivative" i18n="pidTuningDerivative"></th>
</tr>
</table>
<table id="pid_baro" class="pid_tuning">
<tr>
<table id="pid_baro" class="pid_tuning needed_by_ALT needed_by_VEL">
<tr class="needed_by_ALT needed_by_VEL">
<th colspan="4">
<div class="pid_mode" i18n="pidTuningAltitude"></div>
</th>
@ -159,16 +162,16 @@
<td><input type="number" name="i" step="1" min="0" max="255" /></td>
<td><input type="number" name="d" step="1" min="0" max="255" /></td>
</tr>
<tr class="Vario">
<tr class="VEL">
<!-- 9 -->
<td>VEL</td>
<td></td>
<td><input type="number" name="p" step="1" min="0" max="255" /></td>
<td><input type="number" name="i" step="1" min="0" max="255" /></td>
<td><input type="number" name="d" step="1" min="0" max="255" /></td>
</tr>
</table>
<table id="pid_mag" class="pid_tuning">
<tr>
<table id="pid_mag" class="pid_tuning needed_by_MAG">
<tr class="needed_by_MAG">
<th colspan="4">
<div class="pid_mode" i18n="pidTuningMag"></div>
</th>
@ -181,8 +184,8 @@
<td></td>
</tr>
</table>
<table id="pid_gps" class="pid_tuning">
<tr>
<table id="pid_gps" class="pid_tuning needed_by_Pos needed_by_PosR needed_by_NavR">
<tr class="needed_by_Pos needed_by_PosR needed_by_NavR">
<th colspan="4">
<div class="pid_mode" i18n="pidTuningGps"></div>
</th>
@ -211,9 +214,9 @@
</tr>
</table>
</div>
<div id="pid_accel" class="gui_box grey topspacer pid_tuning">
<table id="pid_level" class="pid_tuning">
<tr>
<div id="pid_accel" class="pid_optional needed_by_LEVEL gui_box grey topspacer pid_tuning">
<table id="pid_level" class="pid_tuning needed_by_LEVEL">
<tr class="needed_by_LEVEL">
<th colspan="3">
<div class="pid_mode">
<div i18n="pidTuningLevel" style="float:left;"></div>
@ -222,42 +225,43 @@
</th>
</tr>
</table>
<table class="pid_titlebar">
<tr>
<table class="pid_titlebar needed_by_LEVEL">
<tr class="needed_by_LEVEL">
<th class="third"></th>
<th class="third" i18n="pidTuningStrength" style="width: 33%;"></th>
<th class="third" i18n="pidTuningTransition" style="width: 33%;"></th>
</tr>
</table>
<table>
<tr class="ANGLE">
<table class="needed_by_LEVEL">
<tr class="LEVEL">
<!-- 7 -->
<td class="third" i18n="pidTuningAngle"></td>
<td class="third"><input type="number" name="p" step="1" min="0" max="255" /></td>
<td class="third"></td>
</tr>
<tr class="HORIZON">
<tr class="LEVEL">
<!-- 7 -->
<td class="third" i18n="pidTuningHorizon"></td>
<td class="third"><input type="number" name="i" step="1" min="0" max="255" /></td>
<td class="third"><input type="number" name="d" step="1" min="0" max="255" /></td>
</tr>
</table>
<table class="pid_titlebar pid_sensitivity">
<tr>
<table class="needed_by_LEVEL pid_titlebar pid_sensitivity">
<tr class="needed_by_LEVEL">
<th class="third"></th>
<th class="third" i18n="pidTuningLevelAngleLimit" style="width: 33%;"></th>
<th class="third levelSensitivityHeader" i18n="pidTuningLevelSensitivity" style="width: 33%;"></th>
</tr>
</table>
<table id="pid_sensitivity" class="pid_tuning pid_sensitivity">
<tr>
<table id="pid_sensitivity" class="needed_by_LEVEL pid_tuning pid_sensitivity">
<tr class="needed_by_LEVEL">
<td class="third"></td>
<td class="third"><input type="number" name="angleLimit" step="1" min="10" max="200" /></td>
<td class="third"><input type="number" name="sensitivity" step="1" min="10" max="120" /></td>
</tr>
</table>
</div>
<div class="gui_box grey topspacer pidTuningFeatures">
<table class="pid_titlebar new_rates">
<tr>
@ -322,8 +326,73 @@
<span i18n="pidTuningDtermSetpointTransitionWarning"></span>
</td>
</tr>
<tr class="feedforwardTransition" style="height:30px;">
<td><input type="number" name="feedforwardTransition-number" step="0.01" min="0.00" max="1.00"/></td>
<td class="slider"><input type="range" name="feedforwardTransition-range" step="0.01" min="0.00" max="1.00"/></td>
<td>
<div>
<label>
<span i18n="pidTuningFeedforwardTransition"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardTransitionHelp"></div>
</div>
</td>
</tr>
<tr class="acroTrainerAngleLimit">
<td><input type="number" name="acroTrainerAngleLimit-number" step="1" min="10" max="80"/></td>
<td class="slider"><input type="range" name="acroTrainerAngleLimit-range" step="1" min="10" max="80"/></td>
<td colspan="2">
<div>
<label>
<span i18n="pidTuningAcroTrainerAngleLimit"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningAcroTrainerAngleLimitHelp"></div>
</div>
</td>
</tr>
<tr class="throttleBoost">
<td><input type="number" name="throttleBoost-number" step="1" min="0" max="100"/></td>
<td class="slider"><input type="range" name="throttleBoost-range" step="1" min="0" max="100"/></td>
<td colspan="2">
<div>
<label>
<span i18n="pidTuningThrottleBoost"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningThrottleBoostHelp"></div>
</div>
</td>
</tr>
<tr class="absoluteControlGain">
<td><input type="number" name="absoluteControlGain-number" step="1" min="0" max="20"/></td>
<td class="slider"><input type="range" name="absoluteControlGain-range" step="1" min="0" max="20"/></td>
<td colspan="2">
<div>
<label>
<span i18n="pidTuningAbsoluteControlGain"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningAbsoluteControlGainHelp"></div>
</div>
</td>
</tr>
<tr class="itermrotation">
<td><input type="checkbox" id="itermrotation" class="toggle" /></td>
<td colspan="2">
<div>
<label for="itermrotation">
<span i18n="pidTuningItermRotation"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningItermRotationHelp"></div>
</div>
</td>
</tr>
<tr>
<td style="height:30px;"><input type="checkbox" id="vbatpidcompensation" class="toggle" /></td>
<td><input type="checkbox" id="vbatpidcompensation" class="toggle" /></td>
<td colspan="2">
<div>
<label for="vbatpidcompensation">
@ -333,6 +402,54 @@
</div>
</td>
</tr>
<tr class="smartfeedforward">
<td><input type="checkbox" id="smartfeedforward" class="toggle" /></td>
<td colspan="2">
<div>
<label for="smartfeedforward">
<span i18n="pidTuningSmartFeedforward"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningSmartFeedforwardHelp"></div>
</div>
</td>
</tr>
<tr class="itermrelax">
<td><input type="checkbox" id="itermrelax" class="toggle" /></td>
<td colspan="2">
<span>
<label for="itermrelax">
<span i18n="pidTuningItermRelax" />
</label>
</span>
<span class="suboption">
<label for="itermrelaxAxes">
<span i18n="pidTuningItermRelaxAxes" />
</label>
<select id="itermrelaxAxes">
<option i18n="pidTuningItermRelaxAxesOptionRP" value="1">
<option i18n="pidTuningItermRelaxAxesOptionRPY" value="2"/>
<option i18n="pidTuningItermRelaxAxesOptionRPInc" value="3"/>
<option i18n="pidTuningItermRelaxAxesOptionRPYInc" value="4"/>
</select>
</span>
<span class="suboption">
<label for="itermrelaxType">
<span i18n="pidTuningItermRelaxType" />
</label>
<select id="itermrelaxType">
<option i18n="pidTuningItermRelaxTypeOptionGyro" value="0"/>
<option i18n="pidTuningItermRelaxTypeOptionSetpoint" value="1"/>
</select>
</span>
<div class="helpicon cf_tip" i18n_title="pidTuningItermRelaxHelp"></div>
</td>
</tr>
</table>
</div>
@ -340,14 +457,25 @@
<table class="cf">
<thead>
<tr>
<th i18n="pidTuningAntiGravityMode"></th>
<th i18n="pidTuningAntiGravityGain"></th>
<th i18n="pidTuningAntiGravityThres"></th>
</tr>
</thead>
<tbody>
<tr style="height: 35px;">
<td><input type="number" name="itermAcceleratorGain" step="0.1" min="1" max="30" /></td>
<td><input type="number" name="itermThrottleThreshold" step="10" min="20" max="1000" /></td>
<td>
<select id="antiGravityMode">
<option i18n="pidTuningAntiGravityModeOptionSmooth" value="0">
<option i18n="pidTuningAntiGravityModeOptionStep" value="1">
</select>
</td>
<td>
<input type="number" name="itermAcceleratorGain" step="0.1" min="1" max="30" />
</td>
<td>
<input type="number" name="itermThrottleThreshold" step="10" min="20" max="1000" />
</td>
</tr>
</tbody>
</table>
@ -375,7 +503,7 @@
<div class="cf_column third_right">
<div class="spacer_left rc_curve">
<div class="rc_curve_bg">
<table class="cf">
<table class="cf rc_curve">
<thead>
<tr>
<th colspan="2">
@ -388,7 +516,7 @@
</thead>
<tbody>
<tr>
<td>
<td colspan="2">
<div class="spacer" style="margin-top: 10px; margin-bottom: 8px;">
<div class="rate_curve" style="position:relative;" >
<canvas id="rate_curve_layer0" height="120px" style="position:absolute; top: 0; left: 0; z-index: 0; height:100%; width:100%;"></canvas>

View file

@ -0,0 +1,104 @@
<h1>Privacy Policy</h1>
<p>Effective date: August 06, 2018</p>
<p>The Betaflight Group ("us", "we", or "our") operates the website and the Betaflight Configurator application (the "Service").</p>
<p>This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our Service and the choices you have associated with that data. This Privacy Policy for The Betaflight Group is powered by <a href="https://www.freeprivacypolicy.com/free-privacy-policy-generator.php">FreePrivacyPolicy.com</a>.</p>
<p>We use your data to provide and improve the Service. By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions.</p>
<h2>Information Collection And Use</h2>
<p>We collect several different types of information for various purposes to provide and improve our Service to you.</p>
<h3>Types of Data Collected</h3>
<h4>Usage Data</h4>
<p>We may also collect information that your browser sends whenever you visit our Service or when you access the Service by or through a device ("Usage Data").</p>
<p>This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.</p>
<p>When you access the Service by or through a device, this Usage Data may include information such as the type of device you use, your device unique ID, the IP address of your device, your operating system, the type of Internet browser you use, unique device identifiers and other diagnostic data.</p>
<h4>Tracking & Cookies Data</h4>
<p>We use cookies and similar tracking technologies to track the activity on our Service and hold certain information.</p>
<p>Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our Service.</p>
<p>Examples of Cookies we use:</p>
<ul>
<li><strong>Session Cookies.</strong> We use Session Cookies to operate our Service.</li>
<li><strong>Preference Cookies.</strong> We use Preference Cookies to remember your preferences and various settings.</li>
</ul>
<h2>Use of Data</h2>
<p>The Betaflight Group uses the collected data for various purposes:</p>
<ul>
<li>To provide and maintain the Service</li>
<li>To notify you about changes to our Service</li>
<li>To allow you to participate in interactive features of our Service when you choose to do so</li>
<li>To provide customer care and support</li>
<li>To provide analysis or valuable information so that we can improve the Service</li>
<li>To monitor the usage of the Service</li>
<li>To detect, prevent and address technical issues</li>
</ul>
<h2>Transfer Of Data</h2>
<p>Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.</p>
<p>If you are located outside United States and choose to provide information to us, please note that we transfer the data, including Personal Data, to United States and process it there.</p>
<p>Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.</p>
<p>The Betaflight Group will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.</p>
<h2>Disclosure Of Data</h2>
<h3>Legal Requirements</h3>
<p>The Betaflight Group may disclose your Personal Data in the good faith belief that such action is necessary to:</p>
<ul>
<li>To comply with a legal obligation</li>
<li>To protect and defend the rights or property of The Betaflight Group</li>
<li>To prevent or investigate possible wrongdoing in connection with the Service</li>
<li>To protect the personal safety of users of the Service or the public</li>
<li>To protect against legal liability</li>
</ul>
<h2>Security Of Data</h2>
<p>The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.</p>
<h2>Service Providers</h2>
<p>We may employ third party companies and individuals to facilitate our Service ("Service Providers"), to provide the Service on our behalf, to perform Service-related services or to assist us in analyzing how our Service is used.</p>
<p>These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.</p>
<h3>Analytics</h3>
<p>We may use third-party Service Providers to monitor and analyze the use of our Service.</p>
<ul>
<li>
<p><strong>Google Analytics</strong></p>
<p>Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.</p>
<p>For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: <a href="https://policies.google.com/privacy?hl=en">https://policies.google.com/privacy?hl=en</a></p>
</li>
</ul>
<h2>Links To Other Sites</h2>
<p>Our Service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.</p>
<p>We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.</p>
<h2>Children's Privacy</h2>
<p>Our Service does not address anyone under the age of 18 ("Children").</p>
<p>We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.</p>
<h2>Changes To This Privacy Policy</h2>
<p>We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.</p>
<p>We will let you know via email and/or a prominent notice on our Service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.</p>
<p>You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.</p>
<h2>Contact Us</h2>
<p>If you have any questions about this Privacy Policy, please contact us:</p>
<ul>
<li>By visiting this page on our website: https://github.com/betaflight/betaflight-configurator/issues</li>
</ul>

View file

@ -46,6 +46,9 @@
<a class="restore" href="#" i18n="initialSetupButtonRestore"></a>
</div>
</div>
<div class="default_btn initialSetupRebootBootloader">
<a class="rebootBootloader" href="#" i18n="initialSetupButtonRebootBootloader"></a>
</div>
</div>
</div>
<div class="threefourth_right setupinfo">
@ -61,6 +64,9 @@
<div class="cell_setup">
<span i18n="initialSetupBackupRestoreText"></span>
</div>
<div class="cell_setup initialSetupRebootBootloader">
<span i18n="initialSetupRebootBootloaderText"></span>
</div>
</div>
</div>
<div class="modelwrapper"></div>