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

Merge branch 'master' into fix_tab_switch

This commit is contained in:
haslinghuis 2021-11-26 02:49:56 +01:00 committed by GitHub
commit a45cc1d8c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 4438 additions and 561 deletions

View file

@ -31,6 +31,9 @@
"noticeTitle": {
"message": "Notice"
},
"dontShowAgain": {
"message": "Don't show again"
},
"operationNotSupported": {
"message": "This operation is not supported by your hardware."
},
@ -68,6 +71,9 @@
"close": {
"message": "Close"
},
"OK": {
"message": "OK"
},
"cancel": {
"message": "Cancel"
},
@ -86,6 +92,9 @@
"permanentExpertMode": {
"message": "Permanently enable Expert Mode"
},
"warningSettings": {
"message": "Show warnings"
},
"rememberLastTab": {
"message": "Reopen last tab on connect"
},
@ -1791,6 +1800,18 @@
"pidTuningDtermSetpointTransition": {
"message": "D Setpoint transition"
},
"pidTuningFeedforwardMaxRateLimit": {
"message": "Max Rate Limit"
},
"pidTuningFeedforwardMaxRateLimitHelp": {
"message": "Attenuates feedforward towards zero as the sticks move quickly towards maximum deflection (maximum set turn rate), eg at the start of a quick flip or roll, to minimise overshoot. Does nothing at the end of a flip or roll. Lower values make the attenuation start earlier. Usually this value does not require modification. The highest value consistent with acceptable overshoot at the start of rolls or flips is best."
},
"pidTuningFeedforwardJitter": {
"message": "Jitter Reduction"
},
"pidTuningFeedforwardJitterHelp": {
"message": "Jitter reduction reduces Feedforward when the sticks move slowly. This allows smooth, jitter-free flight when making smooth slow arcs, yet provides full feedforward without any delay when the sticks are moved quickly. A higher threshold value (10-12) is more useful for cinematic or HD freestyle purposes, and a slightly lower value (5) better for racing or higher speed RC links."
},
"pidTuningDtermSetpoint": {
"message": "D Setpoint Weight"
},
@ -1815,6 +1836,9 @@
"pidTuningOptionOff": {
"message": "OFF"
},
"pidTuningOptionOn": {
"message": "ON"
},
"pidTuningFeedforwardAveragingOption2Point": {
"message": "2 Point"
},
@ -3614,6 +3638,10 @@
"message": "Raises or Lowers the default Gyro Lowpass Filters in proportion to each-other. The gyro filtering is applied before the PID loop.<br />General motor noise ranges per quad class:<br /><br />6\"+ quads - generally within 100hz to 330hz<br />5\" quads - generally within 220hz to 500hz<br />Whoop to 3\" quads - generally within 300hz to 850hz<br /><br />Generally, you want to set the slider to have the Gyro Lowpass 1 Dynamic Min/Max Cutoff range cover the above.<br />However, for smoother flights use More Filtering on the slider. To get a more aggressive filter tune, use Less Filtering on the slider.<br /><br />With Less Filtering BE CAREFUL to not get radical as to cause a fly-away or burn out motors.<br />Note frame resonance issues, bad bearings, and beat up props may cause you to need more filtering.",
"description": "Gyro filtering tuning slider helpicon message"
},
"pidTuningGyroSliderEnabled": {
"message": "Use Gyro Slider",
"description": "Disable or enable Gyro Filter Tuning Slider"
},
"pidTuningDTermFilterSlider": {
"message": "D Term Filter Multiplier:",
"description": "D Term filter tuning slider label"
@ -3622,6 +3650,10 @@
"message": "Changes the D-term Lowpass Filter cutoffs.<br />Moving the slider to the left gives stronger D filtering (lower cutoff frequency); to the right gives less filtering (higher cutoff frequency).<br /><br />D-term is the most sensitive PID element to noise and resonance. It can amplify any high frequency noise by 10x to 100x plus. That's why the cutoffs for the D filters are much lower than the gyro filters.<br /><br />D-term filtering is applied after - in addition to - the gyro filtering. Strong D filtering will reduce motor heat on noisy quads and can be useful with high PID 'D' values to minimise D resonance at medium to high frequencies. However, strong D filtering may delay the D signal, worsening propwash handling and encouraging lower frequency D resonances. The default D filtering is optimal for most quads. Larger machines with high D may do better with stronger D filtering than defaults. Moving the D lowpass filter slider to the right is generally not required. On very clean builds, going to the right can attenuate propwash. It should be done very cautiously since having less filtering on D can result in sudden resonances (including motor grinding or taking off on arming) and extreme motor heat.",
"description": "D Term filtering tuning slider helpicon message"
},
"pidTuningDTermSliderEnabled": {
"message": "Use D Term Slider",
"description": "Disable or enable D Term Filter Tuning Slider"
},
"pidTuningPidSlidersHelp": {
"message": "Sliders to adjust the quad flight characteristics (PID gains)<br /><br />Damping (D gain): Resists fast movement, minimises P oscillation.<br /><br />Tracking (P and I gain): Enchances the responsiveness of the quad, if too high may cause trilling or oscillation.<br /><br />Stick Response (Feedforward): Increases the responsiveness of the quad to faster stick movements.<br /><br />Drift - Wobble (I gain, expert): Fine adjustment of I.<br /><br />Dynamic D (D Max, expert): Sets the maximum amount that D can be boosted to during fast movements.<br /><br />Pitch Damping (Pitch:Roll D ratio, expert): Increases the amount of damping on pitch relative to roll.<br /><br />Pitch Tracking (Pitch:Roll P, I and F ratio, expert): Increases stabilising strenght on pitch relative to roll.<br /><br />Master Multiplier (all gains, expert): Raises or Lowers all the PID gains, keeping their proportions constant.",
"description": "Overall helpicon message for PID tuning sliders"
@ -3634,6 +3666,14 @@
"message": "<strong>Note:</strong> Sliders are disabled because values were changed manually. Clicking the '$t(pidTuningSliderEnableButton.message)' button will activate them again. This will reset the values and any unsaved changes will be lost.",
"description": "Tuning sliders disabled note when manual changes are detected"
},
"pidTuningGyroSliderDisabled": {
"message": "<strong>Note:</strong> Gyro Slider is disabled because values were changed manually. Clicking the '$t(pidTuningGyroSliderEnableButton.message)' button will activate them again. This will reset the values and any unsaved changes will be lost.",
"description": "Gyro Tuning sliders disabled note when manual changes are detected"
},
"pidTuningDTermSliderDisabled": {
"message": "<strong>Note:</strong> DTerm Slider is disabled because values were changed manually. Clicking the '$t(pidTuningDTermSliderEnableButton.message)' button will activate them again. This will reset the values and any unsaved changes will be lost.",
"description": "DTerm Tuning sliders disabled note when manual changes are detected"
},
"pidTuningPidSlidersDisabled": {
"message": "<strong>Note:</strong> Sliders are disabled. Clicking the '$t(pidTuningSliderEnableButton.message)' button will change the PID values to match your previously saved slider position.",
"description": "Tuning sliders disabled note"
@ -3762,26 +3802,44 @@
"pidTuningGyroLowpassFiltersGroup": {
"message": "Gyro Lowpass Filters"
},
"pidTuningGyroLowpassFrequency": {
"message": "Gyro Lowpass 1 Cutoff Frequency [Hz]"
},
"pidTuningGyroLowpassType": {
"message": "Gyro Lowpass 1 Filter Type"
},
"pidTuningGyroLowpassFrequency": {
"message": "Cutoff Frequency [Hz]"
},
"pidTuningGyroLowpass": {
"message": "Gyro Lowpass 1"
},
"pidTuningGyroLowpassMode": {
"message": "Mode"
},
"pidTuningLowpassStatic": {
"message": "STATIC"
},
"pidTuningLowpassDynamic": {
"message": "DYNAMIC"
},
"pidTuningLowpassFilterType": {
"message": "Filter Type"
},
"pidTuningGyroLowpassDyn": {
"message": "Gyro Lowpass Dynamic Filter"
},
"pidTuningGyroLowpassDynMinFrequency": {
"message": "Gyro Lowpass 1 Dynamic Min Cutoff Frequency [Hz]"
"message": "Min Cutoff Frequency [Hz]"
},
"pidTuningGyroLowpassDynMaxFrequency": {
"message": "Gyro Lowpass 1 Dynamic Max Cutoff Frequency [Hz]"
"message": "Max Cutoff Frequency [Hz]"
},
"pidTuningGyroLowpassDynType": {
"message": "Gyro Lowpass 1 Dynamic Filter Type"
},
"pidTuningGyroLowpass2Frequency": {
"message": "Gyro Lowpass 2 Cutoff Frequency [Hz]"
"pidTuningGyroLowpass2": {
"message": "Gyro Lowpass 2"
},
"pidTuningGyroLowpass2Type": {
"message": "Gyro Lowpass 2 Filter Type"
"pidTuningGyroLowpass2Frequency": {
"message": "Cutoff Frequency [Hz]"
},
"pidTuningGyroLowpassFilterHelp": {
"message": "Gyro lowpass filters attenuate higher frequency noise to keep it out of the PID loop. There are two independently configurable gyro filters; by default both are active.<br /><br />The first D lowpass can be static (fixed cutoff) or dynamic; the second D lowpass is always static. When a lowpass is in dynamic mode, filter will be stronger at low throttle, and the cutoff will go higher (less filtering) as throttle increases.<br /><br />Without RPM filtering, both PT1 filters should be enabled at default (or stronger) cutoffs, with lowpass 1 in dynamic mode.<br /><br />With RPM filtering, the gyro filter slider can often be moved some way to the right. On clean quads it can go all the way right, or alternatively a single static gyro lowpass filter at 500hz may be sufficient.<br /><br />A quad will have less propwash with the least gyro filter delay (sliders to the right, higher cutoff values).<br /><br />Always check for motor heat when shifting to less gyro filtering (sliders to the right). With minimal gyro filtering, it is essential to have enough D filtering! Take care!"
@ -3792,6 +3850,12 @@
"pidTuningGyroNotchFiltersGroup": {
"message": "Gyro Notch Filters"
},
"pidTuningGyroNotchFilter": {
"message": "Gyro Notch Filter 1"
},
"pidTuningGyroNotchFilter2": {
"message": "Gyro Notch Filter 2"
},
"pidTuningGyroNotch1Frequency": {
"message": "Gyro Notch Filter 1 Center Frequency [Hz]"
},
@ -3852,6 +3916,9 @@
"pidTuningDynamicNotchMaxHzHelp": {
"message": "Set this to the highest incoming noise frequency that is needed to be controlled by the dynamic notch."
},
"pidTuningGyroLowpassType": {
"message": "Gyro Lowpass 1 Filter Type"
},
"pidTuningDynamicNotchCountHelp": {
"message": "Sets the number of dynamic notches per axis. With RPM filter enabled a value of 1 or 2 is recommended. Without RPM filter a value of 4 or 5 is recommended. Lower numbers will reduce filter delay, however it may increase motor temperature."
},
@ -3882,6 +3949,15 @@
"pidTuningFilterSettings": {
"message": "Profile dependent Filter Settings"
},
"pidTuningDTermLowpass": {
"message": "D Term Lowpass 1"
},
"pidTuningDTermLowpassMode": {
"message": "Mode"
},
"pidTuningDTermLowpass2": {
"message": "D Term Lowpass 2"
},
"pidTuningDTermLowpassFiltersGroup": {
"message": "D Term Lowpass Filters"
},
@ -3889,26 +3965,29 @@
"message": "D Term Lowpass 1 Filter Type"
},
"pidTuningDTermLowpassFrequency": {
"message": "D Term Lowpass 1 Cutoff Frequency [Hz]"
"message": "D Term Lowpass 1 Static Cutoff Frequency [Hz]"
},
"pidTuningDTermLowpass2Frequency": {
"message": "D Term Lowpass 2 Cutoff Frequency [Hz]"
"message": "D Term Lowpass 2 Static Cutoff Frequency [Hz]"
},
"pidTuningDTermLowpass2Type": {
"message": "D Term Lowpass 2 Filter Type"
},
"pidTuningDTermLowpassDyn": {
"message": "D Term Lowpass Dynamic Filter"
},
"pidTuningDTermLowpassDynMinFrequency": {
"message": "D Term Lowpass 1 Dynamic Min Cutoff Frequency [Hz]"
},
"pidTuningDTermLowpassDynMaxFrequency": {
"message": "D Term Lowpass 1 Dynamic Max Cutoff Frequency [Hz]"
},
"pidTuningDTermLowpassDynType": {
"message": "D Term Lowpass 1 Dynamic Filter Type"
},
"pidTuningDTermLowpassDynExpo": {
"message": "D Term Lowpass 1 Dynamic Curve Expo"
},
"pidTuningDTermLowpassDynType": {
"message": "D Term Lowpass 1 Dynamic Filter Type"
},
"pidTuningDTermNotchFiltersGroup": {
"message": "D Term Notch Filters"
},
@ -3918,7 +3997,7 @@
"pidTuningDTermNotchCutoff": {
"message": "D Term Notch Filter Cutoff Frequency [Hz]"
},
"pidTuningYawLospassFiltersGroup": {
"pidTuningYawLowpassFiltersGroup": {
"message": "Yaw Lowpass Filters"
},
"pidTuningYawLowpassFrequency": {
@ -6205,5 +6284,205 @@
},
"cordovaExitAppMessage": {
"message": "Do you really want to close the configurator?"
},
"dropDownSelectAll": {
"message": "[Select all]",
"description": "Select all item in the drop down/multiple select"
},
"dropDownAll": {
"message": "All",
"description": "Text indicating everything is selected in the drop down/multiple select"
},
"tabPresets": {
"message": "Presets",
"description": "Presets tab title"
},
"presetsReload": {
"message": "Reload",
"description": "Text on the reload button that appears when there in an error loading presets index"
},
"presetsAuthor": {
"message": "Author:",
"description": "Hint text in the presets detailed dialog"
},
"presetsKeywords": {
"message": "Keywords:",
"description": "Hint text in the presets detailed dialog"
},
"presetsVersions": {
"message": "Firmware:",
"description": "Hint text in the presets detailed dialog"
},
"presetsOfficial": {
"message": "Official",
"description": "Hint text in the presets detailed dialog indication preset is official"
},
"presetsCommunity": {
"message": "Community",
"description": "Hint text in the presets detailed dialog indication preset is not official but community"
},
"presetsExperimental": {
"message": "Experimental",
"description": "Hint text in the presets detailed dialog indication preset is not official but experimental"
},
"presetsApply": {
"message": "Pick",
"description": "Button to pick a preset"
},
"presetsViewOnline": {
"message": "View online&hellip;",
"description": "Link text for opening preset file online"
},
"presetsOpenDiscussion": {
"message": "Discussion&hellip;",
"description": "Link text for opening preset discussion"
},
"presetsShowCli": {
"message": "Show CLI",
"description": "Button to show CLI code of a preset"
},
"presetsHideCli": {
"message": "Hide CLI",
"description": "Button to hide CLI code of a preset"
},
"presetsOptions": {
"message": "Options",
"description": "Text label for Options drop down select"
},
"presetsFilterCategory": {
"message": "Categories",
"description": "UI filter name"
},
"presetsFilterKeyword": {
"message": "Keywords",
"description": "UI filter name"
},
"presetsFilterAuthor": {
"message": "Authors",
"description": "UI filter name"
},
"presetsFilterFirmware": {
"message": "Firmwares",
"description": "UI filter name"
},
"presetsFilterStatus": {
"message": "Status",
"description": "UI filter name - official/community/experimental"
},
"presetsLoadError": {
"message": "Error loading presets from the internet",
"description": "Error report when failed to load presets index or a specific preset"
},
"presetsButtonSave": {
"message": "Save and Reboot",
"description": "A button that saves all appied presets - analog to 'save' command in CLI"
},
"presetsButtonCancel": {
"message": "Cancel",
"description": "A button that restarts FC without saving appied presets - analog to 'exit' command in CLI"
},
"presetsApplyingPresets": {
"message": "Applying configuration...",
"description": "First label in the progress dialog when applying configuration (presets or user config)"
},
"presetsPleaseWait": {
"message": "Please wait.",
"description": "Second label in the progress dialog when applying presets"
},
"presetsCliErrorsWaring": {
"message": "<span class='message-negative'>WARNING!</span><br/>Configuration has been applied with CLI errors.",
"description": "Text to show when there are CLI errors after applying presets or user configuration"
},
"presetsSaveAnyway": {
"message": "Save anyway",
"description": "Save anyway button on the CLI errors presets dialog"
},
"presetsWarningDialogTitle": {
"message": "WARNING!",
"description": "Warning title in the warning dialog in the presets"
},
"presetsWarningDialogYesButton": {
"message": "Agree",
"description": "Agree button in the presets warning dialog"
},
"presetsWarningDialogNoButton": {
"message": "Cancel",
"description": "Cancel button in the presets warning dialog"
},
"presetsWiki": {
"message": "Presets Wiki",
"description": "Button to open Presets Wiki link"
},
"presetsBackupSave": {
"message": "Save backup",
"description": "Button to backup current configuration to file"
},
"presetsBackupLoad": {
"message": "Load backup",
"description": "Button to load backup from the file"
},
"presetsLoadingDumpAll": {
"message": "Loading current configuration from the flight controller",
"description": "Title for the waiting dialog when loading dump all into a file"
},
"dumpAllNotSavedWarning": {
"message": "Error occured while saving current configuration",
"description": "Message appears on presets tab when saving current diff all into a file has failed"
},
"presetSources": {
"message": "Preset sources...",
"description": "A button to show preset sources dialog"
},
"presetsSourcesDialogTitle": {
"message": "Preset sources",
"description": "A button to show preset sources dialog"
},
"presetsSourcesDialogAddNew": {
"message": "Add new source",
"description": "A button to show preset sources dialog"
},
"presetsSourcesDialogDefaultSourceName": {
"message": "New Custom Preset Source",
"description": "A default preset source (repo) name"
},
"presetsSourcesDialogSaveSource": {
"message": "Save",
"description": "Presets tab, sources dialog, button to save new or editable source"
},
"presetsSourcesDialogResetSource": {
"message": "Reset",
"description": "Presets tab, sources dialog, button to reset source after modifications"
},
"presetsSourcesDialogMakeSourceActive": {
"message": "Make Active",
"description": "Presets tab, sources dialog, button to make selected source active"
},
"presetsSourcesDialogDeleteSource": {
"message": "Delete",
"description": "Presets tab, sources dialog, button to delete selected source"
},
"presetsWarningNotOfficialSource": {
"message": "<span class=\"message-negative\">WARNING!</span> A third party preset source is selected.",
"description": "Warning message that shows up when a third party preset source is selected"
},
"presetsWarningBackup": {
"message": "Please make sure you backup your current configuration ('$t(presetsBackupSave.message)' button or via CLI if the button is disabled) <strong>before</strong> picking and applying presets. Otherwise there is no way to return to previous configuration after applying presets.",
"description": "Warning message that shows up at the top of the presets tab"
},
"presets_sources_dialog_warning": {
"message": "<span class=\"message-negative\">WARNING!</span> Using third party preset sources could be dangerous.<br/>Make sure you add and use only trusted sources. Malicious or bad preset sources will break your drone configuration and can potentially harm your devices.",
"description": "Warning message that shows up at the top of the preset sources dialog"
},
"presetsWarningWrongVersionConfirmation": {
"message": "Picked preset requires firmware version $1<br/> Current firmware version is $2",
"description": "Warning message that shows up at the top of the preset sources dialog"
},
"presetsNoPresetsFound": {
"message": "No presets found for the given search paramteres",
"description": "Message that apprears on presets tab if no presets were found"
},
"presetsTooManyPresetsFound": {
"message": "Reached the maximum limit of the shown presets number",
"description": "Message that apprears on presets tab if too many presets found"
}
}

View file

@ -64,6 +64,7 @@
"jquery-ui-npm": "^1.12.0",
"lru_map": "^0.3.3",
"marked": "^0.8.0",
"multiple-select": "^1.5.2",
"nw-vue-devtools-prebuilt": "^0.0.10",
"object-hash": "^2.0.3",
"select2": "^4.0.13",
@ -125,7 +126,7 @@
"temp": "^0.9.1",
"vinyl-source-stream": "^2.0.0",
"vue-template-compiler": "^2.6.12",
"yarn": "^1.22.0"
"yarn": "^1.22.17"
},
"optionalDependencies": {
"gulp-appdmg": "^1.0.3"

View file

@ -111,6 +111,11 @@ dialog {
border: 1px solid #ffbb2a;
}
.standard_input {
background: var(--boxBackground);
color: white;
}
#quad-status_wrapper {
color: #393b3a;
}

View file

@ -78,6 +78,18 @@ a.disabled {
background-position: center;
}
.standard_input {
padding-left: 3px;
height: 20px;
line-height: 20px;
text-align: left;
border-radius: 3px;
font-size: 12px;
font-weight: normal;
border: 1px solid var(--subtleAccent);
background: var(--boxBackground);
}
/* Help-Icon */
.helpicon {
float: right;
@ -740,8 +752,19 @@ input[type="number"]::-webkit-inner-spin-button {
background-image: url(../images/icons/cf_icon_link_active.svg);
}
/** Header (not phones) **/
@media not all and (max-width: 575px) {
.visible-on-phone-only {
display: none !important;
}
}
/** Header (phones) **/
@media all and (max-width: 575px) {
.visible-on-desktop-only {
display: none !important;
}
.headerbar {
height: 56px;
background: rgba(0, 0, 0, 0.15);
@ -1541,10 +1564,67 @@ dialog .dialog_toolbar .btn a.disabled {
opacity: 0.5;
}
.dialogYesNo .dialogYesNoContent {
margin-bottom: 12px;
margin-top: 12px;
white-space: pre-line;
}
.dialogYesNo .dialogYesNo-yesButton, .dialogYesNo .dialogYesNo-noButton {
margin: 0px;
}
.dialogYesNo .dialogYesNo-yesButton {
margin-right: 12px;
}
.dialogYesNo {
width: fit-content;
max-width: 400px;
}
.dialogWait {
width: fit-content;
max-width: 500px;
min-width: 300px;
}
.dialogWait .data-loading {
margin-top: 16px;
margin-bottom: 16px;
margin-left: auto;
margin-right: auto;
width: 100px;
height: 100px;
}
.dialogWait .dialogWaitTitle {
margin-left: auto;
margin-right: auto;
margin-bottom: 16px;
width: fit-content;
}
.dialogInformation .dialogInformationContent {
margin-bottom: 12px;
margin-top: 12px;
white-space: pre-line;
}
.dialogInformation .dialogInformation-confirmButton {
margin: 0px;
}
.dialogInformation {
width: fit-content;
max-width: 400px;
}
@media all and (max-width: 575px) {
dialog {
position: fixed;
width: calc(100% - 2em) !important;
width: calc(100% - 2em - 2px) !important; /* 2px - border */
max-width: unset;
height: auto !important;
bottom: 0;
top: 56px;
@ -2348,7 +2428,6 @@ input {
.noUi-connect {
box-shadow: none;
}
/** Responsive grid **/
@media all and (max-width: 575px) {
.sm, .md, .lg, .xl {

View file

@ -200,7 +200,7 @@
cursor: default;
color: #fff;
background-color: #AFAFAF;
border: none;
border: 1px solid #AFAFAF;
pointer-events: none;
text-shadow: none;
opacity: 0.5;

View file

@ -7,6 +7,8 @@
}
.tab-options .margin-bottom {
margin-bottom: 10px;
display: grid;
grid-template-columns: fit-content(300px) 1fr;
}
.tab-options select {
background: var(--boxBackground);

View file

@ -163,13 +163,17 @@
padding-right: 5px;
}
.tab-pid_tuning table.compensation td:first-child {
.tab-pid_tuning table.compensation td:first-child:not(.filterTable) {
width: 65px;
text-align: center;
vertical-align: top;
padding-top: 4px;
}
.tab-pid_tuning table.filterTable.compensation td:first-child {
width: 5%;
}
.tab-pid_tuning table.compensation td:last-child {
width: 100%;
}
@ -914,10 +918,6 @@
table-layout: auto;
}
.tab-pid_tuning table.filterTable td:first-child {
width: 25%;
}
@media only screen and (max-width: 1205px) {
.tab-pid_tuning .subtab-pid .spacer_left {

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path d="M 498.25 249.524 C 498.25 386.831 386.941 498.14 249.634 498.14 C 112.327 498.14 1.018 386.831 1.018 249.524 C 1.018 112.217 112.327 0.908 249.634 0.908 C 322.597 0.908 388.219 32.339 433.7 82.399 C 334.451 142.436 269.55 216.455 213.055 312.593 L 127.757 151.63 L 54.042 218.642 L 216.387 447.914 C 305.243 286.114 350.541 188.665 450.938 103.594 C 480.699 144.577 498.25 195.001 498.25 249.524 Z" style="fill: rgb(255, 187, 0); fill-rule: nonzero; paint-order: fill;"/>
<path d="M 498.75 249.524 C 498.75 318.28 470.839 380.622 425.786 425.676 C 380.732 470.729 318.39 498.64 249.634 498.64 C 180.878 498.64 118.536 470.729 73.482 425.676 C 28.429 380.622 0.518 318.28 0.518 249.524 C 0.518 180.768 28.429 118.426 73.482 73.372 C 118.536 28.319 180.878 0.408 249.634 0.408 C 318.39 0.408 380.732 28.319 425.786 73.372 C 470.839 118.426 498.75 180.768 498.75 249.524 Z M 425.079 74.079 C 380.151 29.152 318.185 1.408 249.634 1.408 C 181.083 1.408 119.117 29.152 74.189 74.079 C 29.262 119.007 1.518 180.973 1.518 249.524 C 1.518 318.075 29.262 380.041 74.189 424.969 C 119.117 469.896 181.083 497.64 249.634 497.64 C 318.185 497.64 380.151 469.896 425.079 424.969 C 470.006 380.041 497.75 318.075 497.75 249.524 C 497.75 180.973 470.006 119.007 425.079 74.079 Z" style="fill: none;"/>
<path d="M 498.75 249.524 C 498.75 318.28 470.839 380.622 425.786 425.676 C 380.732 470.729 318.39 498.64 249.634 498.64 C 180.878 498.64 118.536 470.729 73.482 425.676 C 28.429 380.622 0.518 318.28 0.518 249.524 C 0.518 180.768 28.429 118.426 73.482 73.372 C 118.536 28.319 180.878 0.408 249.634 0.408 C 318.39 0.408 380.732 28.319 425.786 73.372 C 470.839 118.426 498.75 180.768 498.75 249.524 Z M 425.079 74.079 C 380.151 29.152 318.185 1.408 249.634 1.408 C 181.083 1.408 119.117 29.152 74.189 74.079 C 29.262 119.007 1.518 180.973 1.518 249.524 C 1.518 318.075 29.262 380.041 74.189 424.969 C 119.117 469.896 181.083 497.64 249.634 497.64 C 318.185 497.64 380.151 469.896 425.079 424.969 C 470.006 380.041 497.75 318.075 497.75 249.524 C 497.75 180.973 470.006 119.007 425.079 74.079 Z" style="fill: none;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path d="M 499.204 185.669 C 499.204 287.561 416.604 370.161 314.712 370.161 C 212.82 370.161 130.22 287.561 130.22 185.669 C 130.22 83.777 212.82 1.177 314.712 1.177 C 416.604 1.177 499.204 83.777 499.204 185.669 Z M 314.712 48.304 C 238.662 48.304 177.012 109.954 177.012 186.004 C 177.012 262.054 238.662 323.704 314.712 323.704 C 390.762 323.704 452.412 262.054 452.412 186.004 C 452.412 109.954 390.762 48.304 314.712 48.304 Z" style="stroke-width: 50px; fill: rgb(255, 187, 0);"/>
<path d="M 4.046 435.582 L 146.371 297.269 C 161.05 321.35 181.091 341.803 204.835 356.971 L 63.129 494.665 L 4.046 435.582 Z" style="fill: rgb(255, 187, 0);"/>
</svg>

After

Width:  |  Height:  |  Size: 760 B

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="-70 0 141.7 141.7" style="enable-background:new -70 0 141.7 141.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFBB00;}
</style>
<g>
<path class="st0" d="M8.4,34.1c0,0,16.2,7.3,21.5,13.4c0,0-5.9-0.2-8.1,3.3c-2.6,3.8-1.3,11.4,6.8,17.1s17.7,6.3,22.5,6.6
c4.8,0.2,9.6,1.3,10.6,3.5C61.4,78.1,62.9,51.6,8.4,34.1z M38.7,61.1c-1-4,2.2-5.5,6.6-3.8c4,1.8,10.6,12.1,10.6,12.1
C50.6,68.7,39.9,65.2,38.7,61.1z"/>
<path class="st0" d="M-68.6,10.4l9,19.7l-8.6-3l2.6,4.8c0,0,3.5,6.8,11.9,21.5c6.6,11.4,18.2,17.1,33.8,16.4c0.5,0,1.3,0,2,0
c9.6-0.5,31.6-3,32.6-3h0.2l10.3-4L-68.6,10.4z M-20.1,65.5c-5.5,0.2-10.6-0.5-14.9-2c-6.3-2.2-11.4-6.3-14.9-12.3
c-4.3-7.6-7.3-12.9-9-16.4l7.9,2.8L-59,20.8l74.2,40.9l-1.3,0.5c-0.5,0-1.8,0.2-3.8,0.5l-50-20.2L-2.2,64
C-9,64.4-16.4,65.2-20.1,65.5z"/>
<path class="st0" d="M24.8,59.4c0,0-31.6-8.3-26.8,16.4c3.1,16.2,21.3,6.8,27.9,0.7C32.4,70.5,27,60.2,24.8,59.4z"/>
<path class="st0" d="M41.5,96.8c-14.2-6.1-22.8-16-33.6-15.2C1.3,82.1-5.7,87.6-8.5,95c4,3.3,10.1,5.5,19.7,2c0,0-6.6,8.1-20.4,7.3
c0.2,1.8,1,3.3,1.8,5c1.3,2,3,4,5,5.5c5.9-0.7,13.9-3.5,21.7-12.1c0,0-0.7,9-7.9,17.1c3.8,0.5,7.9,0.5,12.1,0.2
c3.3-1.8,7.9-5,7.9-10.9c0,0,1.5,4.6-0.7,9.9c6.6-1.3,13.4-3.8,19.9-7.6C65.2,103,70,93.2,70,93.2S55.9,102.5,41.5,96.8z
M51.8,107.3c-0.7-2-1-6.1-1-6.1c4.6,0.5,10.1-1.5,10.1-1.5C57.2,104.6,51.8,107.3,51.8,107.3z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -5,30 +5,31 @@
const ConfigStorage = {
// key can be one string, or array of strings
get: function(key, callback) {
let result = {};
if (Array.isArray(key)) {
let obj = {};
key.forEach(function (element) {
try {
obj = {...obj, ...JSON.parse(window.localStorage.getItem(element))};
result = {...result, ...JSON.parse(window.localStorage.getItem(element))};
} catch (e) {
// is okay
}
});
callback(obj);
callback?.(result);
} else {
const keyValue = window.localStorage.getItem(key);
if (keyValue) {
let obj = {};
try {
obj = JSON.parse(keyValue);
result = JSON.parse(keyValue);
} catch (e) {
// It's fine if we fail that parse
}
callback(obj);
callback?.(result);
} else {
callback({});
callback?.(result);
}
}
return result;
},
// set takes an object like {'userLanguageSelect':'DEFAULT'}
set: function(input) {

View file

@ -37,6 +37,9 @@ const TuningSliders = {
cachedGyroSliderValues: false,
cachedDTermSliderValues: false,
sliderGyroFilterDisabled: false,
sliderDTermFilterDisabled: false,
expertMode: false,
};
@ -72,7 +75,25 @@ TuningSliders.initialize = function() {
this.cachedDTermSliderValues = false;
this.updatePidSlidersDisplay();
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
// if sliders are enabled on initialization tuning has not yet updated values according to sliders.
if (!FC.TUNING_SLIDERS.slider_gyro_filter) {
this.updateFilterSlidersDisplay();
}
if (!FC.TUNING_SLIDERS.slider_dterm_filter) {
this.updateFilterSlidersDisplay();
}
this.updateFilterSlidersWarning();
$('.subtab-filter .slidersDisabled').hide();
} else {
this.updateFilterSlidersDisplay();
$('select[id="sliderGyroFilterModeSelect"]').hide();
$('select[id="sliderDTermFilterModeSelect"]').hide();
}
};
TuningSliders.updateExpertModeSlidersDisplay = function() {
@ -109,6 +130,8 @@ TuningSliders.updateExpertModeSlidersDisplay = function() {
$('#sliderPitchPIGain').prop('disabled', !this.expertMode);
$('#sliderMasterMultiplier').prop('disabled', !this.expertMode);
$('.sliderGyroFilter').prop('disabled', gyro && !this.expertMode);
$('.sliderDTermFilter').prop('disabled', dterm && !this.expertMode);
$('#sliderGyroFilterMultiplier').prop('disabled', gyro && !this.expertMode);
$('#sliderDTermFilterMultiplier').prop('disabled', dterm && !this.expertMode);
@ -118,9 +141,6 @@ TuningSliders.updateExpertModeSlidersDisplay = function() {
$('.advancedSlider').toggleClass('disabledSliders', !this.expertMode);
$('.sliderGyroFilter').toggleClass('disabledSliders', gyro && !this.expertMode);
$('.sliderDtermFilter').toggleClass('disabledSliders', dterm && !this.expertMode);
$('.advancedSliderDmaxGain').toggle(dMaxGain || this.expertMode);
$('.advancedSliderIGain').toggle(iGain || this.expertMode);
$('.advancedSliderRollPitchRatio').toggle(rpRatio || this.expertMode);
@ -227,8 +247,11 @@ TuningSliders.initGyroFilterSliderPosition = function() {
this.sliderGyroFilter = FC.TUNING_SLIDERS.slider_gyro_filter;
this.sliderGyroFilterMultiplier = FC.TUNING_SLIDERS.slider_gyro_filter_multiplier / 100;
} else {
this.sliderGyroFilterMultiplier = Math.floor((FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz + FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz + FC.FILTER_CONFIG.gyro_lowpass2_hz) /
(this.FILTER_DEFAULT.gyro_lowpass_dyn_min_hz + this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz + this.FILTER_DEFAULT.gyro_lowpass2_hz) * 100) / 100;
this.sliderGyroFilterMultiplier =
Math.floor(
(FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz + FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz + FC.FILTER_CONFIG.gyro_lowpass_hz + FC.FILTER_CONFIG.gyro_lowpass2_hz) /
(this.FILTER_DEFAULT.gyro_lowpass_dyn_min_hz + this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz + this.FILTER_DEFAULT.gyro_lowpass_hz +
this.FILTER_DEFAULT.gyro_lowpass2_hz) * 100) / 100;
}
$('#sliderGyroFilterMultiplier').val(this.sliderGyroFilterMultiplier);
@ -236,16 +259,19 @@ TuningSliders.initGyroFilterSliderPosition = function() {
};
TuningSliders.initDTermFilterSliderPosition = function() {
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
this.sliderDTermFilterMultiplier = Math.floor((FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz + FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz + FC.FILTER_CONFIG.dterm_lowpass2_hz) /
(this.FILTER_DEFAULT.dterm_lowpass_dyn_min_hz + this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz + this.FILTER_DEFAULT.dterm_lowpass2_hz) * 100) / 100;
} else {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
this.sliderDTermFilter = FC.TUNING_SLIDERS.slider_dterm_filter;
this.sliderDTermFilterMultiplier = FC.TUNING_SLIDERS.slider_dterm_filter_multiplier / 100;
} else {
this.sliderDTermFilterMultiplier =
Math.floor(
(FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz + FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz + FC.FILTER_CONFIG.dterm_lowpass_hz + FC.FILTER_CONFIG.dterm_lowpass2_hz) /
(this.FILTER_DEFAULT.dterm_lowpass_dyn_min_hz + this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz + this.FILTER_DEFAULT.dterm_lowpass_hz +
this.FILTER_DEFAULT.dterm_lowpass2_hz) * 100) / 100;
}
$('output[name="sliderDTermFilterMultiplier-number"]').val(this.sliderDTermFilterMultiplier);
$('#sliderDTermFilterMultiplier').val(this.sliderDTermFilterMultiplier);
$('output[name="sliderDTermFilterMultiplier-number"]').val(this.sliderDTermFilterMultiplier);
};
TuningSliders.resetPidSliders = function() {
@ -282,43 +308,17 @@ TuningSliders.resetPidSliders = function() {
};
TuningSliders.resetGyroFilterSlider = function() {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
if (!this.cachedGyroSliderValues) {
this.sliderGyroFilterMultiplier = 1;
}
this.sliderGyroFilter = 1;
FC.TUNING_SLIDERS.slider_gyro_filter = 1;
this.initGyroFilterSliderPosition();
} else {
this.sliderGyroFilterMultiplier = 1;
$('#sliderGyroFilterMultiplier').val(this.sliderGyroFilterMultiplier);
}
this.FilterReset = true;
this.calculateNewGyroFilters();
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
this.updateFilterSlidersDisplay();
}
};
TuningSliders.resetDTermFilterSlider = function() {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
if (!this.cachedDTermSliderValues) {
this.sliderDTermFilterMultiplier = 1;
}
this.sliderDtermFilter = 1;
FC.TUNING_SLIDERS.slider_dterm_filter = 1;
this.initDTermFilterSliderPosition();
} else {
this.sliderDTermFilterMultiplier = 1;
$('#sliderDTermFilterMultiplier').val(this.sliderDTermFilterMultiplier);
}
this.FilterReset = true;
this.calculateNewDTermFilters();
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
this.updateFilterSlidersDisplay();
}
};
TuningSliders.legacyUpdateFilterSlidersDisplay = function() {
@ -327,6 +327,8 @@ TuningSliders.legacyUpdateFilterSlidersDisplay = function() {
parseInt($('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter select[name="gyroLowpassDynType"]').val()) !== this.FILTER_DEFAULT.gyro_lowpass_type ||
parseInt($('.pid_filter input[name="gyroLowpassFrequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter input[name="gyroLowpass2Frequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass2_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter select[name="gyroLowpass2Type"]').val()) !== this.FILTER_DEFAULT.gyro_lowpass2_type) {
@ -340,6 +342,8 @@ TuningSliders.legacyUpdateFilterSlidersDisplay = function() {
parseInt($('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val()) !==
Math.round(this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz * this.sliderDTermFilterMultiplier) ||
parseInt($('.pid_filter select[name="dtermLowpassDynType"]').val()) !== this.FILTER_DEFAULT.dterm_lowpass_type ||
parseInt($('.pid_filter input[name="dtermLowpassFrequency"]').val()) !==
Math.round(this.FILTER_DEFAULT.dterm_lowpass_hz * this.sliderDTermFilterMultiplier) ||
parseInt($('.pid_filter input[name="dtermLowpass2Frequency"]').val()) !==
Math.round(this.FILTER_DEFAULT.dterm_lowpass2_hz * this.sliderDTermFilterMultiplier) ||
parseInt($('.pid_filter select[name="dtermLowpass2Type"]').val()) !== this.FILTER_DEFAULT.dterm_lowpass2_type) {
@ -347,6 +351,12 @@ TuningSliders.legacyUpdateFilterSlidersDisplay = function() {
} else {
this.cachedDTermSliderValues = true;
}
$('.tuningFilterSliders .sliderLabels tr:nth-child(2)').toggle(!this.GyroSliderUnavailable);
$('.tuningFilterSliders .sliderLabels tr:last-child').toggle(!this.DTermSliderUnavailable);
$('.tuningFilterSliders').toggle(!(this.GyroSliderUnavailable && this.DTermSliderUnavailable));
$('.subtab-filter .slidersDisabled').toggle(this.GyroSliderUnavailable || this.DTermSliderUnavailable);
};
TuningSliders.updateSlidersWarning = function(slidersUnavailable = false) {
@ -366,20 +376,20 @@ TuningSliders.updateSlidersWarning = function(slidersUnavailable = false) {
$('.subtab-pid .slidersWarning').toggle(enableWarning && !slidersUnavailable);
};
TuningSliders.updateFilterSlidersWarning = function(gyroSliderUnavailable = false, DTermSliderUnavailable = false) {
TuningSliders.updateFilterSlidersWarning = function() {
let WARNING_FILTER_GYRO_LOW_GAIN = 0.7;
let WARNING_FILTER_GYRO_HIGH_GAIN = 1.25;
let WARNING_FILTER_GYRO_HIGH_GAIN = 1.4;
let WARNING_FILTER_DTERM_LOW_GAIN = 0.7;
const WARNING_FILTER_DTERM_HIGH_GAIN = 1.25;
let WARNING_FILTER_DTERM_HIGH_GAIN = 1.4;
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
WARNING_FILTER_GYRO_LOW_GAIN = 0.45;
WARNING_FILTER_GYRO_HIGH_GAIN = 1.55;
WARNING_FILTER_DTERM_LOW_GAIN = 0.75;
WARNING_FILTER_DTERM_HIGH_GAIN = 1.25;
}
$('.subtab-filter .slidersWarning').toggle(((this.sliderGyroFilterMultiplier >= WARNING_FILTER_GYRO_HIGH_GAIN ||
this.sliderGyroFilterMultiplier <= WARNING_FILTER_GYRO_LOW_GAIN) && !gyroSliderUnavailable) ||
((this.sliderDTermFilterMultiplier >= WARNING_FILTER_DTERM_HIGH_GAIN ||
this.sliderDTermFilterMultiplier <= WARNING_FILTER_DTERM_LOW_GAIN) && !DTermSliderUnavailable));
$('.subtab-filter .slidersWarning').toggle((this.sliderGyroFilterMultiplier >= WARNING_FILTER_GYRO_HIGH_GAIN ||
this.sliderGyroFilterMultiplier <= WARNING_FILTER_GYRO_LOW_GAIN) ||
(this.sliderDTermFilterMultiplier >= WARNING_FILTER_DTERM_HIGH_GAIN || this.sliderDTermFilterMultiplier <= WARNING_FILTER_DTERM_LOW_GAIN));
};
TuningSliders.updatePidSlidersDisplay = function() {
@ -426,65 +436,116 @@ TuningSliders.updatePidSlidersDisplay = function() {
this.updateSlidersWarning(this.pidSlidersUnavailable);
};
TuningSliders.updateGyroFilterSliderDisplay = function() {
// check if enabled filters were changed manually by comparing current value and those based on slider position
const lp1DynMin = parseInt($('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val());
const lp1DynMax = parseInt($('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val());
const lp1Static = parseInt($('.pid_filter input[name="gyroLowpassFrequency"]').val());
const lp2Freq = parseInt($('.pid_filter input[name="gyroLowpass2Frequency"]').val());
const lp1DynamicMinChanged = (lp1DynMin > 0) && (lp1DynMin !== Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_min_hz * this.sliderGyroFilterMultiplier));
const lp1DynamicMaxChanged = (lp1DynMax > 0) && (lp1DynMax !== Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz * this.sliderGyroFilterMultiplier));
const lp1Changed = (lp1Static > 0) && (lp1Static !== Math.floor(this.FILTER_DEFAULT.gyro_lowpass_hz * this.sliderGyroFilterMultiplier));
const lp2Changed = (lp2Freq > 0) && (lp2Freq !== Math.floor(this.FILTER_DEFAULT.gyro_lowpass2_hz * this.sliderGyroFilterMultiplier));
const hideSlider = (lp1DynMin && (lp1DynamicMinChanged || lp1DynamicMaxChanged)) || (lp1Static && lp1Changed) || (lp2Freq && lp2Changed) || this.sliderGyroFilterDisabled;
if (hideSlider) {
this.GyroSliderUnavailable = true;
this.sliderGyroFilter = 0;
} else {
this.GyroSliderUnavailable = false;
this.sliderGyroFilter = 1;
this.cachedGyroSliderValues = true;
}
// update Gyro mode and slider
$('select[id="sliderGyroFilterModeSelect"]').val(this.sliderGyroFilter);
$('.sliderGyroFilter').toggleClass('disabledSliders', !this.sliderGyroFilter || !(lp1Static || lp1DynMin || lp2Freq));
$('#sliderGyroFilterMultiplier').prop('disabled', !this.sliderGyroFilter);
};
TuningSliders.updateDTermFilterSliderDisplay = function() {
// check if enabled filters were changed manually by comparing current value and those based on slider position
const lp1DynMin = parseInt($('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val());
const lp1DynMax = parseInt($('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val());
const lp1Static = parseInt($('.pid_filter input[name="dtermLowpassFrequency"]').val());
const lp2Freq = parseInt($('.pid_filter input[name="dtermLowpass2Frequency"]').val());
const lp1DynamicMinChanged = (lp1DynMin > 0) && (lp1DynMin !== Math.floor(this.FILTER_DEFAULT.dterm_lowpass_dyn_min_hz * this.sliderDTermFilterMultiplier));
const lp1DynamicMaxChanged = (lp1DynMax > 0) && (lp1DynMax !== Math.floor(this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz * this.sliderDTermFilterMultiplier));
const lp1Changed = (lp1Static > 0) && (lp1Static !== Math.floor(this.FILTER_DEFAULT.dterm_lowpass_hz * this.sliderDTermFilterMultiplier));
const lp2Changed = (lp2Freq > 0) && (lp2Freq !== Math.floor(this.FILTER_DEFAULT.dterm_lowpass2_hz * this.sliderDTermFilterMultiplier));
const hideSlider = (lp1DynMin && (lp1DynamicMinChanged || lp1DynamicMaxChanged)) || (lp1Static && lp1Changed) || (lp2Freq && lp2Changed) || this.sliderDTermFilterDisabled;
if (hideSlider) {
this.DTermSliderUnavailable = true;
this.sliderDTermFilter = 0;
} else {
this.DTermSliderUnavailable = false;
this.sliderDTermFilter = 1;
this.cachedDTermSliderValues = true;
}
// update DTerm mode and slider
$('select[id="sliderDTermFilterModeSelect"]').val(this.sliderDTermFilter);
$('.sliderDTermFilter').toggleClass('disabledSliders', !this.sliderDTermFilter || !(lp1Static || lp1DynMin || lp2Freq));
$('#sliderDTermFilterMultiplier').prop('disabled', !this.sliderDTermFilter);
};
TuningSliders.updateFilterSlidersDisplay = function() {
// check if filters changed manually by comparing current value and those based on slider position
this.GyroSliderUnavailable = false;
this.DTermSliderUnavailable = false;
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
this.GyroSliderUnavailable = !FC.TUNING_SLIDERS.slider_gyro_filter;
this.DTermSliderUnavailable = !FC.TUNING_SLIDERS.slider_dterm_filter;
if (parseInt($('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_min_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter select[name="gyroLowpassDynType"]').val()) !== this.FILTER_DEFAULT.gyro_lowpass_type ||
parseInt($('.pid_filter input[name="gyroLowpass2Frequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.gyro_lowpass2_hz * this.sliderGyroFilterMultiplier) ||
parseInt($('.pid_filter select[name="gyroLowpass2Type"]').val()) !== this.FILTER_DEFAULT.gyro_lowpass2_type) {
this.GyroSliderUnavailable = true;
this.sliderGyroFilter = 0;
} else {
this.cachedGyroSliderValues = true;
this.sliderGyroFilter = 1;
}
if (parseInt($('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.dterm_lowpass_dyn_min_hz * this.sliderDTermFilterMultiplier) ||
Math.abs(parseInt($('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val()) - this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz * this.sliderDTermFilterMultiplier) > 1 ||
parseInt($('.pid_filter select[name="dtermLowpassDynType"]').val()) !== this.FILTER_DEFAULT.dterm_lowpass_type ||
parseInt($('.pid_filter input[name="dtermLowpass2Frequency"]').val()) !==
Math.floor(this.FILTER_DEFAULT.dterm_lowpass2_hz * this.sliderDTermFilterMultiplier) ||
parseInt($('.pid_filter select[name="dtermLowpass2Type"]').val()) !== this.FILTER_DEFAULT.dterm_lowpass2_type) {
this.DTermSliderUnavailable = true;
this.sliderDTermFilter = 0;
} else {
this.cachedDTermSliderValues = true;
this.sliderDTermFilter = 1;
}
this.updateGyroFilterSliderDisplay();
this.updateDTermFilterSliderDisplay();
} else {
this.legacyUpdateFilterSlidersDisplay();
}
if (this.GyroSliderUnavailable) {
$('.tuningFilterSliders .sliderLabels tr:nth-child(2)').hide();
} else {
$('.tuningFilterSliders .sliderLabels tr:nth-child(2)').show();
}
if (this.DTermSliderUnavailable) {
$('.tuningFilterSliders .sliderLabels tr:last-child').hide();
} else {
$('.tuningFilterSliders .sliderLabels tr:last-child').show();
}
$('.tuningFilterSliders').toggle(!(this.GyroSliderUnavailable && this.DTermSliderUnavailable));
$('.subtab-filter .slidersDisabled').toggle(this.GyroSliderUnavailable || this.DTermSliderUnavailable);
$('.subtab-filter .nonExpertModeSlidersNote').toggle((!this.GyroSliderUnavailable || !this.DTermSliderUnavailable) && !this.expertMode);
this.updateFilterSlidersWarning(this.GyroSliderUnavailable, this.DTermSliderUnavailable);
};
TuningSliders.gyroFilterSliderEnable = function() {
this.sliderGyroFilter = true;
this.sliderGyroFilterDisabled = false;
this.FilterReset = true;
FC.TUNING_SLIDERS.slider_gyro_filter = 1;
this.writeFilterSliders();
this.updateLowpassValues();
this.updateFilterSlidersDisplay();
};
TuningSliders.gyroFilterSliderDisable = function() {
this.sliderGyroFilter = false;
this.sliderGyroFilterDisabled = true;
this.FilterReset = true;
FC.TUNING_SLIDERS.slider_gyro_filter = 0;
this.writeFilterSliders();
};
TuningSliders.dtermFilterSliderEnable = function() {
this.sliderDTermFilter = true;
this.sliderDTermFilterDisabled = false;
this.FilterReset = true;
FC.TUNING_SLIDERS.slider_dterm_filter = 1;
this.writeFilterSliders();
this.updateLowpassValues();
this.updateFilterSlidersDisplay();
};
TuningSliders.dtermFilterSliderDisable = function() {
this.sliderDTermFilter = false;
this.sliderDTermFilterDisabled = true;
this.FilterReset = true;
FC.TUNING_SLIDERS.slider_dterm_filter = 0;
this.writeFilterSliders();
};
TuningSliders.updateFormPids = function(updateSlidersOnly = false) {
if (!updateSlidersOnly) {
FC.PID_NAMES.forEach(function (elementPid, indexPid) {
@ -611,6 +672,7 @@ TuningSliders.calculateLegacyGyroFilters = function() {
// calculate, set and display new values in forms based on slider position
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_min_hz * this.sliderGyroFilterMultiplier);
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = Math.floor(this.FILTER_DEFAULT.gyro_lowpass_dyn_max_hz * this.sliderGyroFilterMultiplier);
FC.FILTER_CONFIG.gyro_lowpass_hz = Math.floor(this.FILTER_DEFAULT.gyro_lowpass_hz * this.sliderGyroFilterMultiplier);
FC.FILTER_CONFIG.gyro_lowpass2_hz = Math.floor(this.FILTER_DEFAULT.gyro_lowpass2_hz * this.sliderGyroFilterMultiplier);
FC.FILTER_CONFIG.gyro_lowpass_type = this.FILTER_DEFAULT.gyro_lowpass_type;
FC.FILTER_CONFIG.gyro_lowpass2_type = this.FILTER_DEFAULT.gyro_lowpass2_type;
@ -623,6 +685,7 @@ TuningSliders.calculateLegacyDTermFilters = function() {
// calculate, set and display new values in forms based on slider position
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = Math.round(this.FILTER_DEFAULT.dterm_lowpass_dyn_min_hz * this.sliderDTermFilterMultiplier);
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = Math.round(this.FILTER_DEFAULT.dterm_lowpass_dyn_max_hz * this.sliderDTermFilterMultiplier);
FC.FILTER_CONFIG.dterm_lowpass_hz = Math.round(this.FILTER_DEFAULT.dterm_lowpass_hz * this.sliderDTermFilterMultiplier);
FC.FILTER_CONFIG.dterm_lowpass2_hz = Math.round(this.FILTER_DEFAULT.dterm_lowpass2_hz * this.sliderDTermFilterMultiplier);
FC.FILTER_CONFIG.dterm_lowpass_type = this.FILTER_DEFAULT.dterm_lowpass_type;
FC.FILTER_CONFIG.dterm_lowpass2_type = this.FILTER_DEFAULT.dterm_lowpass2_type;
@ -635,7 +698,6 @@ TuningSliders.calculateNewGyroFilters = function() {
// this is the main calculation for Gyro Filter slider, inputs are in form of slider position values
// values get set both into forms and their respective variables
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
FC.TUNING_SLIDERS.slider_gyro_filter = this.sliderGyroFilter;
//rounds slider values to nearies multiple of 5 and passes to the FW. Avoid dividing calc by (* x 100)/5 = 20
FC.TUNING_SLIDERS.slider_gyro_filter_multiplier = Math.round(this.sliderGyroFilterMultiplier * 20) * 5;
this.writeFilterSliders();
@ -645,10 +707,9 @@ TuningSliders.calculateNewGyroFilters = function() {
};
TuningSliders.calculateNewDTermFilters = function() {
// this is the main calculation for Gyro Filter slider, inputs are in form of slider position values
// this is the main calculation for DTerm Filter slider, inputs are in form of slider position values
// values get set both into forms and their respective variables
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
FC.TUNING_SLIDERS.slider_dterm_filter = this.sliderDTermFilter;
//rounds slider values to nearies multiple of 5 and passes to the FW. Avoid divide by ROUND[(* x 100)/5 = 20]
FC.TUNING_SLIDERS.slider_dterm_filter_multiplier = Math.round(this.sliderDTermFilterMultiplier * 20) * 5;
this.writeFilterSliders();
@ -657,8 +718,20 @@ TuningSliders.calculateNewDTermFilters = function() {
}
};
// We need to write filter config to switch filters without having to save.
TuningSliders.updateFiltersInFirmware = function() {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
MSP.promise(MSPCodes.MSP_SET_FILTER_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_FILTER_CONFIG))
.then(() => MSPCodes.MSP_SET_TUNING_SLIDERS, mspHelper.crunch(MSPCodes.MSP_SET_TUNING_SLIDERS))
.then(() => MSP.promise(MSPCodes.MSP_FILTER_CONFIG))
.then(() => this.updateLowpassValues());
}
};
TuningSliders.writeFilterSliders = function () {
// send sliders to firmware
MSP.promise(MSPCodes.MSP_SET_TUNING_SLIDERS, mspHelper.crunch(MSPCodes.MSP_SET_TUNING_SLIDERS))
// pulls values from firmware
.then(() => MSP.promise(MSPCodes.MSP_FILTER_CONFIG))
.then(() => {
TuningSliders.updateLowpassValues();
@ -673,15 +746,19 @@ TuningSliders.writeFilterSliders = function () {
TuningSliders.updateLowpassValues = function() {
$('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val(FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz);
$('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val(FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz);
$('.pid_filter select[name="gyroLowpassDynType"]').val(FC.FILTER_CONFIG.gyro_lowpass_type);
$('.pid_filter input[name="gyroLowpassFrequency"]').val(FC.FILTER_CONFIG.gyro_lowpass_hz);
$('.pid_filter select[name="gyroLowpassType"]').val(FC.FILTER_CONFIG.gyro_lowpass_type);
$('.pid_filter input[name="gyroLowpass2Frequency"]').val(FC.FILTER_CONFIG.gyro_lowpass2_hz);
$('.pid_filter select[name="gyroLowpassDynType]"').val(FC.FILTER_CONFIG.gyro_lowpass_type);
$('.pid_filter select[name="gyroLowpass2Type"]').val(FC.FILTER_CONFIG.gyro_lowpass2_type);
$('output[name="sliderGyroFilterMultiplier-number"]').val(this.sliderGyroFilterMultiplier);
$('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val(FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz);
$('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val(FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz);
$('.pid_filter input[name="dtermLowpass2Frequency"]').val(FC.FILTER_CONFIG.dterm_lowpass2_hz);
$('.pid_filter select[name="dtermLowpassDynType"]').val(FC.FILTER_CONFIG.dterm_lowpass_type);
$('.pid_filter input[name="dtermLowpassFrequency"]').val(FC.FILTER_CONFIG.dterm_lowpass_hz);
$('.pid_filter select[name="dtermLowpassType"]').val(FC.FILTER_CONFIG.dterm_lowpass_type);
$('.pid_filter input[name="dtermLowpass2Frequency"]').val(FC.FILTER_CONFIG.dterm_lowpass2_hz);
$('.pid_filter select[name="dtermLowpass2Type"]').val(FC.FILTER_CONFIG.dterm_lowpass2_type);
$('output[name="sliderDTermFilterMultiplier-number"]').val(this.sliderDTermFilterMultiplier);
};

View file

@ -30,6 +30,9 @@ const CONFIGURATOR = {
cliActive: false,
cliValid: false,
productName: 'Betaflight Configurator',
cliEngineActive: false,
cliEngineValid: false,
gitChangesetId: 'unknown',
version: '0.0.1',
gitRevision: 'norevision',
latestVersion: '0.0.1',

View file

@ -853,6 +853,7 @@ const FC = {
versionFilterDefaults.gyro_lowpass_hz = 250;
versionFilterDefaults.gyro_lowpass_dyn_min_hz = 250;
versionFilterDefaults.gyro_lowpass2_hz = 500;
versionFilterDefaults.dterm_lowpass_hz = 75;
versionFilterDefaults.dterm_lowpass_dyn_min_hz = 75;
versionFilterDefaults.dterm_lowpass_dyn_max_hz = 150;
}

View file

@ -19,6 +19,7 @@ const GuiControl = function () {
this.operating_system = null;
this.interval_array = [];
this.timeout_array = [];
this.buttonDisabledClass = "disabled";
this.defaultAllowedTabsWhenDisconnected = [
'landing',
@ -36,6 +37,7 @@ const GuiControl = function () {
'power',
'adjustments',
'auxiliary',
'presets',
'cli',
'configuration',
'gps',
@ -407,5 +409,166 @@ GuiControl.prototype.isOther = function () {
return this.Mode === GUI_MODES.Other;
};
GuiControl.prototype.showYesNoDialog = function(yesNoDialogSettings) {
// yesNoDialogSettings:
// title, text, buttonYesText, buttonNoText, buttonYesCallback, buttonNoCallback
const dialog = $(".dialogYesNo");
const title = dialog.find(".dialogYesNoTitle");
const content = dialog.find(".dialogYesNoContent");
const buttonYes = dialog.find(".dialogYesNo-yesButton");
const buttonNo = dialog.find(".dialogYesNo-noButton");
title.html(yesNoDialogSettings.title);
content.html(yesNoDialogSettings.text);
buttonYes.html(yesNoDialogSettings.buttonYesText);
buttonNo.html(yesNoDialogSettings.buttonNoText);
buttonYes.off("click");
buttonNo.off("click");
buttonYes.on("click", () => {
dialog[0].close();
yesNoDialogSettings.buttonYesCallback?.();
});
buttonNo.on("click", () => {
dialog[0].close();
yesNoDialogSettings.buttonNoCallback?.();
});
dialog[0].showModal();
};
GuiControl.prototype.showWaitDialog = function(waitDialogSettings) {
// waitDialogSettings:
// title, buttonCancelCallback
const dialog = $(".dialogWait")[0];
const title = $(".dialogWaitTitle");
const buttonCancel = $(".dialogWait-cancelButton");
title.html(waitDialogSettings.title);
buttonCancel.toggle(!!waitDialogSettings.buttonCancelCallback);
buttonCancel.off("click");
buttonCancel.on("click", () => {
dialog.close();
waitDialogSettings.buttonCancelCallback?.();
});
dialog.showModal();
return dialog;
};
GuiControl.prototype.showInformationDialog = function(informationDialogSettings) {
// informationDialogSettings:
// title, text, buttonConfirmText
return new Promise(resolve => {
const dialog = $(".dialogInformation");
const title = dialog.find(".dialogInformationTitle");
const content = dialog.find(".dialogInformationContent");
const buttonConfirm = dialog.find(".dialogInformation-confirmButton");
title.html(informationDialogSettings.title);
content.html(informationDialogSettings.text);
buttonConfirm.html(informationDialogSettings.buttonConfirmText);
buttonConfirm.off("click");
buttonConfirm.on("click", () => {
dialog[0].close();
resolve();
});
dialog[0].showModal();
});
};
GuiControl.prototype.saveToTextFileDialog = function(textToSave, suggestedFileName, extension) {
return new Promise((resolve, reject) => {
const accepts = [{ description: extension.toUpperCase() + ' files', extensions: [extension] }];
chrome.fileSystem.chooseEntry(
{
type: 'saveFile',
suggestedName: suggestedFileName,
accepts: accepts,
},
entry => this._saveToTextFileDialogFileSelected(entry, textToSave, resolve, reject),
);
});
};
GuiControl.prototype._saveToTextFileDialogFileSelected = function(entry, textToSave, resolve, reject) {
checkChromeRuntimeError();
if (!entry) {
console.log('No file selected for saving');
resolve(false);
return;
}
entry.createWriter(writer => {
writer.onerror = () => {
reject();
console.error('Failed to write file');
};
writer.onwriteend = () => {
if (textToSave.length > 0 && writer.length === 0) {
writer.write(new Blob([textToSave], {type: 'text/plain'}));
} else {
resolve(true);
console.log('File write complete');
}
};
writer.truncate(0);
},
() => {
reject();
console.error('Failed to get file writer');
});
};
GuiControl.prototype.readTextFileDialog = function(extension) {
const accepts = [{ description: extension.toUpperCase() + ' files', extensions: [extension] }];
return new Promise(resolve => {
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: accepts}, function(entry) {
checkChromeRuntimeError();
if (!entry) {
console.log('No file selected for loading');
resolve(false);
return;
}
entry.file((file) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => {
console.error(reader.error);
reject();
};
reader.readAsText(file);
});
});
});
};
GuiControl.prototype.escapeHtml = function(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
// initialize object into GUI variable
window.GUI = new GuiControl();

View file

@ -404,6 +404,10 @@ function startProcess() {
// Add a little timeout to let MSP comands finish
GUI.timeout_add('wait_for_msp_finished', () => TABS.cli.initialize(content_ready, GUI.nwGui), timeout);
break;
case 'presets':
TABS.presets.initialize(content_ready, GUI.nwGui);
break;
default:
console.log(`Tab not found: ${tab}`);
}

View file

@ -1178,6 +1178,8 @@ MspHelper.prototype.process_data = function(dataHandler) {
FC.ADVANCED_TUNING.feedforward_averaging = data.readU8();
FC.ADVANCED_TUNING.feedforward_smooth_factor = data.readU8();
FC.ADVANCED_TUNING.feedforward_boost = data.readU8();
FC.ADVANCED_TUNING.feedforward_max_rate_limit = data.readU8();
FC.ADVANCED_TUNING.feedforward_jitter_factor = data.readU8();
FC.ADVANCED_TUNING.vbat_sag_compensation = data.readU8();
FC.ADVANCED_TUNING.thrustLinearization = data.readU8();
}
@ -1501,7 +1503,6 @@ MspHelper.prototype.process_data = function(dataHandler) {
FC.TUNING_SLIDERS.slider_dterm_filter_multiplier = data.readU8();
FC.TUNING_SLIDERS.slider_gyro_filter = data.readU8();
FC.TUNING_SLIDERS.slider_gyro_filter_multiplier = data.readU8();
break;
case MSPCodes.MSP_SET_VTXTABLE_POWERLEVEL:
@ -1567,7 +1568,7 @@ MspHelper.prototype.process_data = function(dataHandler) {
console.log('Name set');
break;
case MSPCodes.MSP_SET_FILTER_CONFIG:
console.log('Filter config set');
// removed as this fires a lot with firmware sliders console.log('Filter config set');
break;
case MSPCodes.MSP_SET_ADVANCED_CONFIG:
console.log('Advanced config parameters set');
@ -2151,6 +2152,8 @@ MspHelper.prototype.crunch = function(code) {
buffer.push8(FC.ADVANCED_TUNING.feedforward_averaging)
.push8(FC.ADVANCED_TUNING.feedforward_smooth_factor)
.push8(FC.ADVANCED_TUNING.feedforward_boost)
.push8(FC.ADVANCED_TUNING.feedforward_max_rate_limit)
.push8(FC.ADVANCED_TUNING.feedforward_jitter_factor)
.push8(FC.ADVANCED_TUNING.vbat_sag_compensation)
.push8(FC.ADVANCED_TUNING.thrustLinearization);
}
@ -2311,7 +2314,8 @@ MspHelper.prototype.crunch = function(code) {
break;
case MSPCodes.MSP_SET_TUNING_SLIDERS:
buffer.push8(FC.TUNING_SLIDERS.slider_pids_mode)
buffer
.push8(FC.TUNING_SLIDERS.slider_pids_mode)
.push8(FC.TUNING_SLIDERS.slider_master_multiplier)
.push8(FC.TUNING_SLIDERS.slider_roll_pitch_ratio)
.push8(FC.TUNING_SLIDERS.slider_i_gain)

View file

@ -583,13 +583,17 @@ function onClosed(result) {
CONFIGURATOR.connectionValid = false;
CONFIGURATOR.cliValid = false;
CONFIGURATOR.cliActive = false;
CONFIGURATOR.cliEngineValid = false;
CONFIGURATOR.cliEngineActive = false;
}
function read_serial(info) {
if (!CONFIGURATOR.cliActive) {
MSP.read(info);
} else if (CONFIGURATOR.cliActive) {
if (CONFIGURATOR.cliActive) {
TABS.cli.read(info);
} else if (CONFIGURATOR.cliEngineActive) {
TABS.presets.read(info);
} else {
MSP.read(info);
}
}
@ -694,7 +698,8 @@ function update_live_status() {
display: 'inline-block'
});
if (GUI.active_tab != 'cli') {
if (GUI.active_tab !== 'cli' && GUI.active_tab !== 'presets') {
MSP.send_message(MSPCodes.MSP_BOXNAMES, false, false);
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_32)) {
MSP.send_message(MSPCodes.MSP_STATUS_EX, false, false);
} else {

View file

@ -18,6 +18,8 @@ options.initialize = function (callback) {
TABS.options.initCordovaForceComputerUI();
TABS.options.initDarkTheme();
TABS.options.initShowWarnings();
GUI.content_ready(callback);
});
};
@ -28,6 +30,19 @@ options.cleanup = function (callback) {
}
};
options.initShowWarnings = function () {
ConfigStorage.get('showPresetsWarningBackup', function (result) {
if (result.showPresetsWarningBackup) {
$('div.presetsWarningBackup input').prop('checked', true);
}
$('div.presetsWarningBackup input').change(function () {
const checked = $(this).is(':checked');
ConfigStorage.set({'showPresetsWarningBackup': checked});
}).change();
});
};
options.initPermanentExpertMode = function () {
ConfigStorage.get('permanentExpertMode', function (result) {
if (result.permanentExpertMode) {

View file

@ -235,6 +235,7 @@ TABS.pid_tuning.initialize = function (callback) {
$('.pid_filter select[name="gyroLowpassType"]').val(FC.FILTER_CONFIG.gyro_lowpass_type);
$('.pid_filter select[name="gyroLowpass2Type"]').val(FC.FILTER_CONFIG.gyro_lowpass2_type);
$('.pid_filter input[name="dtermLowpass2Frequency"]').val(FC.FILTER_CONFIG.dterm_lowpass2_hz);
$('.pid_filter select[name="dtermLowpass2Type"]').val(FC.FILTER_CONFIG.dterm_lowpass2_type);
// We load it again because the limits are now bigger than in 1.16.0
$('.pid_filter input[name="gyroLowpassFrequency"]').attr("max","16000");
@ -343,16 +344,13 @@ TABS.pid_tuning.initialize = function (callback) {
$('select[id="throttleLimitType"]').val(FC.RC_TUNING.throttleLimitType);
$('.throttle_limit input[name="throttleLimitPercent"]').val(FC.RC_TUNING.throttleLimitPercent);
$('.pid_filter select[name="dtermLowpass2Type"]').val(FC.FILTER_CONFIG.dterm_lowpass2_type);
$('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val(FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz);
$('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val(FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz);
$('.pid_filter select[name="gyroLowpassDynType"]').val(FC.FILTER_CONFIG.gyro_lowpass_type);
$('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val(FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz);
$('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val(FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz);
$('.pid_filter select[name="dtermLowpassDynType"]').val(FC.FILTER_CONFIG.dterm_lowpass_type);
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
$('.pid_filter input[name="dtermLowpassDynExpo"]').val(FC.FILTER_CONFIG.dyn_lpf_curve_expo);
}
$('.pid_tuning input[name="dMinRoll"]').val(FC.ADVANCED_TUNING.dMinRoll);
$('.pid_tuning input[name="dMinPitch"]').val(FC.ADVANCED_TUNING.dMinPitch);
@ -366,8 +364,8 @@ TABS.pid_tuning.initialize = function (callback) {
} else {
$('.throttle_limit').hide();
$('.gyroLowpassDyn').hide();
$('.dtermLowpassDyn').hide();
$('.gyroLowpassDynLegacy').hide();
$('.dtermLowpassDynLegacy').hide();
$('.dtermLowpass2TypeGroup').hide();
$('.dminGroup').hide();
@ -444,7 +442,7 @@ TABS.pid_tuning.initialize = function (callback) {
dynamicNotchQ_e.val(FC.FILTER_CONFIG.dyn_notch_q);
dynamicNotchWidthPercent_e.val(FC.FILTER_CONFIG.dyn_notch_width_percent);
}
$('.rpmFilter span.suboption').toggle(checked);
}).prop('checked', FC.FILTER_CONFIG.gyro_rpm_notch_harmonics != 0).change();
} else {
@ -488,7 +486,19 @@ TABS.pid_tuning.initialize = function (callback) {
$('.rates_type').hide();
}
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
// hide legacy filter switches
$('.gyroLowpassLegacy').hide();
$('.gyroLowpassDynLegacy').hide();
$('.dtermLowpassLegacy').hide();
$('.dtermLowpassDynLegacy').hide();
$('.pid_filter input[name="dtermLowpassExpo"]').val(FC.FILTER_CONFIG.dyn_lpf_curve_expo);
} else {
// hide firmware filter switches
$('.gyroLowpass').hide();
$('.dtermLowpass').hide();
// Previous html attributes for legacy sliders
$('.pid_tuning .ROLL input[name="p"]').attr("max", "200");
$('.pid_tuning .ROLL input[name="i"]').attr("max", "200");
@ -509,6 +519,8 @@ TABS.pid_tuning.initialize = function (callback) {
$('select[id="feedforwardAveraging"]').val(FC.ADVANCED_TUNING.feedforward_averaging);
$('input[name="feedforwardSmoothFactor"]').val(FC.ADVANCED_TUNING.feedforward_smooth_factor);
$('input[name="feedforwardBoost"]').val(FC.ADVANCED_TUNING.feedforward_boost);
$('input[name="feedforwardMaxRateLimit"]').val(FC.ADVANCED_TUNING.feedforward_max_rate_limit);
$('input[name="feedforwardJitterFactor"]').val(FC.ADVANCED_TUNING.feedforward_jitter_factor);
// Vbat Sag Compensation
const vbatSagCompensationCheck = $('input[id="vbatSagCompensation"]');
@ -534,6 +546,8 @@ TABS.pid_tuning.initialize = function (callback) {
} else {
$('.vbatSagCompensation').hide();
$('.thrustLinearization').hide();
$('.feedforwardMaxRateLimit').hide();
$('.feedforwardJitterFactor').hide();
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_40)) {
$('.pid_tuning .ROLL input[name="f"]').val(FC.ADVANCED_TUNING.feedforwardRoll > 0 ? FC.ADVANCED_TUNING.feedforwardRoll : PID_DEFAULT[4]);
@ -680,6 +694,8 @@ TABS.pid_tuning.initialize = function (callback) {
$('.pid_filter input[name="gyroNotch1Frequency"]').val(checked ? hz : 0).attr('disabled', !checked)
.attr("min", checked ? 1 : 0).change();
$('.pid_filter input[name="gyroNotch1Cutoff"]').attr('disabled', !checked).change();
$('.gyroNotch1 span.suboption').toggle(checked);
});
$('input[id="gyroNotch2Enabled"]').change(function() {
@ -689,6 +705,8 @@ TABS.pid_tuning.initialize = function (callback) {
$('.pid_filter input[name="gyroNotch2Frequency"]').val(checked ? hz : 0).attr('disabled', !checked)
.attr("min", checked ? 1 : 0).change();
$('.pid_filter input[name="gyroNotch2Cutoff"]').attr('disabled', !checked).change();
$('.gyroNotch2 span.suboption').toggle(checked);
});
$('input[id="dtermNotchEnabled"]').change(function() {
@ -698,25 +716,70 @@ TABS.pid_tuning.initialize = function (callback) {
$('.pid_filter input[name="dTermNotchFrequency"]').val(checked ? hz : 0).attr('disabled', !checked)
.attr("min", checked ? 1 : 0).change();
$('.pid_filter input[name="dTermNotchCutoff"]').attr('disabled', !checked).change();
$('.dtermNotch span.suboption').toggle(checked);
});
$('input[id="gyroLowpassEnabled"]').change(function() {
// gyro filter selectors
const gyroLowpassDynMinFrequency = $('.pid_filter input[name="gyroLowpassDynMinFrequency"]');
const gyroLowpassDynMaxFrequency = $('.pid_filter input[name="gyroLowpassDynMaxFrequency"]');
const gyroLowpassFrequency = $('.pid_filter input[name="gyroLowpassFrequency"]');
const gyroLowpass2Frequency = $('.pid_filter input[name="gyroLowpass2Frequency"]');
const gyroLowpassType = $('.pid_filter select[name="gyroLowpassType"]');
const gyroLowpass2Type = $('.pid_filter select[name="gyroLowpass2Type"]');
const gyroLowpassDynType = $('.pid_filter select[name="gyroLowpassDynType"]');
const gyroLowpassDynEnabled = $('.pid_filter input[id="gyroLowpassDynEnabled"]');
const gyroLowpassEnabled = $('.pid_filter input[id="gyroLowpassEnabled"]');
const gyroLowpass2Enabled = $('.pid_filter input[id="gyroLowpass2Enabled"]');
const gyroLowpassOption = $('.gyroLowpass span.suboption');
const gyroLowpassOptionStatic = $('.gyroLowpass span.suboption.static');
const gyroLowpassOptionDynamic = $('.gyroLowpass span.suboption.dynamic');
const gyroLowpass2Option = $('.gyroLowpass2 span.suboption');
const gyroLowpassFilterMode = $('.pid_filter select[name="gyroLowpassFilterMode"]');
// dterm filter selectors
const dtermLowpassDynMinFrequency = $('.pid_filter input[name="dtermLowpassDynMinFrequency"]');
const dtermLowpassDynMaxFrequency = $('.pid_filter input[name="dtermLowpassDynMaxFrequency"]');
const dtermLowpassFrequency = $('.pid_filter input[name="dtermLowpassFrequency"]');
const dtermLowpass2Frequency = $('.pid_filter input[name="dtermLowpass2Frequency"]');
const dtermLowpassType = $('.pid_filter select[name="dtermLowpassType"]');
const dtermLowpass2Type = $('.pid_filter select[name="dtermLowpass2Type"]');
const dtermLowpassDynType = $('.pid_filter select[name="dtermLowpassDynType"]');
const dtermLowpassDynEnabled = $('.pid_filter input[id="dtermLowpassDynEnabled"]');
const dtermLowpassEnabled = $('input[id="dtermLowpassEnabled"]');
const dtermLowpass2Enabled = $('input[id="dtermLowpass2Enabled"]');
const dtermLowpassOption = $('.dtermLowpass span.suboption');
const dtermLowpassOptionStatic = $('.dtermLowpass span.suboption.static');
const dtermLowpassOptionDynamic = $('.dtermLowpass span.suboption.dynamic');
const dtermLowpass2Option = $('.dtermLowpass2 span.suboption');
const dtermLowpassFilterMode = $('.pid_filter select[name="dtermLowpassFilterMode"]');
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
// Legacy filter selectors for lowpass 1 and 2
gyroLowpassEnabled.change(function() {
const checked = $(this).is(':checked');
const disabledByDynamicLowpass = $('input[id="gyroLowpassDynEnabled"]').is(':checked');
const disabledByDynamicLowpass = gyroLowpassDynEnabled.is(':checked');
const cutoff = FC.FILTER_CONFIG.gyro_lowpass_hz > 0 ? FC.FILTER_CONFIG.gyro_lowpass_hz : FILTER_DEFAULT.gyro_lowpass_hz;
const type = FC.FILTER_CONFIG.gyro_lowpass_hz > 0 ? FC.FILTER_CONFIG.gyro_lowpass_type : FILTER_DEFAULT.gyro_lowpass_type;
$('.pid_filter input[name="gyroLowpassFrequency"]').val((checked || disabledByDynamicLowpass) ? cutoff : 0).attr('disabled', !checked);
$('.pid_filter select[name="gyroLowpassType"]').val(type).attr('disabled', !checked);
gyroLowpassFrequency.val((checked || disabledByDynamicLowpass) ? cutoff : 0).attr('disabled', !checked);
gyroLowpassType.each((i, el) => $(el).val(type).attr('disabled', !checked));
if (checked) {
$('input[id="gyroLowpassDynEnabled"]').prop('checked', false).change();
gyroLowpassDynEnabled.prop('checked', false).change();
}
self.updateFilterWarning();
});
$('input[id="gyroLowpassDynEnabled"]').change(function() {
gyroLowpassDynEnabled.change(function() {
const checked = $(this).is(':checked');
let cutoff_min = FILTER_DEFAULT.gyro_lowpass_dyn_min_hz;
let type = FILTER_DEFAULT.gyro_lowpass_type;
@ -725,45 +788,44 @@ TABS.pid_tuning.initialize = function (callback) {
type = FC.FILTER_CONFIG.gyro_lowpass_type;
}
$('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val(checked ? cutoff_min : 0).attr('disabled', !checked);
$('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').attr('disabled', !checked);
$('.pid_filter select[name="gyroLowpassDynType"]').val(type).attr('disabled', !checked);
gyroLowpassDynMinFrequency.val(checked ? cutoff_min : 0).attr('disabled', !checked);
gyroLowpassDynMaxFrequency.attr('disabled', !checked);
gyroLowpassDynType.each((i, el) => $(el).val(type).attr('disabled', !checked));
if (checked) {
$('input[id="gyroLowpassEnabled"]').prop('checked', false).change();
} else if (FC.FILTER_CONFIG.gyro_lowpass_hz > 0 && !$('input[id="gyroLowpassEnabled"]').is(':checked')) {
$('input[id="gyroLowpassEnabled"]').prop('checked', true).change();
gyroLowpassEnabled.prop('checked', false).change();
} else if (FC.FILTER_CONFIG.gyro_lowpass_hz > 0 && !gyroLowpassEnabled.is(':checked')) {
gyroLowpassEnabled.prop('checked', true).change();
}
self.updateFilterWarning();
});
$('input[id="gyroLowpass2Enabled"]').change(function() {
gyroLowpass2Enabled.change(function() {
const checked = $(this).is(':checked');
const cutoff = FC.FILTER_CONFIG.gyro_lowpass2_hz > 0 ? FC.FILTER_CONFIG.gyro_lowpass2_hz : FILTER_DEFAULT.gyro_lowpass2_hz;
const type = FC.FILTER_CONFIG.gyro_lowpass2_hz > 0 ? FC.FILTER_CONFIG.gyro_lowpass2_type : FILTER_DEFAULT.gyro_lowpass2_type;
$('.pid_filter input[name="gyroLowpass2Frequency"]').val(checked ? cutoff : 0).attr('disabled', !checked);
$('.pid_filter select[name="gyroLowpass2Type"]').val(type).attr('disabled', !checked);
gyroLowpass2Frequency.val(checked ? cutoff : 0).attr('disabled', !checked);
gyroLowpass2Type.each((i, el) => $(el).val(type).attr('disabled', !checked));
});
$('input[id="dtermLowpassEnabled"]').change(function() {
dtermLowpassEnabled.change(function() {
const checked = $(this).is(':checked');
const disabledByDynamicLowpass = $('input[id="dtermLowpassDynEnabled"]').is(':checked');
const disabledByDynamicLowpass = dtermLowpassDynEnabled.is(':checked');
const cutoff = FC.FILTER_CONFIG.dterm_lowpass_hz > 0 ? FC.FILTER_CONFIG.dterm_lowpass_hz : FILTER_DEFAULT.dterm_lowpass_hz;
const type = FC.FILTER_CONFIG.dterm_lowpass_hz > 0 ? FC.FILTER_CONFIG.dterm_lowpass_type : FILTER_DEFAULT.dterm_lowpass_type;
$('.pid_filter input[name="dtermLowpassFrequency"]').val((checked || disabledByDynamicLowpass) ? cutoff : 0).attr('disabled', !checked);
$('.pid_filter select[name="dtermLowpassType"]').val(type).attr('disabled', !checked);
dtermLowpassFrequency.val((checked || disabledByDynamicLowpass) ? cutoff : 0).attr('disabled', !checked);
dtermLowpassType.each((i, el) => $(el).val(type).attr('disabled', !checked));
if (checked) {
$('input[id="dtermLowpassDynEnabled"]').prop('checked', false).change();
dtermLowpassDynEnabled.prop('checked', false).change();
}
self.updateFilterWarning();
});
$('.dynLpfCurveExpo').toggle(semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44));
$('input[id="dtermLowpassDynEnabled"]').change(function() {
dtermLowpassDynEnabled.change(function() {
const checked = $(this).is(':checked');
let cutoff_min = FILTER_DEFAULT.dterm_lowpass_dyn_min_hz;
let type = FILTER_DEFAULT.dterm_lowpass_type;
@ -772,40 +834,236 @@ TABS.pid_tuning.initialize = function (callback) {
type = FC.FILTER_CONFIG.dterm_lowpass_type;
}
$('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val(checked ? cutoff_min : 0).attr('disabled', !checked);
$('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').attr('disabled', !checked);
$('.pid_filter select[name="dtermLowpassDynType"]').val(type).attr('disabled', !checked);
dtermLowpassDynMinFrequency.val(checked ? cutoff_min : 0).attr('disabled', !checked);
dtermLowpassDynMaxFrequency.attr('disabled', !checked);
dtermLowpassDynType.each((i, el) => $(el).val(type).attr('disabled', !checked));
if (checked) {
$('input[id="dtermLowpassEnabled"]').prop('checked', false).change();
} else if (FC.FILTER_CONFIG.dterm_lowpass_hz > 0 && !$('input[id="dtermLowpassEnabled"]').is(':checked')) {
$('input[id="dtermLowpassEnabled"]').prop('checked', true).change();
$('.pid_filter input[id="dtermLowpassDynExpoEnabled"]').prop('checked', false).change();
dtermLowpassEnabled.prop('checked', false).change();
} else if (FC.FILTER_CONFIG.dterm_lowpass_hz > 0 && !dtermLowpassEnabled.is(':checked')) {
dtermLowpassEnabled.prop('checked', true).change();
}
self.updateFilterWarning();
});
$('input[id="dtermLowpassDynExpoEnabled"]').change(function() {
const checked = $(this).is(':checked');
const curveExpo = FC.FILTER_CONFIG.dyn_lpf_curve_expo > 0 ? FC.FILTER_CONFIG.dyn_lpf_curve_expo : FILTER_DEFAULT.dyn_lpf_curve_expo;
$('.pid_filter input[name="dtermLowpassDynExpo"]').val(checked ? curveExpo : 0).attr('disabled', !checked);
});
$('input[id="dtermLowpass2Enabled"]').change(function() {
dtermLowpass2Enabled.change(function() {
const checked = $(this).is(':checked');
const cutoff = FC.FILTER_CONFIG.dterm_lowpass2_hz > 0 ? FC.FILTER_CONFIG.dterm_lowpass2_hz : FILTER_DEFAULT.dterm_lowpass2_hz;
const type = FC.FILTER_CONFIG.dterm_lowpass2_hz > 0 ? FC.FILTER_CONFIG.dterm_lowpass2_type : FILTER_DEFAULT.dterm_lowpass2_type;
$('.pid_filter input[name="dtermLowpass2Frequency"]').val(checked ? cutoff : 0).attr('disabled', !checked);
$('.pid_filter select[name="dtermLowpass2Type"]').val(type).attr('disabled', !checked);
dtermLowpass2Frequency.val(checked ? cutoff : 0).attr('disabled', !checked);
dtermLowpass2Type.each((i, el) => $(el).val(type).attr('disabled', !checked));
});
} else {
// firmware 4.3 filter selectors for lowpass 1 and 2
gyroLowpassEnabled.change(function() {
const checked = $(this).is(':checked');
let cutoffMin = FILTER_DEFAULT.gyro_lowpass_dyn_min_hz;
let cutoffMax = FILTER_DEFAULT.gyro_lowpass_dyn_max_hz;
if (FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz > 0 || FC.FILTER_CONFIG.gyro_lowpass_hz > 0) {
// lowpass1 is enabled, set the master switch on, show the label, mode selector and type fields
if (checked) {
gyroLowpassFilterMode.val(FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz > 0 ? 1 : 0).change();
} else {
// the user is disabling Lowpass 1 so set everything to zero
gyroLowpassDynMinFrequency.val(0);
gyroLowpassDynMaxFrequency.val(0);
gyroLowpassFrequency.val(0);
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = 0;
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = 0;
FC.FILTER_CONFIG.gyro_lowpass_hz = 0;
TuningSliders.updateFiltersInFirmware();
}
} else {
// lowpass 1 is disabled, set the master switch off, only show label
if (checked) {
// user is trying to enable the lowpass filter, but it was off (both cutoffs are zero)
// initialise in dynamic mode with values at sliders, or use defaults
gyroLowpassFilterMode.val(1).change();
cutoffMin = Math.floor(cutoffMin * TuningSliders.sliderGyroFilterMultiplier);
cutoffMax = Math.floor(cutoffMax * TuningSliders.sliderGyroFilterMultiplier);
gyroLowpassDynMinFrequency.val(cutoffMin);
gyroLowpassDynMaxFrequency.val(cutoffMax);
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = cutoffMin;
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = cutoffMax;
TuningSliders.updateFiltersInFirmware();
}
}
gyroLowpassOption.toggle(checked);
gyroLowpassOptionStatic.toggle(FC.FILTER_CONFIG.gyro_lowpass_hz !== 0);
gyroLowpassOptionDynamic.toggle(FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz !== 0);
});
gyroLowpassFilterMode.change(function() {
const dynMode = parseInt($(this).val());
let cutoff = FILTER_DEFAULT.gyro_lowpass_hz;
let cutoffMin = FILTER_DEFAULT.gyro_lowpass_dyn_min_hz;
let cutoffMax = FILTER_DEFAULT.gyro_lowpass_dyn_max_hz;
if (dynMode) {
// dynamic mode, set the static field min to zero
gyroLowpassFrequency.val(0);
FC.FILTER_CONFIG.gyro_lowpass_hz = 0;
// if dyn min is zero, set dyn min to sliders or default
if (!FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz) {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44) && TuningSliders.sliderGyroFilter) {
cutoffMin = Math.floor(cutoffMin * TuningSliders.sliderGyroFilterMultiplier);
cutoffMax = Math.floor(cutoffMax * TuningSliders.sliderGyroFilterMultiplier);
}
gyroLowpassDynMinFrequency.val(cutoffMin);
gyroLowpassDynMaxFrequency.val(cutoffMax);
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = cutoffMin;
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = cutoffMax;
}
gyroLowpassOptionStatic.hide();
gyroLowpassOptionDynamic.show();
TuningSliders.updateFiltersInFirmware();
} else {
// static, set the dynamic field min to zero
gyroLowpassDynMinFrequency.val(0);
gyroLowpassDynMaxFrequency.val(0);
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = 0;
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = 0;
// If static is zero, set the dynamic cutoff field according to sliders or default
if (!FC.FILTER_CONFIG.gyro_lowpass_hz) {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44) && TuningSliders.sliderGyroFilter) {
cutoff = Math.floor(FILTER_DEFAULT.gyro_lowpass_hz * TuningSliders.sliderGyroFilterMultiplier);
}
gyroLowpassFrequency.val(cutoff);
FC.FILTER_CONFIG.gyro_lowpass_hz = cutoff;
}
gyroLowpassOptionStatic.show();
gyroLowpassOptionDynamic.hide();
TuningSliders.updateFiltersInFirmware();
}
});
// switch gyro lpf2
gyroLowpass2Enabled.change(function() {
const checked = $(this).is(':checked');
let cutoff = FC.FILTER_CONFIG.gyro_lowpass2_hz > 0 ? FC.FILTER_CONFIG.gyro_lowpass2_hz : FILTER_DEFAULT.gyro_lowpass2_hz;
if (TuningSliders.sliderGyroFilter) {
cutoff = checked ? Math.floor(FILTER_DEFAULT.gyro_lowpass2_hz * TuningSliders.sliderGyroFilterMultiplier) : 0;
FC.FILTER_CONFIG.gyro_lowpass2_hz = cutoff;
TuningSliders.updateFiltersInFirmware();
}
gyroLowpass2Frequency.val(checked ? cutoff : 0).attr('disabled', !checked);
gyroLowpass2Option.toggle(checked);
self.updateFilterWarning();
});
dtermLowpassEnabled.change(function() {
const checked = $(this).is(':checked');
let cutoffMin = FILTER_DEFAULT.dterm_lowpass_dyn_min_hz;
let cutoffMax = FILTER_DEFAULT.dterm_lowpass_dyn_max_hz;
if (FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz > 0 || FC.FILTER_CONFIG.dterm_lowpass_hz > 0) {
// lowpass1 is enabled, set the master switch on, show the label, mode selector and type fields
if (checked) {
dtermLowpassFilterMode.val(FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz > 0 ? 1 : 0).change();
} else {
// the user is disabling Lowpass 1 so set everything to zero
dtermLowpassDynMinFrequency.val(0);
dtermLowpassDynMaxFrequency.val(0);
dtermLowpassFrequency.val(0);
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = 0;
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = 0;
FC.FILTER_CONFIG.dterm_lowpass_hz = 0;
TuningSliders.updateFiltersInFirmware();
}
} else {
// lowpass 1 is disabled, set the master switch off, only show label
if (checked) {
// user is trying to enable the lowpass filter, but it was off (both cutoffs are zero)
// initialise in dynamic mode with values at sliders, or use defaults
dtermLowpassFilterMode.val(1).change();
if (TuningSliders.sliderDTermFilter) {
cutoffMin = Math.floor(cutoffMin * TuningSliders.sliderDTermFilterMultiplier);
cutoffMax = Math.floor(cutoffMax * TuningSliders.sliderDTermFilterMultiplier);
}
dtermLowpassDynMinFrequency.val(cutoffMin);
dtermLowpassDynMaxFrequency.val(cutoffMax);
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = cutoffMin;
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = cutoffMax;
TuningSliders.updateFiltersInFirmware();
}
}
dtermLowpassOption.toggle(checked);
dtermLowpassOptionStatic.toggle(FC.FILTER_CONFIG.dterm_lowpass_hz !== 0);
dtermLowpassOptionDynamic.toggle(FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz !== 0);
});
dtermLowpassFilterMode.change(function() {
const dynMode = parseInt($(this).val());
let cutoff = FILTER_DEFAULT.dterm_lowpass_hz;
let cutoffMin = FILTER_DEFAULT.dterm_lowpass_dyn_min_hz;
let cutoffMax = FILTER_DEFAULT.dterm_lowpass_dyn_max_hz;
if (dynMode) {
// dynamic mode, set the static field min to zero
dtermLowpassFrequency.val(0);
FC.FILTER_CONFIG.dterm_lowpass_hz = 0;
// if dyn min is zero, set dyn min to sliders or default
if (!FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz) {
if (TuningSliders.sliderDTermFilter) {
cutoffMin = Math.floor(cutoffMin * TuningSliders.sliderDTermFilterMultiplier);
cutoffMax = Math.floor(cutoffMax * TuningSliders.sliderDTermFilterMultiplier);
}
dtermLowpassDynMinFrequency.val(cutoffMin);
dtermLowpassDynMaxFrequency.val(cutoffMax);
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = cutoffMin;
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = cutoffMax;
}
dtermLowpassOptionStatic.hide();
dtermLowpassOptionDynamic.show();
TuningSliders.updateFiltersInFirmware();
} else {
// static, set the dynamic field min to zero
dtermLowpassDynMinFrequency.val(0);
dtermLowpassDynMaxFrequency.val(0);
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = 0;
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = 0;
// If static is zero, set the dynamic cutoff field according to sliders or default
if (!FC.FILTER_CONFIG.dterm_lowpass_hz) {
if (TuningSliders.sliderDTermFilter) {
cutoff = Math.floor(FILTER_DEFAULT.dterm_lowpass_hz * TuningSliders.sliderDTermFilterMultiplier);
}
dtermLowpassFrequency.val(cutoff);
FC.FILTER_CONFIG.dterm_lowpass_hz = cutoff;
}
dtermLowpassOptionStatic.show();
dtermLowpassOptionDynamic.hide();
TuningSliders.updateFiltersInFirmware();
}
});
dtermLowpass2Enabled.change(function() {
const checked = $(this).is(':checked');
let cutoff = FC.FILTER_CONFIG.dterm_lowpass2_hz > 0 ? FC.FILTER_CONFIG.dterm_lowpass2_hz : FILTER_DEFAULT.dterm_lowpass2_hz;
if (TuningSliders.sliderDTermFilter) {
cutoff = checked ? Math.floor(FILTER_DEFAULT.dterm_lowpass2_hz * TuningSliders.sliderDTermFilterMultiplier) : 0;
FC.FILTER_CONFIG.dterm_lowpass2_hz = cutoff;
TuningSliders.updateFiltersInFirmware();
}
dtermLowpass2Frequency.val(checked ? cutoff : 0).attr('disabled', !checked);
dtermLowpass2Option.toggle(checked);
self.updateFilterWarning();
});
}
$('input[id="yawLowpassEnabled"]').change(function() {
const checked = $(this).is(':checked');
const cutoff = FC.FILTER_CONFIG.yaw_lowpass_hz > 0 ? FC.FILTER_CONFIG.yaw_lowpass_hz : FILTER_DEFAULT.yaw_lowpass_hz;
$('.pid_filter input[name="yawLowpassFrequency"]').val(checked ? cutoff : 0).attr('disabled', !checked);
$('.yawLowpass span.suboption').toggle(checked);
});
// The notch cutoff must be smaller than the notch frecuency
@ -834,17 +1092,37 @@ TABS.pid_tuning.initialize = function (callback) {
}).change();
// Initial state of the filters: enabled or disabled
$('input[id="gyroNotch1Enabled"]').prop('checked', FC.FILTER_CONFIG.gyro_notch_hz != 0).change();
$('input[id="gyroNotch2Enabled"]').prop('checked', FC.FILTER_CONFIG.gyro_notch2_hz != 0).change();
$('input[id="dtermNotchEnabled"]').prop('checked', FC.FILTER_CONFIG.dterm_notch_hz != 0).change();
$('input[id="gyroLowpassEnabled"]').prop('checked', FC.FILTER_CONFIG.gyro_lowpass_hz != 0).change();
$('input[id="gyroLowpassDynEnabled"]').prop('checked', FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz != 0 && FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz < FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz).change();
$('input[id="dtermLowpassDynExpoEnabled"]').prop('checked', FC.FILTER_CONFIG.dyn_lpf_curve_expo != 0).change();
$('input[id="gyroLowpass2Enabled"]').prop('checked', FC.FILTER_CONFIG.gyro_lowpass2_hz != 0).change();
$('input[id="dtermLowpassEnabled"]').prop('checked', FC.FILTER_CONFIG.dterm_lowpass_hz != 0).change();
$('input[id="dtermLowpassDynEnabled"]').prop('checked', FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz != 0 && FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz < FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz).change();
$('input[id="dtermLowpass2Enabled"]').prop('checked', FC.FILTER_CONFIG.dterm_lowpass2_hz != 0).change();
$('input[id="yawLowpassEnabled"]').prop('checked', FC.FILTER_CONFIG.yaw_lowpass_hz != 0).change();
$('input[id="gyroNotch1Enabled"]').prop('checked', FC.FILTER_CONFIG.gyro_notch_hz !== 0).change();
$('input[id="gyroNotch2Enabled"]').prop('checked', FC.FILTER_CONFIG.gyro_notch2_hz !== 0).change();
$('input[id="dtermNotchEnabled"]').prop('checked', FC.FILTER_CONFIG.dterm_notch_hz !== 0).change();
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
gyroLowpassEnabled.prop('checked', FC.FILTER_CONFIG.gyro_lowpass_hz !== 0 || FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz !== 0).change();
dtermLowpassEnabled.prop('checked', FC.FILTER_CONFIG.dterm_lowpass_hz !== 0 || FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz !== 0).change();
if (FC.FILTER_CONFIG.gyro_lowpass_hz > 0) {
gyroLowpassFilterMode.val(0).change();
} else if (FC.FILTER_CONFIG.gyroLowpassDynMinFrequency > 0) {
gyroLowpassFilterMode.val(1).change();
}
if (FC.FILTER_CONFIG.dterm_lowpass_hz > 0) {
dtermLowpassFilterMode.val(0).change();
} else if (FC.FILTER_CONFIG.dtermLowpassDynMinFrequency > 0) {
dtermLowpassFilterMode.val(1).change();
}
} else {
gyroLowpassEnabled.prop('checked', FC.FILTER_CONFIG.gyro_lowpass_hz !== 0).change();
gyroLowpassDynEnabled.prop('checked', FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz !== 0 &&
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz < FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz).change();
dtermLowpassEnabled.prop('checked', FC.FILTER_CONFIG.dterm_lowpass_hz !== 0).change();
dtermLowpassDynEnabled.prop('checked', FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz !== 0 &&
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz < FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz).change();
}
gyroLowpass2Enabled.prop('checked', FC.FILTER_CONFIG.gyro_lowpass2_hz !== 0).change();
dtermLowpass2Enabled.prop('checked', FC.FILTER_CONFIG.dterm_lowpass2_hz !== 0).change();
$('input[id="yawLowpassEnabled"]').prop('checked', FC.FILTER_CONFIG.yaw_lowpass_hz !== 0).change();
self.updatePIDColors();
}
@ -973,7 +1251,7 @@ TABS.pid_tuning.initialize = function (callback) {
}
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) {
FC.FILTER_CONFIG.dterm_lowpass_type = $('.pid_filter select[name="dtermLowpassType"]').val();
FC.FILTER_CONFIG.dterm_lowpass_type = parseInt($('.pid_filter select[name="dtermLowpassType"]').val());
FC.ADVANCED_TUNING.itermThrottleThreshold = parseInt($('.antigravity input[name="itermThrottleThreshold"]').val());
FC.ADVANCED_TUNING.itermAcceleratorGain = parseInt($('.antigravity input[name="itermAcceleratorGain"]').val() * 1000);
}
@ -983,6 +1261,7 @@ TABS.pid_tuning.initialize = function (callback) {
FC.FILTER_CONFIG.gyro_lowpass_type = parseInt($('.pid_filter select[name="gyroLowpassType"]').val());
FC.FILTER_CONFIG.gyro_lowpass2_type = parseInt($('.pid_filter select[name="gyroLowpass2Type"]').val());
FC.FILTER_CONFIG.dterm_lowpass2_hz = parseInt($('.pid_filter input[name="dtermLowpass2Frequency"]').val());
FC.FILTER_CONFIG.dterm_lowpass2_type = parseInt($('.pid_filter select[name="dtermLowpass2Type"]').val());
}
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_40)) {
@ -1013,18 +1292,19 @@ TABS.pid_tuning.initialize = function (callback) {
FC.RC_TUNING.throttleLimitType = $('select[id="throttleLimitType"]').val();
FC.RC_TUNING.throttleLimitPercent = parseInt($('.throttle_limit input[name="throttleLimitPercent"]').val());
FC.FILTER_CONFIG.dterm_lowpass2_type = $('.pid_filter select[name="dtermLowpass2Type"]').val();
FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz = parseInt($('.pid_filter input[name="gyroLowpassDynMinFrequency"]').val());
FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz = parseInt($('.pid_filter input[name="gyroLowpassDynMaxFrequency"]').val());
FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz = parseInt($('.pid_filter input[name="dtermLowpassDynMinFrequency"]').val());
FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz = parseInt($('.pid_filter input[name="dtermLowpassDynMaxFrequency"]').val());
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
if (FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz > 0 && FC.FILTER_CONFIG.gyro_lowpass_dyn_min_hz < FC.FILTER_CONFIG.gyro_lowpass_dyn_max_hz ) {
FC.FILTER_CONFIG.gyro_lowpass_type = $('.pid_filter select[name="gyroLowpassDynType"]').val();
}
if (FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz > 0 && FC.FILTER_CONFIG.dterm_lowpass_dyn_min_hz < FC.FILTER_CONFIG.dterm_lowpass_dyn_max_hz ) {
FC.FILTER_CONFIG.dterm_lowpass_type = $('.pid_filter select[name="dtermLowpassDynType"]').val();
}
}
FC.ADVANCED_TUNING.dMinRoll = parseInt($('.pid_tuning input[name="dMinRoll"]').val());
FC.ADVANCED_TUNING.dMinPitch = parseInt($('.pid_tuning input[name="dMinPitch"]').val());
@ -1069,10 +1349,11 @@ TABS.pid_tuning.initialize = function (callback) {
FC.FILTER_CONFIG.dyn_lpf_curve_expo = parseInt($('.pid_filter input[name="dtermLowpassDynExpo"]').val());
FC.ADVANCED_TUNING.vbat_sag_compensation = $('input[id="vbatSagCompensation"]').is(':checked') ? parseInt($('input[name="vbatSagValue"]').val()) : 0;
FC.ADVANCED_TUNING.thrustLinearization = $('input[id="thrustLinearization"]').is(':checked') ? parseInt($('input[name="thrustLinearValue"]').val()) : 0;
FC.FILTER_CONFIG.dyn_lpf_curve_expo = parseInt($('.pid_filter input[name="dtermLowpassExpo"]').val());
FC.FILTER_CONFIG.dyn_notch_count = parseInt($('.pid_filter input[name="dynamicNotchCount"]').val());
FC.TUNING_SLIDERS.slider_pids_mode = TuningSliders.sliderPidsMode;
//rounds slider values to nearies multiple of 5 and passes to the FW. Avoid dividing calc by (* x 100)/5 = 20
//round slider values to nearest multiple of 5 and passes to the FW. Avoid dividing calc by (* x 100)/5 = 20
FC.TUNING_SLIDERS.slider_master_multiplier = Math.round(TuningSliders.sliderMasterMultiplier * 20) * 5;
FC.TUNING_SLIDERS.slider_d_gain = Math.round(TuningSliders.sliderDGain * 20) * 5;
FC.TUNING_SLIDERS.slider_pi_gain = Math.round(TuningSliders.sliderPIGain * 20) * 5;
@ -1488,11 +1769,14 @@ TABS.pid_tuning.initialize = function (callback) {
}
populateFilterTypeSelector('gyroLowpassType', loadFilterTypeValues());
populateFilterTypeSelector('gyroLowpassDynType', loadFilterTypeValues());
populateFilterTypeSelector('gyroLowpass2Type', loadFilterTypeValues());
populateFilterTypeSelector('dtermLowpassType', loadFilterTypeValues());
populateFilterTypeSelector('dtermLowpass2Type', loadFilterTypeValues());
if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
populateFilterTypeSelector('gyroLowpassDynType', loadFilterTypeValues());
populateFilterTypeSelector('dtermLowpassDynType', loadFilterTypeValues());
}
pid_and_rc_to_form();
@ -1900,6 +2184,8 @@ TABS.pid_tuning.initialize = function (callback) {
const SLIDER_STEP_UPPER = semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44) ? 0.05 : 0.1;
const sliderPidsModeSelect = $('#sliderPidsModeSelect');
const sliderGyroFilterModeSelect = $('#sliderGyroFilterModeSelect');
const sliderDTermFilterModeSelect = $('#sliderDTermFilterModeSelect');
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
if (self.retainConfiguration) {
@ -1908,6 +2194,8 @@ TABS.pid_tuning.initialize = function (callback) {
self.saveInitialSettings();
}
sliderPidsModeSelect.val(FC.TUNING_SLIDERS.slider_pids_mode);
sliderGyroFilterModeSelect.val(FC.TUNING_SLIDERS.slider_gyro_filter);
sliderDTermFilterModeSelect.val(FC.TUNING_SLIDERS.slider_dterm_filter);
} else {
$('#dMinSwitch').change(function() {
TuningSliders.setDMinFeatureEnabled($(this).is(':checked'));
@ -1930,9 +2218,9 @@ TABS.pid_tuning.initialize = function (callback) {
// disable slides if Integrated Yaw is enabled or Slider PID mode is set to OFF
$('input[id="useIntegratedYaw"]').change(() => TuningSliders.updatePidSlidersDisplay());
// trigger Slider Display update when PID mode is changed
// trigger Slider Display update when PID / Filter mode is changed
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
$('select[id="sliderPidsModeSelect"]').on('change', function () {
sliderPidsModeSelect.on('change', function () {
const setMode = parseInt($(this).val());
TuningSliders.sliderPidsMode = setMode;
@ -1956,6 +2244,30 @@ TABS.pid_tuning.initialize = function (callback) {
}
}).trigger('change');
sliderGyroFilterModeSelect.change(function() {
const mode = parseInt($(this).find(':selected').val());
if (mode === 0) {
TuningSliders.gyroFilterSliderEnable();
} else {
TuningSliders.gyroFilterSliderDisable();
}
});
sliderDTermFilterModeSelect.change(function() {
const mode = parseInt($(this).find(':selected').val());
if (mode === 0) {
TuningSliders.dtermFilterSliderEnable();
} else {
TuningSliders.dtermFilterSliderDisable();
}
});
// initial gyro mode
sliderGyroFilterModeSelect.val(TuningSliders.sliderGyroFilter);
// initial dterm mode
sliderDTermFilterModeSelect.val(TuningSliders.sliderDTermFilter);
}
let allPidTuningSliders;
@ -2164,13 +2476,10 @@ TABS.pid_tuning.initialize = function (callback) {
TuningSliders.calculateNewDTermFilters();
}
});
// enable Filter sliders button
// enable Filter sliders button (legacy sliders)
$('a.buttonFilterTuningSliders').click(function() {
if (TuningSliders.GyroSliderUnavailable) {
//set Slider mode to ON when re-enabling Sliders
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
FC.TUNING_SLIDERS.slider_gyro_filter = 1;
}
// update switchery dynamically based on defaults
$('input[id="gyroLowpassDynEnabled"]').prop('checked', false).click();
$('input[id="gyroLowpassEnabled"]').prop('checked', true).click();
@ -2179,11 +2488,6 @@ TABS.pid_tuning.initialize = function (callback) {
self.analyticsChanges['GyroFilterTuningSlider'] = "On";
}
if (TuningSliders.DTermSliderUnavailable) {
//set Slider mode to ON when re-enabling Sliders
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
FC.TUNING_SLIDERS.slider_dterm_filter = 1;
}
// update switchery dynamically based on defaults
$('input[id="dtermLowpassDynEnabled"]').prop('checked', false).click();
$('input[id="dtermLowpassEnabled"]').prop('checked', true).click();
$('input[id="dtermLowpass2Enabled"]').prop('checked', false).click();
@ -2200,7 +2504,15 @@ TABS.pid_tuning.initialize = function (callback) {
}
});
// update on filter value or type changes
$('.pid_filter tr:not(.newFilter) input, .pid_filter tr:not(.newFilter) select').on('input', function() {
$('.pid_filter tr:not(.newFilter) input, .pid_filter tr:not(.newFilter) select').on('input', function(e) {
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) {
// because legacy / firmware slider inputs for lowpass1 are duplicate the value isn't updated so set it here.
if (e.target.type === 'number') {
$(`.pid_filter input[name="${e.target.name}"]`).val(e.target.value);
} else if (e.target.type === 'select-one') {
$(`.pid_filter select[name="${e.target.name}"]`).val(e.target.value);
}
}
TuningSliders.updateFilterSlidersDisplay();
if (TuningSliders.GyroSliderUnavailable) {
self.analyticsChanges['GyroFilterTuningSlider'] = "Off";
@ -2741,10 +3053,12 @@ TABS.pid_tuning.updateRatesLabels = function() {
};
TABS.pid_tuning.updateFilterWarning = function() {
const gyroDynamicLowpassEnabled = $('input[id="gyroLowpassDynEnabled"]').is(':checked');
const gyroLowpass1Enabled = $('input[id="gyroLowpassEnabled"]').is(':checked');
const dtermDynamicLowpassEnabled = $('input[id="dtermLowpassDynEnabled"]').is(':checked');
const dtermLowpass1Enabled = $('input[id="dtermLowpassEnabled"]').is(':checked');
const gyroLowpassFilterMode = parseInt($('.pid_filter select[name="gyroLowpassFilterMode"]').val());
const gyroDynamicLowpassEnabled = gyroLowpassFilterMode === 1;
const gyroLowpass1Enabled = !gyroLowpassFilterMode;
const dtermLowpassFilterMode = parseInt($('.pid_filter select[name="dtermLowpassFilterMode"]').val());
const dtermDynamicLowpassEnabled = dtermLowpassFilterMode === 1;
const dtermLowpass1Enabled = !dtermLowpassFilterMode;
const warningE = $('#pid-tuning .filterWarning');
const warningDynamicNotchE = $('#pid-tuning .dynamicNotchWarning');
if (!(gyroDynamicLowpassEnabled || gyroLowpass1Enabled) || !(dtermDynamicLowpassEnabled || dtermLowpass1Enabled)) {

View file

@ -22,6 +22,11 @@
<link type="text/css" rel="stylesheet" href="./css/tabs/led_strip.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/sensors.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/cli.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./tabs/presets/presets.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./tabs/presets/TitlePanel/PresetTitlePanel.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./tabs/presets/DetailedDialog/PresetsDetailedDialog.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./tabs/presets/SourcesDialog/SourcesDialog.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./tabs/presets/SourcesDialog/SourcePanel.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/logging.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/onboard_logging.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/tabs/firmware_flasher.css" media="all"/>
@ -42,6 +47,7 @@
<link type="text/css" rel="stylesheet" href="./components/MotorOutputReordering/Styles.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/select2_custom.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./node_modules/select2/dist/css/select2.min.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./node_modules/multiple-select/dist/multiple-select.min.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./components/EscDshotDirection/Styles.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/dark-theme.css" media="all" disabled/>
@ -126,6 +132,18 @@
<script type="text/javascript" src="./js/tabs/cli.js"></script>
<!-- TODO: might be removed when everythign is in modules -->
<script type="module" src="./js/tabs/logging.js"></script>
<script type="text/javascript" src="./tabs/presets/presets.js"></script>
<script type="text/javascript" src="./tabs/presets/CliEngine.js"></script>
<script type="text/javascript" src="./tabs/presets/PickedPreset.js"></script>
<script type="text/javascript" src="./tabs/presets/DetailedDialog/PresetsDetailedDialog.js"></script>
<script type="text/javascript" src="./tabs/presets/TitlePanel/PresetTitlePanel.js"></script>
<script type="text/javascript" src="./tabs/presets/PresetsRepoIndexed/PresetsRepoIndexed.js"></script>
<script type="text/javascript" src="./tabs/presets/PresetsRepoIndexed/PresetsGithubRepo.js"></script>
<script type="text/javascript" src="./tabs/presets/PresetsRepoIndexed/PresetsWebsiteRepo.js"></script>
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcesDialog.js"></script>
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcePanel.js"></script>
<script type="text/javascript" src="./tabs/presets/SourcesDialog/PresetSource.js"></script>
<script type="text/javascript" src="./js/tabs/logging.js"></script>
<script type="text/javascript" src="./js/tabs/onboard_logging.js"></script>
<script type="text/javascript" src="./js/FirmwareCache.js"></script>
<script type="text/javascript" src="./js/tabs/failsafe.js"></script>
@ -145,6 +163,7 @@
<script type="text/javascript" src="./components/MotorOutputReordering/MotorOutputReorderingCanvas.js"></script>
<script type="text/javascript" src="./components/MotorOutputReordering/MotorOutputReorderingConfig.js"></script>
<script type="text/javascript" src="./node_modules/select2/dist/js/select2.min.js"></script>
<script type="text/javascript" src="./node_modules/multiple-select/dist/multiple-select.min.js"></script>
<script type="text/javascript" src="./js/utils/EscProtocols.js"></script>
<script type="text/javascript" src="./js/utils/DshotCommand.js"></script>
<script type="text/javascript" src="./components/EscDshotDirection/EscDshotDirectionComponent.js"></script>
@ -326,6 +345,9 @@
i18n_title="tabPower"></a></li>
<li class="tab_failsafe"><a href="#" i18n="tabFailsafe" class="tabicon ic_failsafe"
i18n_title="tabFailsafe"></a></li>
<li class="tab_presets">
<a href="#" i18n="tabPresets" class="tabicon ic_wizzard" i18n_title="tabPresets"></a>
</li>
<li class="tab_pid_tuning"><a href="#" i18n="tabPidTuning" class="tabicon ic_pid"
i18n_title="tabPidTuning"></a></li>
<li class="tab_receiver"><a href="#" i18n="tabReceiver" class="tabicon ic_rx" i18n_title="tabReceiver"></a></li>
@ -462,6 +484,30 @@
</div>
</dialog>
<dialog class="dialogYesNo">
<h3 class="dialogYesNoTitle"></h3>
<div class="dialogYesNoContent"></div>
<div class="buttons">
<a href="#" class="dialogYesNo-yesButton regular-button"></a>
<a href="#" class="dialogYesNo-noButton regular-button"></a>
</div>
</dialog>
<dialog class="dialogWait">
<div class="data-loading"></div>
<h3 class="dialogWaitTitle"></h3>
<div class="buttons">
<a href="#" class="dialogWait-cancelButton regular-button" i18n="cancel"></a>
</div>
</dialog>
<dialog class="dialogInformation">
<h3 class="dialogInformationTitle"></h3>
<div class="dialogInformationContent"></div>
<div class="buttons">
<a href="#" class="dialogInformation-confirmButton regular-button"></a>
</div>
</dialog>
<!-- CORDOVA_INCLUDE cordova.js -->
</body>
</html>

View file

@ -62,5 +62,20 @@
</div>
</div>
</div>
<div class="gui_box">
<div class="gui_box_titlebar">
<div class="spacer_box_title" i18n="warningSettings"></div>
</div>
<div class="spacer">
<div class="presetsWarningBackup margin-bottom">
<div>
<input type="checkbox" class="toggle" />
</div>
<span class="freelabel" i18n="presetsWarningBackup"></span>
</div>
</div>
</div>
</div>
</div>

View file

@ -605,12 +605,19 @@
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardGroupHelp"></div>
<span i18n="pidTuningFeedforwardGroup"></span>
<span class="feedforwardTransition suboption">
<input type="number" name="feedforwardTransition-number" step="0.01" min="0.00" max="1.00"/>
<span class="feedforwardJitterFactor suboption">
<input type="number" name="feedforwardJitterFactor" step="1" min="0" max="20"/>
<label>
<span i18n="pidTuningFeedforwardTransition"></span>
<span i18n="pidTuningFeedforwardJitter"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardJitterHelp"></div>
</span>
<span class="feedforwardOption feedforwardSmoothFactor suboption">
<input type="number" name="feedforwardSmoothFactor" step="1" min="0" max="75" />
<label for="feedforwardSmoothFactor">
<span i18n="pidTuningFeedforwardSmoothFactor"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardTransitionHelp"></div>
</span>
<span class="feedforwardOption feedforwardAveraging suboption">
@ -625,19 +632,28 @@
</label>
</span>
<span class="feedforwardOption feedforwardSmoothFactor suboption">
<input type="number" name="feedforwardSmoothFactor" step="1" min="0" max="75" />
<label for="feedforwardSmoothFactor">
<span i18n="pidTuningFeedforwardSmoothFactor"></span>
</label>
</span>
<span class="feedforwardOption feedforwardBoost suboption">
<input type="number" name="feedforwardBoost" step="1" min="0" max="50" />
<label for="feedforwardBoost">
<span i18n="pidTuningFeedforwardBoost"></span>
</label>
</span>
<span class="feedforwardMaxRateLimit suboption">
<input type="number" name="feedforwardMaxRateLimit" step="1" min="0" max="150"/>
<label>
<span i18n="pidTuningFeedforwardMaxRateLimit"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardMaxRateLimitHelp"></div>
</span>
<span class="feedforwardTransition suboption">
<input type="number" name="feedforwardTransition-number" step="0.01" min="0.00" max="1.00"/>
<label>
<span i18n="pidTuningFeedforwardTransition"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningFeedforwardTransitionHelp"></div>
</span>
</td>
</tr>
@ -1139,6 +1155,7 @@
</th>
</tr>
</table>
<table class="sliderLabels">
<tr class="xs sliderHeaders">
<td colspan="5">
@ -1153,7 +1170,7 @@
<output type="number" name="sliderGyroFilterMultiplier-number"></output>
</td>
<td colspan="3">
<input type="range" min="0.0" max="2.0" step="0.05" class="tuningSlider" id="sliderGyroFilterMultiplier" />
<input type="range" min="0.1" max="2.0" step="0.05" class="tuningSlider" id="sliderGyroFilterMultiplier" />
</td>
<td>
<div class="helpicon cf_tip" i18n_title="pidTuningGyroFilterSliderHelp"></div>
@ -1164,7 +1181,7 @@
<span i18n="pidTuningDTermFilterSlider"></span>
</td>
</tr>
<tr class="sliderDtermFilter">
<tr class="sliderDTermFilter">
<td class="sm-min">
<span i18n="pidTuningDTermFilterSlider"></span>
</td>
@ -1172,7 +1189,7 @@
<output type="number" name="sliderDTermFilterMultiplier-number"></output>
</td>
<td colspan="3">
<input type="range" min="0.0" max="2.0" step="0.05" class="tuningSlider" id="sliderDTermFilterMultiplier" />
<input type="range" min="0.1" max="2.0" step="0.05" class="tuningSlider" id="sliderDTermFilterMultiplier" />
</td>
<td>
<div class="helpicon cf_tip" i18n_title="pidTuningDTermFilterSliderHelp"></div>
@ -1194,123 +1211,161 @@
<table class="pid_titlebar new_rates">
<tr>
<th i18n="pidTuningNonProfileFilterSettings"></th>
<td>
<select id="sliderGyroFilterModeSelect" class="sliderMode">
<option value="0" i18n="pidTuningOptionOff"></option>
<option value="1" i18n="pidTuningOptionOn"></option>
</select>
</td>
</tr>
</table>
<table class="filterTable">
<table class="filterTable compensation">
<tr>
<th colspan="2">
<div class="pid_mode">
<div i18n="pidTuningGyroLowpassFiltersGroup" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpassFilterHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpassFilterHelp"></div>
</div>
</th>
</tr>
<tr class="gyroLowpassDyn">
<!-- legacy filter switches -->
<tr class="gyroLowpassDynLegacy">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="gyroLowpassDynEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" name="gyroLowpassDynMinFrequency" step="1" min="1" max="1000"/></span>
</span>
</td>
<td>
<div>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpassDyn"></div>
<span i18n="pidTuningGyroLowpassDyn"></span>
<span class="suboption">
<input type="number" name="gyroLowpassDynMinFrequency" step="1" min="1" max="1000"/>
<label>
<span i18n="pidTuningGyroLowpassDynMinFrequency"></span>
</label>
</div>
</td>
</tr>
<tr class="gyroLowpassDyn">
<td>
<span class="inputValue"><input type="number" name="gyroLowpassDynMaxFrequency" step="1" min="1" max="1000"/></span>
</td>
<td>
<div>
</span>
<span class="suboption">
<input type="number" name="gyroLowpassDynMaxFrequency" step="1" min="1" max="1000"/>
<label>
<span i18n="pidTuningGyroLowpassDynMaxFrequency"></span>
</label>
</div>
</td>
</tr>
<tr class="gyroLowpassDyn">
<td>
</span>
<span class="suboption">
<select name="gyroLowpassDynType">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningGyroLowpassDynType"></span>
</label>
</div>
</span>
</td>
</tr>
<tr>
<tr class="gyroLowpassLegacy">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="gyroLowpassEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" class="nonProfile" name="gyroLowpassFrequency" step="1" min="1" max="255"/></span>
</span>
</td>
<td>
<div>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpass"></div>
<span i18n="pidTuningGyroLowpass"></span>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroLowpassFrequency" step="1" min="1" max="255"/>
<label>
<span i18n="pidTuningGyroLowpassFrequency"></span>
</label>
</div>
</td>
</tr>
<tr>
<td>
</span>
<span class="suboption">
<select name="gyroLowpassType">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningGyroLowpassType"></span>
</label>
</div>
</span>
</td>
</tr>
<!-- firmware filter switches -->
<tr class="gyroLowpass">
<td>
<span class="inputSwitch"><input type="checkbox" id="gyroLowpassEnabled" class="toggle" /></span>
</td>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpass"></div>
<span i18n="pidTuningGyroLowpass"></span>
<span class="suboption gyroLowpassFilterModeGroup">
<span class="inputValue">
<select name="gyroLowpassFilterMode">
<option value="0" i18n="pidTuningLowpassStatic"></option>
<option value="1" i18n="pidTuningLowpassDynamic"></option>
</select>
</span>
<label>
<span i18n="pidTuningGyroLowpassMode"></span>
</label>
</span>
<span class="suboption static">
<input type="number" class="nonProfile" name="gyroLowpassFrequency" step="1" min="1" max="255"/>
<label>
<span i18n="pidTuningGyroLowpassFrequency"></span>
</label>
</span>
<span class="suboption dynamic">
<input type="number" name="gyroLowpassDynMinFrequency" step="1" min="1" max="1000"/>
<label>
<span i18n="pidTuningGyroLowpassDynMinFrequency"></span>
</label>
</span>
<span class="suboption dynamic">
<input type="number" name="gyroLowpassDynMaxFrequency" step="1" min="1" max="1000"/>
<label>
<span i18n="pidTuningGyroLowpassDynMaxFrequency"></span>
</label>
</span>
<span class="suboption">
<select name="gyroLowpassType">
<!-- Populated on execution -->
</select>
<label>
<span i18n="pidTuningLowpassFilterType"></span>
</label>
</span>
</td>
</tr>
<tr class="gyroLowpass2">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="gyroLowpass2Enabled" class="toggle" /></span>
<span class="inputValue"><input type="number" class="nonProfile" name="gyroLowpass2Frequency" step="1" min="1" max="16000"/></span>
</span>
</td>
<td>
<div>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroLowpass2"></div>
<span i18n="pidTuningGyroLowpass2"></span>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroLowpass2Frequency" step="1" min="1" max="16000"/>
<label>
<span i18n="pidTuningGyroLowpass2Frequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="gyroLowpass2Type">
<td>
<span class="suboption">
<select name="gyroLowpass2Type">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningGyroLowpass2Type"></span>
<span i18n="pidTuningLowpassFilterType"></span>
</label>
</div>
</span>
</td>
</tr>
@ -1323,107 +1378,88 @@
</th>
</tr>
<tr class="newFilter">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="gyroNotch1Enabled" class="toggle" /></span>
<span class="inputValue"><input type="number" class="nonProfile" name="gyroNotch1Frequency" step="1" min="1" max="16000"/></span>
</span>
</td>
<td>
<div>
<tr class="newFilter gyroNotch1">
<td><span class="inputSwitch"><input type="checkbox" id="gyroNotch1Enabled" class="toggle" /></span></td>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroNotchFilter"></div>
<span i18n="pidTuningGyroNotchFilter"></span>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroNotch1Frequency" step="1" min="1" max="16000"/>
<label>
<span i18n="pidTuningGyroNotch1Frequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="newFilter">
<td>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroNotch1Cutoff" step="1" min="0" max="16000"/>
</td>
<td>
<div>
<label>
<span i18n="pidTuningGyroNotch1Cutoff"></span>
</label>
</div>
</span>
</td>
</tr>
<tr class="newFilter gyroNotch2">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="gyroNotch2Enabled" class="toggle" /></span>
<span class="inputValue"><input type="number" class="nonProfile" name="gyroNotch2Frequency" step="1" min="1" max="16000"/></span>
</span>
</td>
<td>
<div>
<td><span class="inputSwitch"><input type="checkbox" id="gyroNotch2Enabled" class="toggle" /></span></td>
<td colspan="2">
<div class="helpicon cf_tip" i18n_title="pidTuningGyroNotchFilter2"></div>
<span i18n="pidTuningGyroNotchFilter2"></span>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroNotch2Frequency" step="1" min="1" max="16000"/>
<label>
<span i18n="pidTuningGyroNotch2Frequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="newFilter gyroNotch2">
<td>
<span class="suboption">
<input type="number" class="nonProfile" name="gyroNotch2Cutoff" step="1" min="0" max="16000"/>
</td>
<td>
<div>
<label>
<span i18n="pidTuningGyroNotch2Cutoff"></span>
</label>
</div>
</span>
</td>
</tr>
<tr>
<th class="rpmFilter" colspan="2">
<div class="pid_mode rpmFilter">
<div i18n="pidTuningRpmFilterGroup" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmFilterHelp" ></div>
<div i18n="pidTuningRpmFilterGroup"></div>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmFilterHelp"></div>
</div>
</th>
</tr>
<tr class="newFilter rpmFilter">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="rpmFilterEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" class="nonProfile" name="rpmFilterHarmonics" step="1" min="1" max="3"/></span>
</span>
</td>
<td>
<div>
<td><span class="inputSwitch"><input type="checkbox" id="rpmFilterEnabled" class="toggle" /></span></td>
<td colspan="2">
<span i18n="pidTuningRpmFilterGroup"></span>
<span class="suboption">
<input type="number" class="nonProfile" name="rpmFilterHarmonics" step="1" min="1" max="3"/>
<label>
<span i18n="pidTuningRpmHarmonics"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmHarmonicsHelp" ></div>
</div>
</td>
</tr>
<tr class="newFilter rpmFilter">
<td>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmHarmonicsHelp"></div>
</span>
<span class="suboption">
<input type="number" name="rpmFilterMinHz" step="1" min="50" max="200"/>
</td>
<td>
<div>
<label>
<span i18n="pidTuningRpmMinHz"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmMinHzHelp" ></div>
</div>
<div class="helpicon cf_tip" i18n_title="pidTuningRpmMinHzHelp"></div>
</span>
</td>
</tr>
<tr>
<th class="dynamicNotch" colspan="2">
<div class="pid_mode dynamicNotch">
<div i18n="pidTuningDynamicNotchFilterGroup" ></div>
<div class="helpicon cf_tip dynamicNotchHelp" i18n_title="pidTuningDynamicNotchFilterHelp" ></div>
<div i18n="pidTuningDynamicNotchFilterGroup"></div>
<div class="helpicon cf_tip dynamicNotchHelp" i18n_title="pidTuningDynamicNotchFilterHelp"></div>
</div>
</th>
</tr>
@ -1438,7 +1474,7 @@
<label>
<span i18n="pidTuningDynamicNotchRange"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchRangeHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchRangeHelp"></div>
</div>
</td>
</tr>
@ -1451,7 +1487,7 @@
<label>
<span i18n="pidTuningDynamicNotchWidthPercent"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchWidthPercentHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchWidthPercentHelp"></div>
</div>
</td>
</tr>
@ -1464,7 +1500,7 @@
<label>
<span i18n="pidTuningDynamicNotchQ"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchQHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchQHelp"></div>
</div>
</td>
</tr>
@ -1477,7 +1513,7 @@
<label>
<span i18n="pidTuningDynamicNotchCount"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchCountHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchCountHelp"></div>
</div>
</td>
</tr>
@ -1490,7 +1526,7 @@
<label>
<span i18n="pidTuningDynamicNotchMinHz"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchMinHzHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchMinHzHelp"></div>
</div>
</td>
</tr>
@ -1503,7 +1539,7 @@
<label>
<span i18n="pidTuningDynamicNotchMaxHz"></span>
</label>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchMaxHzHelp" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDynamicNotchMaxHzHelp"></div>
</div>
</td>
</tr>
@ -1514,201 +1550,219 @@
<table class="pid_titlebar new_rates">
<tr>
<th i18n="pidTuningFilterSettings"></th>
<td>
<select id="sliderDTermFilterModeSelect" class="sliderMode">
<option value="0" i18n="pidTuningOptionOff"></option>
<option value="1" i18n="pidTuningOptionOn"></option>
</select>
</td>
</tr>
</table>
<table class="filterTable">
<table class="filterTable compensation">
<tr>
<th colspan="2">
<th colspan="3">
<div class="pid_mode">
<div i18n="pidTuningDTermLowpassFiltersGroup" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpassFilterHelp" ></div>
</div>
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpassFilterHelp"></div>
</div>
</th>
</tr>
<tr class="dtermLowpassDyn">
<!-- Legacy filter switches -->
<tr class="dtermLowpassDynLegacy">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="dtermLowpassDynEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" name="dtermLowpassDynMinFrequency" step="1" min="1" max="1000"/></span>
</span>
</td>
<td>
<div>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpassDyn"></div>
<span i18n="pidTuningDTermLowpassDyn"></span>
<span class="suboption">
<span class="inputValue"><input type="number" name="dtermLowpassDynMinFrequency" step="1" min="1" max="1000"/></span>
<label>
<span i18n="pidTuningDTermLowpassDynMinFrequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="dtermLowpassDyn">
<td>
<span class="suboption">
<span class="inputValue"><input type="number" name="dtermLowpassDynMaxFrequency" step="1" min="1" max="1000"/></span>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermLowpassDynMaxFrequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="dtermLowpassDyn">
<td>
<span class="suboption">
<select name="dtermLowpassDynType">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermLowpassDynType"></span>
</label>
</div>
</td>
</tr>
<tr class="dtermLowpassDyn dynLpfCurveExpo">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="dtermLowpassDynExpoEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" name="dtermLowpassDynExpo" step="1" min="1" max="10"/></span>
</span>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermLowpassDynExpo"></span>
</label>
</div>
</td>
</tr>
<tr>
<tr class="dtermLowpassLegacy">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="dtermLowpassEnabled" class="toggle" /></span>
<span class="inputValue"><input type="number" name="dtermLowpassFrequency" step="1" min="1" max="16000"/></span>
</span>
</td>
<td>
<div>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpass"></div>
<span i18n="pidTuningDTermLowpass"></span>
<span class="suboption">
<span class="inputValue"><input type="number" name="dtermLowpassFrequency" step="1" min="1" max="16000"/></span>
<label>
<span i18n="pidTuningDTermLowpassFrequency"></span>
</label>
</div>
</td>
</tr>
<tr>
<td>
</span>
<span class="suboption">
<select name="dtermLowpassType">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermLowpassType"></span>
</label>
</div>
</span>
</td>
</tr>
<!-- firmware filter switches -->
<tr class="dtermLowpass">
<td>
<span class="inputSwitch"><input type="checkbox" id="dtermLowpassEnabled" class="toggle" /></span>
</td>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpass"></div>
<span i18n="pidTuningDTermLowpass"></span>
<span class="suboption dtermLowpassFilterModeGroup">
<span class="inputValue">
<select name="dtermLowpassFilterMode">
<option value="0" i18n="pidTuningLowpassStatic"></option>
<option value="1" i18n="pidTuningLowpassDynamic"></option>
</select>
</span>
<label>
<span i18n="pidTuningDTermLowpassMode"></span>
</label>
</span>
<span class="suboption static">
<span class="inputValue"><input type="number" name="dtermLowpassFrequency" step="1" min="1" max="16000"/></span>
<label>
<span i18n="pidTuningDTermLowpassFrequency"></span>
</label>
</span>
<span class="suboption dynamic">
<span class="inputValue"><input type="number" name="dtermLowpassDynMinFrequency" step="1" min="1" max="1000"/></span>
<label>
<span i18n="pidTuningDTermLowpassDynMinFrequency"></span>
</label>
</span>
<span class="suboption dynamic">
<span class="inputValue"><input type="number" name="dtermLowpassDynMaxFrequency" step="1" min="1" max="1000"/></span>
<label>
<span i18n="pidTuningDTermLowpassDynMaxFrequency"></span>
</label>
</span>
<span class="suboption dynamic">
<span class="inputValue"><input type="number" name="dtermLowpassExpo" step="1" min="1" max="10"/></span>
<label>
<span i18n="pidTuningDTermLowpassDynExpo"></span>
</label>
</span>
<span class="suboption">
<select name="dtermLowpassType">
<!-- Populated on execution -->
</select>
<label>
<span i18n="pidTuningLowpassFilterType"></span>
</label>
</span>
</span>
</td>
</tr>
<tr class="dtermLowpass2">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="dtermLowpass2Enabled" class="toggle" /></span>
<span class="inputValue"><input type="number" name="dtermLowpass2Frequency" step="1" min="1" max="16000"/></span>
</span>
</td>
<td>
<div>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningDTermLowpass2"></div>
<span i18n="pidTuningDTermLowpass2"></span>
<span class="suboption">
<span class="inputValue"><input type="number" name="dtermLowpass2Frequency" step="1" min="1" max="16000"/></span>
<label>
<span i18n="pidTuningDTermLowpass2Frequency"></span>
</label>
</div>
</td>
</tr>
</span>
<tr class="dtermLowpass2 dtermLowpass2TypeGroup">
<td>
<span class="suboption dtermLowpass2TypeGroup">
<select name="dtermLowpass2Type">
<!-- Populated on execution -->
</select>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermLowpass2Type"></span>
<span i18n="pidTuningLowpassFilterType"></span>
</label>
</div>
</span>
</td>
</tr>
<tr>
<th colspan="2">
<th colspan="3">
<div class="pid_mode">
<div i18n="pidTuningDTermNotchFiltersGroup" ></div>
<div class="helpicon cf_tip" i18n_title="pidTuningNotchFilterHelp" ></div>
</div>
</th>
</tr>
<tr class="newFilter">
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="dtermNotchEnabled" class="toggle" /></span>
<tr class="newFilter dtermNotch">
<td><span class="inputSwitch"><input type="checkbox" id="dtermNotchEnabled" class="toggle" /></span>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningDTermNotchFiltersGroup"></div>
<span i18n="pidTuningDTermNotchFiltersGroup"></span>
<span class="suboption">
<span class="inputValue"><input type="number" name="dTermNotchFrequency" step="1" min="1" max="16000"/></span>
<label><span i18n="pidTuningDTermNotchFrequency"></span></label>
</span>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermNotchFrequency"></span>
</label>
</div>
</td>
</tr>
<tr class="newFilter">
<td>
<span class="suboption">
<input type="number" name="dTermNotchCutoff" step="1" min="0" max="16000"/>
</td>
<td>
<div>
<label>
<span i18n="pidTuningDTermNotchCutoff"></span>
</label>
</div>
<label><span i18n="pidTuningDTermNotchCutoff"></span></label>
</span>
</td>
</tr>
<tr>
<th colspan="2">
<div class="pid_mode" i18n="pidTuningYawLospassFiltersGroup" ></div>
<th colspan="3">
<div class="pid_mode" i18n="pidTuningYawLowpassFiltersGroup" ></div>
</th>
</tr>
<tr>
<td>
<span class="groupSwitchValue">
<span class="inputSwitch"><input type="checkbox" id="yawLowpassEnabled" class="toggle" /></span>
<tr class="yawLowpass">
<td><span class="inputSwitch"><input type="checkbox" id="yawLowpassEnabled" class="toggle" /></span>
<td colspan="3">
<div class="helpicon cf_tip" i18n_title="pidTuningYawLowpassFiltersGroup"></div>
<span i18n="pidTuningYawLowpassFiltersGroup"></span>
<span class="suboption">
<span class="inputValue"><input type="number" name="yawLowpassFrequency" step="1" min="1" max="500"/></span>
</span>
</td>
<td>
<div>
<label>
<span i18n="pidTuningYawLowpassFrequency"></span>
</label>
</div>
</span>
</td>
</tr>
</table>

View file

@ -0,0 +1,275 @@
'use strict';
class CliEngine
{
constructor(currentTab) {
this._currentTab = currentTab;
this._lineDelayMs = 15;
this._profileSwitchDelayMs = 100;
this._cliBuffer = "";
this._window = null;
this._windowWrapper = null;
this._cliErrorsCount = 0;
this._sendCommandsProgress = 0;
this._onSendCommandsProgressChange = undefined;
this._responseCallback = undefined;
this._onRowCameCallback = undefined;
}
setUi(window, windowWrapper, textarea) {
this._window = window;
this._windowWrapper = windowWrapper;
this._setTextareaListen(textarea);
}
get errorsCount() { return this._cliErrorsCount; }
setProgressCallback(sendCommandsProgressCallBack) {
this._onSendCommandsProgressChange = sendCommandsProgressCallBack;
}
_reportSendCommandsProgress(value) {
this._sendCommandsProgress = value;
this._onSendCommandsProgressChange?.(value);
}
enterCliMode() {
const bufferOut = new ArrayBuffer(1);
const bufView = new Uint8Array(bufferOut);
this.cliBuffer = "";
bufView[0] = 0x23;
serial.send(bufferOut);
}
_setTextareaListen(textarea) {
// Tab key detection must be on keydown,
// `keypress`/`keyup` happens too late, as `textarea` will have already lost focus.
textarea.keydown(event => {
if (event.which === CliEngine.s_tabCode) {
// prevent default tabbing behaviour
event.preventDefault();
}
});
textarea.keypress(event => {
if (event.which === CliEngine.s_enterKeyCode) {
event.preventDefault(); // prevent the adding of new line
const outString = textarea.val();
this.executeCommands(outString);
textarea.val('');
}
});
// give input element user focus
textarea.focus();
}
close(callback) {
this.send(this.getCliCommand('exit\r', ""), function () { //this.cliBuffer
if (callback) {
callback();
}
});
}
executeCommands(outString) {
const outputArray = outString.split("\n");
return this.executeCommandsArray(outputArray);
}
executeCommandsArray(strings) {
this._reportSendCommandsProgress(0);
const totalCommandsCount = strings.length;
return Promise.reduce(strings, (delay, line, index) => {
return new Promise((resolve) => {
GUI.timeout_add('CLI_send_slowly', () => {
let processingDelay = this.lineDelayMs;
line = line.trim();
if (line.toLowerCase().startsWith('profile')) {
processingDelay = this.profileSwitchDelayMs;
}
const isLastCommand = totalCommandsCount === index + 1;
if (isLastCommand && this.cliBuffer) {
line = this.getCliCommand(line, this.cliBuffer);
}
this.sendLine(line, ()=>{ /* empty on-send callback */ }, () => {
resolve(processingDelay);
this._reportSendCommandsProgress(100.0 * index / totalCommandsCount);
});
}, delay);
});
}, 0).then(() => {
this._reportSendCommandsProgress(100);
});
}
removePromptHash(promptText) {
return promptText.replace(/^# /, '');
}
cliBufferCharsToDelete(command, buffer) {
let commonChars = 0;
for (let i = 0; i < buffer.length; i++) {
if (command[i] === buffer[i]) {
commonChars++;
} else {
break;
}
}
return buffer.length - commonChars;
}
commandWithBackSpaces(command, buffer, noOfCharsToDelete) {
const backspace = String.fromCharCode(127);
return backspace.repeat(noOfCharsToDelete) + command.substring(buffer.length - noOfCharsToDelete, command.length);
}
getCliCommand(command, cliBuffer) {
const buffer = this.removePromptHash(cliBuffer);
const bufferRegex = new RegExp('^' + buffer, 'g');
if (command.match(bufferRegex)) {
return command.replace(bufferRegex, '');
}
const noOfCharsToDelete = this.cliBufferCharsToDelete(command, buffer);
return this.commandWithBackSpaces(command, buffer, noOfCharsToDelete);
}
writeToOutput(text) {
this._windowWrapper.append(text);
this._window.scrollTop(this._windowWrapper.height());
}
writeLineToOutput(text) {
if (text.startsWith("###ERROR")) {
this.writeToOutput(`<span class="error_message">${text}</span><br>`);
this._cliErrorsCount++;
} else {
this.writeToOutput(text + "<br>");
}
this._responseCallback?.();
this._onRowCameCallback?.(text);
}
subscribeOnRowCame(callback) {
this._onRowCameCallback = callback;
}
unsubscribeOnRowCame() {
this._onRowCameCallback = undefined;
}
readSerial(readInfo) {
/* Some info about handling line feeds and carriage return
line feed = LF = \n = 0x0A = 10
carriage return = CR = \r = 0x0D = 13
MAC only understands CR
Linux and Unix only understand LF
Windows understands (both) CRLF
Chrome OS currently unknown
*/
const data = new Uint8Array(readInfo.data);
let validateText = "";
let sequenceCharsToSkip = 0;
for (const charCode of data) {
const currentChar = String.fromCharCode(charCode);
if (!CONFIGURATOR.cliEngineValid) {
// try to catch part of valid CLI enter message
validateText += currentChar;
this.writeToOutput(currentChar);
continue;
}
const escapeSequenceCode = 27;
const escapeSequenceCharLength = 3;
if (charCode === escapeSequenceCode && !sequenceCharsToSkip) { // ESC + other
sequenceCharsToSkip = escapeSequenceCharLength;
}
if (sequenceCharsToSkip) {
sequenceCharsToSkip--;
continue;
}
this._adjustCliBuffer(charCode);
if (this.cliBuffer === 'Rebooting' && CliEngine.s_backspaceCode !== charCode) {
CONFIGURATOR.cliEngineActive = false;
CONFIGURATOR.cliEngineValid = false;
GUI.log(i18n.getMessage('cliReboot'));
reinitialiseConnection(this._currentTab);
}
}
if (!CONFIGURATOR.cliEngineValid && validateText.indexOf('CLI') !== -1) {
GUI.log(i18n.getMessage('cliEnter'));
CONFIGURATOR.cliEngineValid = true;
}
}
sendLine(line, callback, responseCallback) {
this.send(line + '\n', callback, responseCallback);
}
send(line, callback, responseCallback) {
this._responseCallback = responseCallback;
const bufferOut = new ArrayBuffer(line.length);
const bufView = new Uint8Array(bufferOut);
for (let cKey = 0; cKey < line.length; cKey++) {
bufView[cKey] = line.charCodeAt(cKey);
}
serial.send(bufferOut, callback);
}
_adjustCliBuffer(newCharacterCode) {
const currentChar = String.fromCharCode(newCharacterCode);
switch (newCharacterCode) {
case CliEngine.s_lineFeedCode:
if (GUI.operating_system === "Windows") {
this.writeLineToOutput(this.cliBuffer);
this.cliBuffer = "";
}
break;
case CliEngine.s_carriageReturnCode:
if (GUI.operating_system !== "Windows") {
this.writeLineToOutput(this.cliBuffer);
this.cliBuffer = "";
}
break;
case 60:
this.cliBuffer += '&lt';
break;
case 62:
this.cliBuffer += '&gt';
break;
case CliEngine.s_backspaceCode:
this.cliBuffer = this.cliBuffer.slice(0, -1);
break;
default:
this.cliBuffer += currentChar;
}
}
}
CliEngine.s_backspaceCode = 8;
CliEngine.s_lineFeedCode = 10;
CliEngine.s_carriageReturnCode = 13;
CliEngine.s_tabCode = 9;
CliEngine.s_enterKeyCode = 13;
CliEngine.s_commandDiffAll = "diff all";
CliEngine.s_commandDefaultsNoSave = "defaults nosave";
CliEngine.s_commandSave = "save";
CliEngine.s_commandExit = "exit";

View file

@ -0,0 +1,112 @@
.preset_detailed_dialog_title {
padding-left: 10px;
padding-right: 10px;
}
#presets_detailed_dialog_content_wrapper .preset_title_panel_title {
padding-bottom: .5ex;
border-bottom: 1px solid var(--accent);
margin-bottom: 2ex;
}
.presets-detailed-dialog-property-table {
margin-top: 10px;
margin-bottom: 10px;
}
.presets-detailed-dialog-property-table td {
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
}
.presets-detailed-dialog-property-table-first-col {
width: 150px;
}
.presets_detailed_dialog_text {
padding-top: 6px;
padding-bottom: 6px;
margin-top: 12px;
overflow-y: scroll;
height: 270px;
font-size: 110%;
white-space: pre-line;
user-select: text;
}
#presets_detailed_dialog_properties {
height: 360px;
}
#presets_detailed_dialog_content_wrapper .left-panel {
position: absolute;
left: 0px;
padding-left: 20px;
}
#presets_detailed_dialog_loading {
height: 300px;
}
#presets_options_select {
width: 296px;
}
#presets_options_panel {
height: 26px;
margin-top: 6px;
}
#presets_options_panel > span{
line-height: 26px;
}
#preset_options_label {
width : 100px;
display: inline-block;
}
@media all and (max-width: 575px) {
.presets_detailed_dialog_text {
height: unset;
padding-bottom: 100px;
}
#presets_detailed_dialog .content_toolbar {
position: fixed;
height: 70px;
}
#presets_options_panel {
height: 26px;
margin-top: 6px;
grid-template-columns: 100px 1fr;
display: grid;
}
#presets_options_select {
width: 100%;
}
#presets_detailed_dialog .btn {
margin-left: 0px;
width: 100%;
}
#presets_detailed_dialog .btn .left-panel {
margin-left: 12px;
margin-bottom: 12px;
padding-left: 0px;
}
#presets_detailed_dialog .btn .left-panel .regular-button {
float: none;
display: inline-block;
}
#presets_detailed_dialog .btn .regular-button:not(#presets_detailed_dialog .btn .left-panel .regular-button) {
margin-top: 40px;
}
}

View file

@ -0,0 +1,28 @@
<div id="presets_detailed_dialog_content_wrapper">
<div id="presets_detailed_dialog_content">
<div id="presets_detailed_dialog_properties">
<div class="preset_detailed_dialog_title_panel"></div>
<div id="presets_options_panel">
<span id="preset_options_label" i18n="presetsOptions"></span>
<select multiple="multiple" id="presets_options_select"></select>
</div>
<div class="presets_detailed_dialog_text" id="presets_detailed_dialog_text_description"></div>
<div class="presets_detailed_dialog_text" id="presets_detailed_dialog_text_cli"></div>
</div>
<div id = "presets_detailed_dialog_loading" class="data-loading"></div>
<div id = "presets_detailed_dialog_error"></div>
</div>
<div class="content_toolbar">
<div class="btn">
<div class="left-panel">
<a id="presets_cli_show" i18n="presetsShowCli" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
<a id="presets_cli_hide" i18n="presetsHideCli" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
<a id="presets_open_online" i18n="presetsViewOnline" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
<a id="presets_open_discussion" i18n="presetsOpenDiscussion" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
</div>
<a href="#" id="presets_detailed_dialog_applybtn" class="tool regular-button" i18n="presetsApply"></a>
<a href="#" id="presets_detailed_dialog_closebtn" class="tool regular-button" i18n="close"></a>
</div>
</div>
</div>

View file

@ -0,0 +1,223 @@
'use strict';
class PresetsDetailedDialog {
constructor(domDialog, pickedPresetList, onPresetPickedCallback) {
this._domDialog = domDialog;
this._pickedPresetList = pickedPresetList;
this._finalDialogYesNoSettings = {};
this._onPresetPickedCallback = onPresetPickedCallback;
this._openPromiseResolve = undefined;
}
load() {
return new Promise(resolve => {
this._domDialog.load("./tabs/presets/DetailedDialog/PresetsDetailedDialog.html", () => {
this._setupdialog();
resolve();
});
});
}
open(preset, presetsRepo) {
this._presetsRepo = presetsRepo;
this._preset = preset;
this._setLoadingState(true);
this._domDialog[0].showModal();
this._presetsRepo.loadPreset(this._preset)
.then(() => {
this._loadPresetUi();
this._setLoadingState(false);
this._setFinalYesNoDialogSettings();
})
.catch(err => {
console.error(err);
const msg = i18n.getMessage("presetsLoadError");
this._showError(msg);
});
return new Promise(resolve => this._openPromiseResolve = resolve);
}
_setFinalYesNoDialogSettings() {
this._finalDialogYesNoSettings = {
title: i18n.getMessage("presetsWarningDialogTitle"),
text: GUI.escapeHtml(this._preset.completeWarning),
buttonYesText: i18n.getMessage("presetsWarningDialogYesButton"),
buttonNoText: i18n.getMessage("presetsWarningDialogNoButton"),
buttonYesCallback: () => this._pickPresetFwVersionCheck(),
buttonNoCallback: null,
};
}
_getFinalCliText() {
const optionsToInclude = this._domOptionsSelect.multipleSelect("getSelects", "text");
return this._presetsRepo.removeUncheckedOptions(this._preset.originalPresetCliStrings, optionsToInclude);
}
_loadPresetUi() {
this._domDescription.text(this._preset.description?.join("\n"));
this._domGitHubLink.attr("href", this._presetsRepo.getPresetOnlineLink(this._preset));
if (this._preset.discussion) {
this._domDiscussionLink.removeClass(GUI.buttonDisabledClass);
this._domDiscussionLink.attr("href", this._preset.discussion);
} else{
this._domDiscussionLink.addClass(GUI.buttonDisabledClass);
}
this._titlePanel.empty();
const titlePanel = new PresetTitlePanel(this._titlePanel, this._preset, false, () => this._setLoadingState(false));
titlePanel.load();
this._loadOptionsSelect();
this._updateFinalCliText();
this._showCliText(false);
}
_updateFinalCliText() {
this._domCliText.text(this._getFinalCliText().join("\n"));
}
_setLoadingState(isLoading) {
this._domProperties.toggle(!isLoading);
this._domLoading.toggle(isLoading);
this._domError.toggle(false);
if (isLoading) {
this._domButtonApply.addClass(GUI.buttonDisabledClass);
} else {
this._domButtonApply.removeClass(GUI.buttonDisabledClass);
}
}
_showError(msg) {
this._domError.toggle(true);
this._domError.text(msg);
this._domProperties.toggle(false);
this._domLoading.toggle(false);
this._domButtonApply.addClass(GUI.buttonDisabledClass);
}
_readDom() {
this._domButtonApply = $('#presets_detailed_dialog_applybtn');
this._domButtonCancel = $('#presets_detailed_dialog_closebtn');
this._domLoading = $('#presets_detailed_dialog_loading');
this._domError = $('#presets_detailed_dialog_error');
this._domProperties = $('#presets_detailed_dialog_properties');
this._titlePanel = $('.preset_detailed_dialog_title_panel');
this._domDescription = $('#presets_detailed_dialog_text_description');
this._domCliText = $('#presets_detailed_dialog_text_cli');
this._domGitHubLink = this._domDialog.find('#presets_open_online');
this._domDiscussionLink = this._domDialog.find('#presets_open_discussion');
this._domOptionsSelect = $('#presets_options_select');
this._domOptionsSelectPanel = $('#presets_options_panel');
this._domButtonCliShow = $('#presets_cli_show');
this._domButtonCliHide = $('#presets_cli_hide');
}
_showCliText(value) {
this._domDescription.toggle(!value);
this._domCliText.toggle(value);
this._domButtonCliShow.toggle(!value);
this._domButtonCliHide.toggle(value);
}
_createOptionsSelect(options) {
options.forEach(option => {
let selectedString = "selected=\"selected\"";
if (!option.checked) {
selectedString = "";
}
this._domOptionsSelect.append(`<option value="${option.name}" ${selectedString}>${option.name}</option>`);
});
this._domOptionsSelect.multipleSelect({
placeholder: i18n.getMessage("dropDownAll"),
formatSelectAll () { return i18n.getMessage("dropDownSelectAll"); },
formatAllSelected() { return i18n.getMessage("dropDownAll"); },
onClick: () => this._optionsSelectionChanged(),
onCheckAll: () => this._optionsSelectionChanged(),
onUncheckAll: () => this._optionsSelectionChanged(),
});
}
_optionsSelectionChanged() {
this._updateFinalCliText();
}
_destroyOptionsSelect() {
this._domOptionsSelect.multipleSelect('destroy');
}
_loadOptionsSelect() {
const optionsVisible = 0 !== this._preset.options.length;
this._domOptionsSelect.empty();
this._domOptionsSelectPanel.toggle(optionsVisible);
if (optionsVisible) {
this._createOptionsSelect(this._preset.options);
}
this._domOptionsSelect.multipleSelect('refresh');
}
_setupdialog() {
i18n.localizePage();
this._readDom();
this._domButtonApply.on("click", () => this._onApplyButtonClicked());
this._domButtonCancel.on("click", () => this._onCancelButtonClicked(false));
this._domButtonCliShow.on("click", () => this._showCliText(true));
this._domButtonCliHide.on("click", () => this._showCliText(false));
}
_onApplyButtonClicked() {
if (!this._preset.completeWarning) {
this._pickPresetFwVersionCheck();
} else {
GUI.showYesNoDialog(this._finalDialogYesNoSettings);
}
}
_pickPreset() {
const cliStrings = this._getFinalCliText();
const pickedPreset = new PickedPreset(this._preset, cliStrings);
this._pickedPresetList.push(pickedPreset);
this._onPresetPickedCallback?.();
this._onCancelButtonClicked(true);
}
_pickPresetFwVersionCheck() {
let compatitable = false;
for (const fw of this._preset.firmware_version) {
if (FC.CONFIG.flightControllerVersion.startsWith(fw)) {
compatitable = true;
break;
}
}
if (compatitable) {
this._pickPreset();
} else {
const dialogSettings = {
title: i18n.getMessage("presetsWarningDialogTitle"),
text: i18n.getMessage("presetsWarningWrongVersionConfirmation", [this._preset.firmware_version, FC.CONFIG.flightControllerVersion]),
buttonYesText: i18n.getMessage("presetsWarningDialogYesButton"),
buttonNoText: i18n.getMessage("presetsWarningDialogNoButton"),
buttonYesCallback: () => this._pickPreset(),
buttonNoCallback: null,
};
GUI.showYesNoDialog(dialogSettings);
}
}
_onCancelButtonClicked(isPresetPicked) {
this._destroyOptionsSelect();
this._domDialog[0].close();
this._openPromiseResolve?.(isPresetPicked);
}
}

View file

@ -0,0 +1,10 @@
'use strict';
class PickedPreset
{
constructor(preset, presetCli)
{
this.preset = preset;
this.presetCli = presetCli;
}
}

View file

@ -0,0 +1,26 @@
'use strict';
class PresetsGithubRepo extends PresetsRepoIndexed {
constructor(urlRepo, branch) {
let correctUrlRepo = urlRepo.trim();
if (!correctUrlRepo.endsWith("/")) {
correctUrlRepo += "/";
}
let correctBranch = branch.trim();
if (correctBranch.startsWith("/")) {
correctBranch = correctBranch.slice(1);
}
if (correctBranch.endsWith("/")) {
correctBranch = correctBranch.slice(0, -1);
}
const urlRaw = `https://raw.githubusercontent.com${correctUrlRepo.slice("https://github.com".length)}${correctBranch}/`;
const urlViewOnline = `${correctUrlRepo}blob/${correctBranch}/`;
super(urlRaw, urlViewOnline);
}
}

View file

@ -0,0 +1,223 @@
'use strict';
class PresetsRepoIndexed {
constructor(urlRaw, urlViewOnline) {
this._urlRaw = urlRaw;
this._urlViewOnline = urlViewOnline;
this._index = null;
}
get index() {
return this._index;
}
loadIndex() {
return fetch(this._urlRaw + "index.json")
.then(res => res.json())
.then(out => this._index = out);
}
removeUncheckedOptions(strings, checkedOptions) {
let resultStrings = [];
let isCurrentOptionExcluded = false;
const lowerCasedCheckedOptions = checkedOptions.map(optionName => optionName.toLowerCase());
strings.forEach(str => {
if (this._isLineAttribute(str)) {
const line = this._removeAttributeDirective(str);
if (this._isOptionBegin(line)) {
const optionNameLowCase = this._getOptionName(line).toLowerCase();
if (!lowerCasedCheckedOptions.includes(optionNameLowCase)) {
isCurrentOptionExcluded = true;
}
} else if (this._isOptionEnd(line)) {
isCurrentOptionExcluded = false;
}
} else if (!isCurrentOptionExcluded) {
resultStrings.push(str);
}
});
resultStrings = this._removeExcessiveEmptyLines(resultStrings);
return resultStrings;
}
_removeExcessiveEmptyLines(strings) {
// removes empty lines if there are two or more in a row leaving just one empty line
const result = [];
let lastStringEmpty = false;
strings.forEach(str => {
if ("" !== str || !lastStringEmpty) {
result.push(str);
}
if ("" === str) {
lastStringEmpty = true;
} else {
lastStringEmpty = false;
}
});
return result;
}
_isLineAttribute(line) {
return line.trim().startsWith(PresetsRepoIndexed._sCliAttributeDirective);
}
_isOptionBegin(line) {
const lowCaseLine = line.toLowerCase();
return lowCaseLine.startsWith(this._index.settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE);
}
_isOptionEnd(line) {
const lowCaseLine = line.toLowerCase();
return lowCaseLine.startsWith(this._index.settings.OptionsDirectives.END_OPTION_DIRECTIVE);
}
_getOptionName(line) {
const directiveRemoved = line.slice(this._index.settings.OptionsDirectives.BEGIN_OPTION_DIRECTIVE.length).trim();
const regExpRemoveChecked = new RegExp(this._escapeRegex(this._index.settings.OptionsDirectives.OPTION_CHECKED +":"), 'gi');
const regExpRemoveUnchecked = new RegExp(this._escapeRegex(this._index.settings.OptionsDirectives.OPTION_UNCHECKED +":"), 'gi');
let optionName = directiveRemoved.replace(regExpRemoveChecked, "");
optionName = optionName.replace(regExpRemoveUnchecked, "").trim();
return optionName;
}
_escapeRegex(string) {
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
_removeAttributeDirective(line) {
return line.trim().slice(PresetsRepoIndexed._sCliAttributeDirective.length).trim();
}
getPresetOnlineLink(preset) {
return this._urlViewOnline + preset.fullPath;
}
_parceInclude(strings, includeRowIndexes, promises)
{
for (let i = 0; i < strings.length; i++) {
const match = PresetsRepoIndexed._sRegExpInclude.exec(strings[i].toLowerCase());
if (match !== null) {
includeRowIndexes.push(i);
const filePath = this._urlRaw + match.groups.filePath;
const promise = this._loadPresetText(filePath);
promises.push(promise);
}
}
}
_executeIncludeOnce(strings) {
const includeRowIndexes = []; // row indexes with "#include" statements
const promises = []; // promises to load included files
this._parceInclude(strings, includeRowIndexes, promises);
return Promise.all(promises)
.then(includedTexts => {
for (let i = 0; i < includedTexts.length; i++) {
strings[includeRowIndexes[i]] = includedTexts[i];
}
const text = strings.join('\n');
return text.split("\n").map(str => str.trim());
});
}
_executeIncludeNested(strings) {
const isIncludeFound = this._isIncludeFound(strings);
if (isIncludeFound) {
return this._executeIncludeOnce(strings)
.then(resultStrings => this._executeIncludeNested(resultStrings));
} else {
return new Promise.resolve(strings);
}
}
_isIncludeFound(strings) {
for (const str of strings) {
const match = PresetsRepoIndexed._sRegExpInclude.exec(str.toLowerCase());
if (match !== null) {
return true;
}
}
return false;
}
loadPreset(preset) {
const promiseMainText = this._loadPresetText(this._urlRaw + preset.fullPath);
return promiseMainText
.then(text => {
let strings = text.split("\n");
strings = strings.map(str => str.trim());
return strings;
})
.then(strings => this._executeIncludeNested(strings))
.then(strings => {
preset.originalPresetCliStrings = strings;
return this._loadPresetWarning(preset);
});
}
_loadPresetWarning(preset) {
let completeWarning = "";
if (preset.warning) {
completeWarning += (completeWarning?"\n":"") + preset.warning;
}
if (preset.disclaimer) {
completeWarning += (completeWarning?"\n":"") + preset.disclaimer;
}
const allFiles = [].concat(...[preset.include_warning, preset.include_disclaimer].filter(Array.isArray));
return this._loadFilesInOneText(allFiles)
.then(text => {
completeWarning += (completeWarning?"\n":"") + text;
preset.completeWarning = completeWarning;
});
}
_loadFilesInOneText(fileUrls) {
const loadPromises = [];
fileUrls?.forEach(url => {
const filePath = this._urlRaw + url;
loadPromises.push(this._loadPresetText(filePath));
});
return Promise.all(loadPromises)
.then(texts => {
return texts.join('\n');
});
}
_loadPresetText(fullUrl) {
return new Promise((resolve, reject) => {
fetch(fullUrl)
.then(res => res.text())
.then(text => resolve(text))
.catch(err => {
console.error(err);
reject(err);
});
});
}
}
PresetsRepoIndexed._sCliCommentDirective = "#";
PresetsRepoIndexed._sCliAttributeDirective = "#$";
// Reg exp extracts file/path.txt from # include: file/path.txt
PresetsRepoIndexed._sRegExpInclude = /^#\$[ ]+?include:[ ]+?(?<filePath>\S+$)/;

View file

@ -0,0 +1,16 @@
'use strict';
class PresetsWebsiteRepo extends PresetsRepoIndexed {
constructor(url) {
let correctUrl = url.trim();
if (!correctUrl.endsWith("/")) {
correctUrl += "/";
}
const urlRaw = correctUrl;
const urlViewOnline = correctUrl;
super(urlRaw, urlViewOnline);
}
}

View file

@ -0,0 +1,14 @@
'use strict';
class PresetSource {
constructor(name, url, gitHubBranch = "") {
this.name = name;
this.url = url;
this.gitHubBranch = gitHubBranch;
this.official = false;
}
static isUrlGithubRepo(url) {
return url.trim().toLowerCase().startsWith("https://github.com/");
}
}

View file

@ -0,0 +1,70 @@
.presets_source_panel {
background-color: var(--boxBackground);
border: 1px solid var(--subtleAccent);
padding: 1.5ex;
box-shadow: 2px 2px 5px rgba(92, 92, 92, 0.25);
border-radius: 4px;
margin-bottom: 6px;
}
.presets_source_panel_not_selected {
cursor: pointer;
}
.presets_source_panel_selected {
cursor: unset;
}
.presets_source_panel_not_selected:hover {
background-color: var(--subtleAccent);
box-shadow: 2px 2px 5px rgba(92, 92, 92, 0.5);
}
.presets_source_panel_editing_table {
display: table;
width: 100%;
border-spacing: 6px;
}
.presets_source_panel_editing_row {
display: table-row;
}
.presets_source_panel_editing_field_label {
display: table-cell;
white-space: pre;
padding-right: 10px;
min-width: 100px;
}
.presets_source_panel_editing_field_edit {
display: table-cell;
width: 100%;
padding-right: 10px;
}
.presets_source_panel_editing_row .standard_input {
width: 100%;
margin-right: 12px;
}
.presets_source_panel_no_editing_name {
font-size: 130%;
display: inline-block;
vertical-align: middle;
}
.presets_source_panel_no_editing_selected {
background-image: url(../../../images/icons/cf_icon_check_orange.svg);
width: 30px;
height: 30px;
display: inline-block;
margin-top: 8px;
margin-bottom: 8px;
margin-right: 5px;
vertical-align: middle;
}
.presets_source_panel_reset, .presets_source_panel_save, .presets_source_panel_delete {
float: right;
}

View file

@ -0,0 +1,29 @@
<div class="presets_source_panel">
<div class="presets_source_panel_no_editing">
<div class="presets_source_panel_no_editing_selected"></div>
<div class="presets_source_panel_no_editing_name"></div>
</div>
<div class="presets_source_panel_editing">
<div class="presets_source_panel_editing_table">
<div class="presets_source_panel_editing_row">
<div class="presets_source_panel_editing_field_label">Name</div>
<div class="presets_source_panel_editing_field_edit"><input type="text" class="presets_source_panel_editing_name_field standard_input"/></div>
</div>
<div class="presets_source_panel_editing_row">
<div class="presets_source_panel_editing_field_label">Url</div>
<div class="presets_source_panel_editing_field_edit"><input type="text" class="presets_source_panel_editing_url_field standard_input"/></div>
</div>
<div class="presets_source_panel_editing_row presets_source_panel_editing_github_branch">
<div class="presets_source_panel_editing_field_label">GitHub branch</div>
<div class="presets_source_panel_editing_field_edit"><input type="text" class="presets_source_panel_editing_branch_field standard_input"/></div>
</div>
</div>
<div class="btn">
<div class="presets_source_panel_no_editing_selected"></div>
<a href="#" class="tool regular-button presets_source_panel_activate" i18n="presetsSourcesDialogMakeSourceActive"></a>
<a href="#" class="tool regular-button presets_source_panel_save" i18n="presetsSourcesDialogSaveSource"></a>
<a href="#" class="tool regular-button presets_source_panel_reset" i18n="presetsSourcesDialogResetSource"></a>
<a href="#" class="tool regular-button presets_source_panel_delete" i18n="presetsSourcesDialogDeleteSource"></a>
</div>
</div>
</div>

View file

@ -0,0 +1,199 @@
'use strict';
class SourcePanel {
constructor(parentDiv, presetSource) {
this._parentDiv = parentDiv;
this._presetSource = presetSource;
this._active = false;
}
get presetSource() {
return this._presetSource;
}
load() {
SourcePanel.s_panelCounter++;
this._domId = `source_panel_${SourcePanel.s_panelCounter}`;
this._parentDiv.append(`<div id="${this._domId}"></div>`);
this._domWrapperDiv = $(`#${this._domId}`);
this._domWrapperDiv.toggle(false);
return new Promise(resolve => {
this._domWrapperDiv.load("./tabs/presets/SourcesDialog/SourcePanel.html",
() => {
this._setupHtml();
resolve();
});
});
}
setOnSelectedCallback(onSelectedCallback) {
// callback with this (SourcePanel) argument
// so that consumer knew which panel was clicked on
this._onSelectedCallback = onSelectedCallback;
}
setOnDeleteCallback(onDeletedCallback) {
// callback with this (SourcePanel) argument
// so that consumer knew which panel was clicked on
this._onDeletedCallback = onDeletedCallback;
}
setOnActivateCallback(onActivateCallback) {
// callback with this (SourcePanel) argument
// so that consumer knew which panel was clicked on
this._onActivateCallback = onActivateCallback;
}
setOnSaveCallback(onSaveCallback) {
// callback with this (SourcePanel) argument
// so that consumer knew which panel was clicked on
this._onSaveCallback = onSaveCallback;
}
setSelected(isSelected) {
this._setUiSelected(isSelected);
}
get active() {
return this._active;
}
setActive(isActive) {
this._active = isActive;
this._domDivSelectedIndicator.toggle(this._active);
this._domButtonActivate.toggle(!isActive);
}
_setUiOfficial() {
if (this.presetSource.official){
this._domButtonSave.toggle(false);
this._domButtonReset.toggle(false);
this._domButtonDelete.toggle(false);
this._domEditName.prop("disabled", true);
this._domEditUrl.prop("disabled", true);
this._domEditGitHubBranch.prop("disabled", true);
}
}
_setUiSelected(isSelected) {
if (this._selected !== isSelected) {
this._domDivNoEditing.toggle(!isSelected);
this._domDivEditing.toggle(isSelected);
this._onResetButtonClick();
this._updateNoEditingName();
this._domDivInnerPanel.toggleClass("presets_source_panel_not_selected", !isSelected);
this._domDivInnerPanel.toggleClass("presets_source_panel_selected", isSelected);
if (isSelected) {
this._domDivInnerPanel.off("click");
} else {
this._domDivInnerPanel.on("click", () => this._onPanelSelected());
}
this._selected = isSelected;
}
}
_updateNoEditingName() {
this._domDivNoEditingName.text(this._presetSource.name);
}
_setupHtml() {
this._readDom();
this._setupActions();
this.setSelected(false);
this._setIsSaved(true);
this._checkIfGithub();
this.setActive(this._active);
this._setUiOfficial();
i18n.localizePage();
this._domWrapperDiv.toggle(true);
}
_setupActions() {
this._domButtonSave.on("click", () => this._onSaveButtonClick());
this._domButtonReset.on("click", () => this._onResetButtonClick());
this._domButtonDelete.on("click", () => this._onDeleteButtonClick());
this._domButtonActivate.on("click", () => this._onActivateButtonClick());
this._domEditName.on("input", () => this._onInputChange());
this._domEditUrl.on("input", () => this._onInputChange());
this._domEditGitHubBranch.on("input", () => this._onInputChange());
}
_onPanelSelected() {
this._setUiSelected(true);
this._onSelectedCallback?.(this);
}
_checkIfGithub() {
const isGithubUrl = PresetSource.isUrlGithubRepo(this._domEditUrl.val());
this._domDivGithubBranch.toggle(isGithubUrl);
}
_onInputChange() {
this._checkIfGithub();
this._setIsSaved(false);
}
_onSaveButtonClick() {
this._presetSource.name = this._domEditName.val();
this._presetSource.url = this._domEditUrl.val();
this._presetSource.gitHubBranch = this._domEditGitHubBranch.val();
this._setIsSaved(true);
this._onSaveCallback?.(this);
}
_onResetButtonClick() {
this._domEditName.val(this._presetSource.name);
this._domEditUrl.val(this._presetSource.url);
this._domEditGitHubBranch.val(this._presetSource.gitHubBranch);
this._checkIfGithub();
this._setIsSaved(true);
}
_onDeleteButtonClick() {
this._domWrapperDiv.remove();
this._onDeletedCallback?.(this);
}
_onActivateButtonClick() {
this.setActive(true);
this._onActivateCallback?.(this);
}
_setIsSaved(isSaved) {
if (isSaved) {
this._domButtonSave.addClass(GUI.buttonDisabledClass);
this._domButtonReset.addClass(GUI.buttonDisabledClass);
} else {
this._domButtonSave.removeClass(GUI.buttonDisabledClass);
this._domButtonReset.removeClass(GUI.buttonDisabledClass);
}
}
_readDom() {
this._domDivInnerPanel = this._domWrapperDiv.find(".presets_source_panel");
this._domDivNoEditing = this._domWrapperDiv.find(".presets_source_panel_no_editing");
this._domDivEditing = this._domWrapperDiv.find(".presets_source_panel_editing");
this._domEditName = this._domWrapperDiv.find(".presets_source_panel_editing_name_field");
this._domEditUrl = this._domWrapperDiv.find(".presets_source_panel_editing_url_field");
this._domEditGitHubBranch = this._domWrapperDiv.find(".presets_source_panel_editing_branch_field");
this._domButtonSave = this._domWrapperDiv.find(".presets_source_panel_save");
this._domButtonReset = this._domWrapperDiv.find(".presets_source_panel_reset");
this._domButtonActivate = this._domWrapperDiv.find(".presets_source_panel_activate");
this._domButtonDelete = this._domWrapperDiv.find(".presets_source_panel_delete");
this._domDivGithubBranch = this._domWrapperDiv.find(".presets_source_panel_editing_github_branch");
this._domDivNoEditingName = this._domWrapperDiv.find(".presets_source_panel_no_editing_name");
this._domDivSelectedIndicator = this._domWrapperDiv.find(".presets_source_panel_no_editing_selected");
}
}
SourcePanel.s_panelCounter = 0;

View file

@ -0,0 +1,27 @@
.presets_sources_dialog_title_panel {
padding-bottom: .5ex;
border-bottom: 1px solid var(--accent);
margin-bottom: 2ex;
font-size: 1.5em;
font-weight: bold;
}
.presets_sources_dialog_scrollable {
height: 430px;
overflow-y: auto;
overflow-x: hidden;
}
@media all and (max-width: 575px) {
#presets_sources_dialog_content_wrapper .content_toolbar {
position: fixed;
}
.presets_sources_dialog_scrollable {
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 51px;
height: unset;
}
}

View file

@ -0,0 +1,16 @@
<div id="presets_sources_dialog_content_wrapper">
<div id="presets_sources_dialog_content">
<div class="presets_sources_dialog_title_panel" i18n="presetsSourcesDialogTitle"></div>
<div class="presets_sources_dialog_scrollable">
<div class="note" i18n="presets_sources_dialog_warning"></div>
<div class="presets_sources_dialog_sources"></div>
</div>
</div>
<div class="content_toolbar">
<div class="btn">
<a id="presets_sources_dialog_close" i18n="OK" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
<a id="presets_sources_dialog_add_new" i18n="presetsSourcesDialogAddNew" class="tool regular-button" target="_blank" rel="noopener noreferrer"></a>
</div>
</div>
</div>

View file

@ -0,0 +1,193 @@
'use strict';
class PresetsSourcesDialog {
constructor(domDialog) {
this._domDialog = domDialog;
this._sourceSelectedPromiseResolve = null;
this._sourcesPanels = [];
this._sources = [];
this._activeSourceIndex = 0;
}
load() {
return new Promise(resolve => {
this._domDialog.load("./tabs/presets/SourcesDialog/SourcesDialog.html",
() => {
this._setupDialog();
this._initializeSources();
resolve();
});
});
}
show() {
this._domDialog[0].showModal();
return new Promise(resolve => this._sourceSelectedPromiseResolve = resolve);
}
getActivePresetSource() {
return this._sources[this._activeSourceIndex];
}
get isOfficialActive() {
return this._activeSourceIndex === 0;
}
_initializeSources() {
this._sources = this._readSourcesFromStorage();
this._activeSourceIndex = this._readActiveSourceIndexFromStorage(this._sources.length);
for (let i = 0; i < this._sources.length; i++) {
const isActive = this._activeSourceIndex === i;
this._addNewSourcePanel(this._sources[i], isActive, false);
}
}
_readSourcesFromStorage() {
const officialSource = this._createOfficialSource();
const officialSourceSecondary = this._createSecondaryOfficialSource();
const obj = ConfigStorage.get('PresetSources');
let sources = obj.PresetSources;
if (sources && sources.length > 0) {
sources[0] = officialSource;
} else {
sources = [officialSource, officialSourceSecondary];
}
if (sources.length === 1) {
sources.push(officialSourceSecondary);
}
return sources;
}
_readActiveSourceIndexFromStorage(sourcesCount) {
const obj = ConfigStorage.get('PresetSourcesActiveIndex');
const index = Number(obj.PresetSourcesActiveIndex);
let result = 0;
if (index && Number.isInteger(index) && index < sourcesCount) {
result = index;
}
return result;
}
_createOfficialSource() {
const officialSource = new PresetSource("Betaflight Official Presets", "https://api.betaflight.com/firmware-presets/", "");
officialSource.official = true;
return officialSource;
}
_createSecondaryOfficialSource() {
const officialSource = new PresetSource("Betaflight Presets - GitHub BACKUP", "https://github.com/betaflight/firmware-presets", "backup");
officialSource.official = false;
return officialSource;
}
_setupDialog() {
this._readDom();
this._setupEvents();
this._domButtonAddNew.on("click", () => this._onAddNewSourceButtonClick());
i18n.localizePage();
}
_onAddNewSourceButtonClick() {
const presetSource = new PresetSource(i18n.getMessage("presetsSourcesDialogDefaultSourceName"), "", "");
this._addNewSourcePanel(presetSource).then(() => {
this._scrollDown();
this._updateSourcesFromPanels();
});
}
_scrollDown() {
this._domDivSourcesPanel.stop();
this._domDivSourcesPanel.animate({scrollTop: this._domDivSourcesPanel.prop('scrollHeight') + "px"});
}
_addNewSourcePanel(presetSource, isActive = false, isSelected = true) {
const sourcePanel = new SourcePanel(this._domDivSourcesPanel, presetSource);
this._sourcesPanels.push(sourcePanel);
return sourcePanel.load().then(() => {
sourcePanel.setOnSelectedCallback(selectedPanel => this._onSourcePanelSelected(selectedPanel));
sourcePanel.setOnDeleteCallback(selectedPanel => this._onSourcePanelDeleted(selectedPanel));
sourcePanel.setOnActivateCallback(selectedPanel => this._onSourcePanelActivated(selectedPanel));
sourcePanel.setOnSaveCallback(() => this._onSourcePanelSaved());
sourcePanel.setActive(isActive);
if (isSelected) {
this._onSourcePanelSelected(sourcePanel);
}
});
}
_setupEvents() {
this._domButtonClose.on("click", () => this._onCloseButtonClick());
}
_onCloseButtonClick() {
this._sourceSelectedPromiseResolve?.();
this._domDialog[0].close();
}
_readPanels() {
this._sources = [];
this._activeSourceIndex = 0;
for(let i = 0; i < this._sourcesPanels.length; i++) {
this._sources.push(this._sourcesPanels[i].presetSource);
if (this._sourcesPanels[i].active) {
this._activeSourceIndex = i;
}
}
}
_saveSources() {
ConfigStorage.set({'PresetSources': this._sources});
ConfigStorage.set({'PresetSourcesActiveIndex': this._activeSourceIndex});
}
_updateSourcesFromPanels() {
this._readPanels();
this._saveSources();
}
_onSourcePanelSaved() {
this._updateSourcesFromPanels();
}
_onSourcePanelSelected(selectedPanel) {
for (const panel of this._sourcesPanels) {
if (panel !== selectedPanel) {
panel.setSelected(false);
} else {
panel.setSelected(true);
}
}
}
_onSourcePanelDeleted(selectedPanel) {
this._sourcesPanels = this._sourcesPanels.filter(panel => panel !== selectedPanel);
if (selectedPanel.active) {
this._sourcesPanels[0].setActive(true);
}
this._updateSourcesFromPanels();
}
_onSourcePanelActivated(selectedPanel) {
for (const panel of this._sourcesPanels) {
if (panel !== selectedPanel) {
panel.setActive(false);
} else {
panel.setActive(true);
}
}
this._updateSourcesFromPanels();
}
_readDom() {
this._domButtonAddNew = $("#presets_sources_dialog_add_new");
this._domButtonClose = $("#presets_sources_dialog_close");
this._domDivSourcesPanel = $(".presets_sources_dialog_sources");
}
}

View file

@ -0,0 +1,82 @@
.preset_title_panel {
color: var(--defaultText);
}
.preset_title_panel_border {
background-color: var(--boxBackground);
border: 1px solid var(--subtleAccent);
cursor: pointer;
padding: 1.5ex;
box-shadow: 2px 2px 5px rgba(92, 92, 92, 0.25);
border-radius: 4px;
}
.preset_title_panel_title {
font-size: 1.5em;
font-weight: bold;
display: block;
margin-bottom: 1ex;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.preset_title_panel_category {
color: var(--mutedText);
font-weight: bold;
}
.preset_title_panel_official {
padding-left: 3px;
padding-right: 3px;
padding-top: 3px;
padding-bottom: 3px;
display: inline-block;
color: white;
font-weight: 700;
border-radius: 4px;
}
.preset_title_panel_status_official {
background-color: green;
}
.preset_title_panel_status_community {
background-color: var(--accent);
}
.preset_title_panel_status_experimental {
background-color: red;
}
.preset_title_panel_versions_text {
font-weight: bold;
}
.preset_title_panel_keywords_text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.presets_title_panel_table {
table-layout: fixed;
width: 100%;
border-collapse: collapse;
}
.presets_title_panel_table td{
overflow: hidden;
height: 24px;
width: auto;
text-overflow: ellipsis;
white-space: nowrap;
}
.presets_title_panel_table td:nth-child(1){
width: 100px;
}
.preset_title_panel_label {
color: var(--quietHeader);
}

View file

@ -0,0 +1,92 @@
'use strict';
class PresetTitlePanel
{
constructor(parentDiv, preset, clickable, onLoadedCallback)
{
PresetTitlePanel.s_panelCounter ++;
this._parentDiv = parentDiv;
this._onLoadedCallback = onLoadedCallback;
this._domId = `preset_title_panel_${PresetTitlePanel.s_panelCounter}`;
this._preset = preset;
this._parentDiv.append(`<div class="${this._domId}"></div>`);
this._domWrapperDiv = $(`.${this._domId}`);
this._domWrapperDiv.toggle(false);
if (clickable) {
this._domWrapperDiv.addClass("preset_title_panel_border");
// setting up hover effect here, because if setup in SCC it stops working after background animation like - this._domWrapperDiv.animate({ backgroundColor....
this._domWrapperDiv.on("mouseenter", () => this._domWrapperDiv.css({"background-color": "var(--subtleAccent)"}));
this._domWrapperDiv.on("mouseleave", () => this._domWrapperDiv.css({"background-color": "var(--boxBackground)"}));
}
}
load() {
this._domWrapperDiv.load("./tabs/presets/TitlePanel/PresetTitlePanelBody.html", () => this._setupHtml());
}
subscribeClick(presetsDetailedDialog, presetsRepo)
{
this._domWrapperDiv.on("click", () => {
presetsDetailedDialog.open(this._preset, presetsRepo).then(isPresetPicked => {
if (isPresetPicked) {
const color = this._domWrapperDiv.css( "background-color" );
this._domWrapperDiv.css('background-color', 'green');
this._domWrapperDiv.animate({ backgroundColor: color }, 2000);
this.setPicked(true);
}
});
});
}
setPicked(isPicked) {
this._preset.isPicked = isPicked;
if (isPicked) {
this._domWrapperDiv.css({"border": "2px solid green"});
} else {
this._domWrapperDiv.css({"border": "1px solid var(--subtleAccent)"});
}
}
_setupHtml()
{
this._readDom();
this._domCategory.text(this._preset.category);
this._domTitle.text(this._preset.title);
this._domTitle.prop("title", this._preset.title);
this._domAuthor.text(this._preset.author);
this._domVersions.text(this._preset.firmware_version?.join("; "));
this._domKeywords.text(this._preset.keywords?.join("; "));
this._domKeywords.prop("title", this._preset.keywords?.join("; "));
this._domStatusOfficial.toggle(this._preset.status === "OFFICIAL");
this._domStatusCommunity.toggle(this._preset.status === "COMMUNITY");
this._domStatusExperimental.toggle(this._preset.status === "EXPERIMENTAL");
this.setPicked(this._preset.isPicked);
i18n.localizePage();
this._domWrapperDiv.toggle(true);
this._onLoadedCallback?.();
}
_readDom()
{
this._domTitle = this._domWrapperDiv.find('.preset_title_panel_title');
this._domCategory = this._domWrapperDiv.find('.preset_title_panel_category');
this._domAuthor = this._domWrapperDiv.find('.preset_title_panel_author_text');
this._domKeywords = this._domWrapperDiv.find('.preset_title_panel_keywords_text');
this._domVersions = this._domWrapperDiv.find('.preset_title_panel_versions_text');
this._domStatusOfficial = this._domWrapperDiv.find('.preset_title_panel_status_official');
this._domStatusCommunity = this._domWrapperDiv.find('.preset_title_panel_status_community');
this._domStatusExperimental = this._domWrapperDiv.find('.preset_title_panel_status_experimental');
}
remove()
{
this._domWrapperDiv.remove();
}
}
PresetTitlePanel.s_panelCounter = 0;

View file

@ -0,0 +1,45 @@
<div class="preset_title_panel">
<div>
<span class="preset_title_panel_title"></span>
</div>
<div>
<table class="presets_title_panel_table" role="presentation">
<tbody>
<tr>
<td>
<span class="preset_title_panel_official preset_title_panel_status_experimental" i18n="presetsExperimental"></span>
<span class="preset_title_panel_official preset_title_panel_status_community" i18n="presetsCommunity"></span>
<span class="preset_title_panel_official preset_title_panel_status_official" i18n="presetsOfficial"></span>
</td>
<td>
<span class="preset_title_panel_category"></span>
</td>
</tr>
<tr>
<td>
<span class="preset_title_panel_label preset_title_panel_author_label" i18n="presetsAuthor"></span>
</td>
<td>
<span class="preset_title_panel_author_text"></span>
</td>
</tr>
<tr>
<td>
<span class="preset_title_panel_label preset_title_panel_versions_label" i18n="presetsVersions"></span>
</td>
<td>
<span class="preset_title_panel_versions_text"></span>
</td>
</tr>
<tr>
<td>
<span class="preset_title_panel_label preset_title_panel_keywords_label" i18n="presetsKeywords"></span>
</td>
<td>
<span class="preset_title_panel_keywords_text"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View file

@ -0,0 +1,426 @@
.tab-presets {
height: 100%;
}
.tab-presets .content_wrapper {
height: calc(100% - 30px - 3ex);
overflow-y: scroll;
overflow-x: hidden;
}
.tab-presets p {
padding: 0;
border: 0 dotted var(--subtleAccent);
}
.tab-presets .presets_warnings {
padding-left : 20px;
padding-right : 20px;
}
#presets_content_wrapper {
padding: 0px;
position: relative;
}
.presets_top_bar_pannel {
margin-left: auto;
}
.presets_title_text {
display: inline-block;
}
.presets_top_bar_button_pannel {
display: inline;
}
.presets_warning_backup {
display: grid;
grid-template-columns: 1fr fit-content(300px);
}
.presets_warning_backup_text {
padding-right: 24px;
}
.presets_warning_backup_button_hide {
margin-top: 0px;
margin-bottom: 0px;
margin-right: 0px;
line-height: 17px;
font-size: 10px;
height: 17px;
}
.tab-presets .top_panel_spacer {
width: 0px;
display: inline;
border: 1px var(--subtleAccent);
border-style: none none none solid;
height: 60%;
margin-right: 10px;
float: right;
}
.tab-presets .tab_title .presets_top_bar_button_pannel .regular-button{
margin-bottom: 0px;
margin-top: 0px;
line-height : 17px;
font-size : 10px;
border-radius: 3px;
}
.presetsWikiButton {
margin-right: 0px;
}
.tab-presets .tab_title
{
padding: 20px 20px 0px 20px;
}
#presets_list {
padding: 0px 0px 20px 0px;
height: calc(100% - 180px);
display: grid;
grid-gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
}
#preset_list_wrapper {
padding-left: 20px;
padding-right: 20px;
}
#presets_global_loading {
padding: 0px 20px 20px 20px;
display: none;
width: 50%;
height: 50%;
margin: auto;
}
#presets_global_loading_error {
padding: 0px 20px 20px 20px;
display: none;
}
#presets_main_content {
display: none;
}
#presets_list:last-child::after {
visibility: hidden;
display: block;
font-size: 0;
content: "&nbsp;";
clear: both;
height: 0;
}
.presets_filter_select {
width: 100%;
background-color: var(--boxBackground);
color: var(--defaultText);
}
.presets_search_settings {
position: sticky;
top: 0px;
background-color: var(--boxBackground);
}
.tab-presets .ms-choice,.ms-drop {
background-color: var(--boxBackground)!important;
color: var(--defaultText)!important;
}
.presets_text_input {
border: 1px solid var(--subtleAccent);
border-radius: 3px;
background-color: var(--boxBackground);
color: var(--defaultText);
}
#presets_filter_text {
height: 26px;
flex: 1;
padding-left: 5px;
}
#presets_search_hint {
float: left;
width: 28px;
height: 28px;
margin-right: 12px;
background-repeat: no-repeat;
background-image: url(../../images/icons/cf_icon_search_orange.svg);
}
#presets_search_bar_wrapper {
display: flex;
padding: 2ex 20px 2ex 20px;
}
#presets_cli {
width: 100%;
}
#presets_cli_background {
border: 1px solid var(--subtleAccent);
background-color: rgba(64, 64, 64, 1);
margin-top: 0;
height: 300px;
border-radius: 5px;
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.80);
}
.tab-presets .window {
height: 100%;
width: 100%;
padding: 5px;
overflow-y: auto;
overflow-x: hidden;
font-family: monospace;
color: white;
box-sizing: border-box;
-webkit-user-select: text;
user-select: text;
float: left;
}
.presets_fitler_table_header
{
background: var(--quietHeader);
color: var(--quietText);
border-right: 1px solid var(--subtleAccent);
font-weight: normal;
text-align: left;
padding: 4px 4px 4px 6px;
}
.presets_fitler_table_value {
background-color: var(--alternativeBackground);
width: 100%;
}
.tab-presets textarea[name='commands'] {
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
margin-top: 6px;
padding: 4px;
padding-left: 8px;
padding-right: 8px;
color: white;
border: 1px solid var(--subtleAccent);
background-color: rgba(64, 64, 64, 1);
resize: none;
}
.tab-presets .presets_cli_errors_dialog_warning {
font-size: 1.2em;
margin-bottom: 8px;
}
.tab-presets #content-watermark {
z-index: 0;
}
#presets_cli_errors_dialog .regular-button {
margin-bottom: 0px;
margin-left: 12px;
margin-right: 0px;
float: right;
}
.tab-presets .window .wrapper {
white-space: pre-wrap;
user-select: text;
}
.tab-presets .window .error_message {
color: red;
font-weight: bold;
}
.tab-presets .save {
color: white;
}
#presets_detailed_dialog {
width : 600px;
height : 520px;
padding: 12px;
}
#presets_sources_dialog {
width : 600px;
height : 520px;
padding: 12px;
}
#presets_cli_errors_dialog {
width : 600px;
padding: 24px;
}
#presets_apply_progress_dialog {
width: 300px;
padding: 24px;
}
.tab-presets .presets_apply_progress_dialog_progress_bar {
width: 100%;
height: 20px;
margin-top: 12px;
}
.tab-presets .ms-drop {
z-index: 2001;
}
/* hack-fix for multiple-select with small font size. Originally checkboxes are not ligned with the text options when the font size is smaller than 0.7em */
.tab-presets
.ms-drop input[type="radio"], .ms-drop input[type="checkbox"] {
margin-top: 0.1rem !important;
}
.presets_filter_table_wrapper {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
padding: 0px 20px 0px 20px;
}
.presets_filter_table_wrapper .freelabel {
display: inline-block;
padding: 4px 0 4px 0;
}
.tab-presets .cf_doc_version_bt {
margin-top: 20px;
}
#presets_list_no_found, #presets_list_too_many_found {
font-size: 1.5em;
}
@media only screen and (max-width: 1055px) , only screen and (max-device-width: 1055px) {
.tab-presets .content_wrapper {
height: calc(100% - 87px);
}
.tab-presets .content_toolbar {
margin-top: 5px;
}
.presets_search_settings {
position: static;
top: unset;
}
}
.presets_secondary_top_bar_panel_buttons {
padding-left: 8px;
padding-right: 8px;
margin-bottom: 8px;
}
.presets_secondary_top_bar_panel_buttons .regular-button {
margin-top: 0px;
margin-bottom: 0px;
line-height: 17px;
font-size: 10px;
border-radius: 3px;
}
@media all and (max-width: 575px) {
.tab-presets .content_wrapper {
height: calc(100% - 51px);
}
.tab-presets .tab_title
{
padding: 20px 10px 10px 10px;
}
.tab-presets .backdrop {
background-size: 100%;
}
#presets_list {
grid-template-columns: 100%;
}
#preset_list_wrapper, .presets_search_settings {
padding-left: 8px;
padding-right: 8px;
}
#presets_search_bar_wrapper {
padding-left: 0px;
padding-right: 0px;
padding-top: 1ex;
}
.presets_filter_row {
display: table-row;
}
.presets_filter_table_wrapper {
display: table;
border-spacing: 6px;
margin-right: -6px;
margin-left: -6px;
padding-left: 0px;
padding-right: 0px;
}
.presets_fitler_table_header {
display: table-cell;
background-color: unset;
border-right: unset;
}
.presets_fitler_table_value {
display: table-cell;
}
.tab-presets .presets_warnings {
padding-left: 8px;
padding-right: 8px;
}
#presets_cli_errors_dialog {
padding: 12px;
}
#presets_apply_progress_dialog {
padding: 12px;
}
#presets_cli_errors_dialog .btn {
position: fixed;
right: 12px;
bottom: 12px;
}
#presets_cli {
height: calc(100% - 121px);
}
#presets_cli_background {
height: 100%;
}
.presets_warning_backup {
display: block;
grid-template-columns: 1fr fit-content(300px);
}
.presets_warning_backup_text {
padding-right: 24px;
margin-bottom: 6px;
}
}

View file

@ -0,0 +1,116 @@
<div class="tab-presets toolbar_fixed_bottom">
<div class="content_wrapper" id="presets_content_wrapper">
<div class="tab_title">
<div i18n="tabPresets" class="presets_title_text"></div>
<div class="presets_top_bar_button_pannel">
<a i18n="presetsWiki" class="presetsWikiButton tool regular-button right" href="https://github.com/betaflight/betaflight/wiki/Presets-in-BF-4.3" target="_blank" rel="noopener noreferrer">Presets Wiki</a>
<div class = "top_panel_spacer visible-on-desktop-only"></div>
<a href="#" class="presets_sources_show tool regular-button right" i18n="presetSources"></a>
<div class = "top_panel_spacer visible-on-desktop-only"></div>
<a href="#" class="presets_load_config visible-on-desktop-only tool regular-button right" i18n="presetsBackupLoad"></a>
<a href="#" class="presets_save_config visible-on-desktop-only tool regular-button right" i18n="presetsBackupSave"></a>
</div>
</div>
<div class="visible-on-phone-only presets_secondary_top_bar_panel_buttons">
<a href="#" class="presets_load_config tool regular-button disabled" i18n="presetsBackupLoad"></a>
<a href="#" class="presets_save_config tool regular-button disabled" i18n="presetsBackupSave"></a>
</div>
<div class="presets_warnings">
<div class="note presets_warning_not_official_source" i18n="presetsWarningNotOfficialSource"></div>
<div class="note presets_warning_backup">
<div class="presets_warning_backup_text" i18n="presetsWarningBackup"></div>
<a href="#" id="" class="tool regular-button presets_warning_backup_button_hide" i18n="dontShowAgain">Don't show again</a>
</div>
</div>
<div id="presets_global_loading" class="data-loading">
</div>
<div id="presets_global_loading_error">
<h3 i18n="presetsLoadError"></h3>
<a href="#" id="presets_reload" class="tool regular-button" i18n="presetsReload"></a>
</div>
<div id="presets_main_content">
<div class="presets_search_settings">
<div class="presets_filter_table_wrapper">
<div class="presets_filter_row">
<div class = "presets_fitler_table_header" i18n="presetsFilterCategory"></div>
<div class = "presets_fitler_table_value">
<select multiple="multiple" class="presets_filter_select" id="presets_filter_category"></select>
</div>
</div>
<div class="presets_filter_row">
<div class = "presets_fitler_table_header" i18n="presetsFilterKeyword"></div>
<div class = "presets_fitler_table_value">
<select multiple="multiple" class="presets_filter_select" id="presets_filter_keyword"></select>
</div>
</div>
<div class="presets_filter_row">
<div class = "presets_fitler_table_header" i18n="presetsFilterAuthor"></div>
<div class = "presets_fitler_table_value">
<select multiple="multiple" class="presets_filter_select" id="presets_filter_author"></select>
</div>
</div>
<div class="presets_filter_row">
<div class = "presets_fitler_table_header" i18n="presetsFilterFirmware"></div>
<div class = "presets_fitler_table_value">
<select multiple="multiple" class="presets_filter_select" id="presets_filter_firmware_version"></select>
</div>
</div>
<div class="presets_filter_row">
<div class = "presets_fitler_table_header" i18n="presetsFilterStatus"></div>
<div class = "presets_fitler_table_value">
<select multiple="multiple" class="presets_filter_select" id="presets_filter_status"></select>
</div>
</div>
</div>
<div id="presets_search_bar_wrapper">
<div id="presets_search_hint">
</div>
<input type="text" class="presets_text_input" id="presets_filter_text"/>
</div>
</div>
<div id="preset_list_wrapper">
<div id="presets_list_no_found" i18n="presetsNoPresetsFound"></div>
<div id="presets_list"></div>
<div id="presets_list_too_many_found" i18n="presetsTooManyPresetsFound">Too many found</div>
</div>
</div>
</div>
<div class="content_toolbar">
<div class="btn">
<a href="#" id="presets_save_button" class="tool regular-button" i18n="presetsButtonSave"></a>
<a href="#" id="presets_cancel_button" class="tool regular-button" i18n="presetsButtonCancel"></a>
</div>
</div>
<dialog id="presets_detailed_dialog">
</dialog>
<dialog id="presets_sources_dialog">
</dialog>
<dialog id="presets_apply_progress_dialog">
<div class="presets_apply_progress_dialog_label" i18n="presetsApplyingPresets"></div>
<div class="presets_apply_progress_dialog_please_wait" i18n="presetsPleaseWait"></div>
<progress class="presets_apply_progress_dialog_progress_bar" value="0" max="100"></progress>
</dialog>
<dialog id="presets_cli_errors_dialog">
<div class="presets_cli_errors_dialog_warning" i18n="presetsCliErrorsWaring"></div>
<div id="presets_cli">
<div id="presets_cli_background">
<div class="window" id="presets_cli_window">
<div class="wrapper" id="presets_cli_window_wrapper"></div>
</div>
</div>
<div class="commandline">
<textarea name="commands" id="presets_cli_command" rows="1" cols="0"></textarea>
</div>
</div>
<div class="btn">
<a href="#" id="presets_cli_errors_save_anyway_button" class="tool regular-button" i18n="presetsSaveAnyway"></a>
<a href="#" id="presets_cli_errors_exit_no_save_button" class="tool regular-button" i18n="presetsButtonCancel"></a>
</div>
</dialog>
</div>

549
src/tabs/presets/presets.js Normal file
View file

@ -0,0 +1,549 @@
'use strict';
TABS.presets = {
presetsRepo: null,
cliEngine: null,
pickedPresetList: [],
};
TABS.presets.initialize = function (callback) {
const self = this;
self.cliEngine = new CliEngine(self);
self.cliEngine.setProgressCallback(value => this.onApplyProgressChange(value));
self._presetPanels = [];
$('#content').load("./tabs/presets/presets.html", () => self.onHtmlLoad(callback));
if (GUI.active_tab !== 'presets') {
GUI.active_tab = 'presets';
}
};
TABS.presets.readDom = function() {
this._divGlobalLoading = $('#presets_global_loading');
this._divGlobalLoadingError = $('#presets_global_loading_error');
this._divCli = $('#presets_cli');
this._divMainContent = $('#presets_main_content');
this._selectCategory = $('#presets_filter_category');
this._selectKeyword = $('#presets_filter_keyword');
this._selectAuthor = $('#presets_filter_author');
this._selectFirmwareVersion = $('#presets_filter_firmware_version');
this._selectStatus = $('#presets_filter_status');
this._inputTextFilter = $('#presets_filter_text');
this._divPresetList = $('#presets_list');
this._domButtonSave = $("#presets_save_button");
this._domButtonCancel = $("#presets_cancel_button");
this._domReloadButton = $("#presets_reload");
this._domContentWrapper = $("#presets_content_wrapper");
this._domProgressDialog = $("#presets_apply_progress_dialog")[0];
this._domProgressDialogProgressBar = $(".presets_apply_progress_dialog_progress_bar");
this._domButtonaSaveAnyway = $("#presets_cli_errors_save_anyway_button");
this._domButtonaCliExit = $("#presets_cli_errors_exit_no_save_button");
this._domDialogCliErrors = $("#presets_cli_errors_dialog")[0];
this._domButtonSaveBackup = $(".presets_save_config");
this._domButtonLoadBackup = $(".presets_load_config");
this._domButtonPresetSources = $(".presets_sources_show");
this._domWarningNotOfficialSource = $(".presets_warning_not_official_source");
this._domWarningBackup = $(".presets_warning_backup");
this._domButtonHideBackupWarning = $(".presets_warning_backup_button_hide");
this._domListNoFound = $("#presets_list_no_found");
this._domListTooManyFound = $("#presets_list_too_many_found");
};
TABS.presets.getPickedPresetsCli = function() {
let result = [];
this.pickedPresetList.forEach(pickedPreset => {
result.push(...pickedPreset.presetCli);
});
result = result.filter(command => command !== "");
return result;
};
TABS.presets.onApplyProgressChange = function(value) {
this._domProgressDialogProgressBar.val(value);
};
TABS.presets.applyCommandsList = function(strings) {
strings.forEach(cliCommand => {
this.cliEngine.sendLine(cliCommand);
});
};
TABS.presets.onSaveClick = function() {
this._domProgressDialogProgressBar.val(0);
this._domProgressDialog.showModal();
const currentCliErrorsCount = this.cliEngine.errorsCount;
this.activateCli().then(() => {
const cliCommandsArray = this.getPickedPresetsCli();
this.cliEngine.executeCommandsArray(cliCommandsArray).then(() => {
const newCliErrorsCount = this.cliEngine.errorsCount;
if (newCliErrorsCount !== currentCliErrorsCount) {
this._domProgressDialog.close();
this._domDialogCliErrors.showModal();
} else {
this._domProgressDialog.close();
this.cliEngine.sendLine(CliEngine.s_commandSave);
this.disconnectCliMakeSure();
}
});
});
};
TABS.presets.disconnectCliMakeSure = function() {
GUI.timeout_add('disconnect', function () {
$('div.connect_controls a.connect').trigger( "click" );
}, 500);
};
TABS.presets.setupMenuButtons = function() {
this._domButtonSave.on("click", () => this.onSaveClick());
this._domButtonCancel.on("click", () => {
for(const pickedPreset of this.pickedPresetList) {
pickedPreset.preset.isPicked = false;
}
this.updateSearchResults();
this.pickedPresetList.length = 0;
this.enableSaveCancelButtons(false);
});
this._domButtonaCliExit.on("click", () =>{
this._domDialogCliErrors.close();
this.cliEngine.sendLine(CliEngine.s_commandExit);
this.disconnectCliMakeSure();
});
this._domButtonaSaveAnyway.on("click", () => {
this._domDialogCliErrors.close();
this.cliEngine.sendLine(CliEngine.s_commandSave);
this.disconnectCliMakeSure();
});
this._domButtonSaveBackup.on("click", () => this.onSaveConfigClick());
this._domButtonLoadBackup.on("click", () => this.onLoadConfigClick());
this._domButtonPresetSources.on("click", () => this.onPresetSourcesShowClick());
this._domButtonHideBackupWarning.on("click", () => this.onButtonHideBackupWarningClick());
this._domButtonSave.toggleClass(GUI.buttonDisabledClass, false);
this._domButtonCancel.toggleClass(GUI.buttonDisabledClass, false);
this._domReloadButton.on("click", () => this.reload());
this.enableSaveCancelButtons(false);
};
TABS.presets.enableSaveCancelButtons = function (isEnabled) {
this._domButtonSave.toggleClass(GUI.buttonDisabledClass, !isEnabled);
this._domButtonCancel.toggleClass(GUI.buttonDisabledClass, !isEnabled);
};
TABS.presets.onButtonHideBackupWarningClick = function() {
this._domWarningBackup.toggle(false);
ConfigStorage.set({ 'showPresetsWarningBackup': false });
};
TABS.presets.setupBackupWarning = function() {
const obj = ConfigStorage.get('showPresetsWarningBackup');
if (obj.showPresetsWarningBackup === undefined) {
obj.showPresetsWarningBackup = true;
}
const warningVisible = !!obj.showPresetsWarningBackup;
this._domWarningBackup.toggle(warningVisible);
};
TABS.presets.onPresetSourcesShowClick = function() {
this.presetsSourcesDialog.show().then(() => {
this.reload();
});
};
TABS.presets.onSaveConfigClick = function() {
const waitingDialog = GUI.showWaitDialog({title: i18n.getMessage("presetsLoadingDumpAll"), buttonCancelCallback: null});
const saveFailedDialogSettings = {
title: i18n.getMessage("warningTitle"),
text: i18n.getMessage("dumpAllNotSavedWarning"),
buttonConfirmText: i18n.getMessage("close"),
};
this.activateCli()
.then(() => this.readDumpAll())
.then(cliStrings => {
const prefix = 'cli_backup';
const suffix = 'txt';
const text = cliStrings.join("\n");
const filename = generateFilename(prefix, suffix);
return GUI.saveToTextFileDialog(text, filename, suffix);
})
.then(() => {
waitingDialog.close();
this.cliEngine.sendLine(CliEngine.s_commandExit);
})
.catch(() => {
waitingDialog.close();
return GUI.showInformationDialog(saveFailedDialogSettings);
})
.then(() => this.cliEngine.sendLine(CliEngine.s_commandExit));
};
TABS.presets.readDumpAll = function() {
let lastCliStringReceived = performance.now();
const diffAll = [CliEngine.s_commandDefaultsNoSave, ""];
const readingDumpIntervalName = "PRESETS_READING_DUMP_INTERVAL";
this.cliEngine.subscribeOnRowCame(str => {
lastCliStringReceived = performance.now();
if (CliEngine.s_commandDiffAll !== str && CliEngine.s_commandSave !== str) {
diffAll.push(str);
}
});
this.cliEngine.sendLine(CliEngine.s_commandDiffAll);
return new Promise(resolve => {
GUI.interval_add(readingDumpIntervalName, () => {
const currentTime = performance.now();
if (currentTime - lastCliStringReceived > 500) {
this.cliEngine.unsubscribeOnRowCame();
GUI.interval_remove(readingDumpIntervalName);
resolve(diffAll);
}
}, 500, false);
});
};
TABS.presets.onLoadConfigClick = function() {
GUI.readTextFileDialog("txt")
.then(text => {
if (text) {
const cliStrings = text.split("\n");
const pickedPreset = new PickedPreset({title: "user configuration"}, cliStrings);
this.pickedPresetList.push(pickedPreset);
this.onSaveClick();
}
});
};
TABS.presets.onHtmlLoad = function(callback) {
i18n.localizePage();
TABS.presets.adaptPhones();
this.readDom();
this.setupMenuButtons();
this.setupBackupWarning();
this._inputTextFilter.attr("placeholder", "example: \"karate racing\", or \"5'' freestyle\"");
this.presetsDetailedDialog = new PresetsDetailedDialog($("#presets_detailed_dialog"), this.pickedPresetList, () => this.onPresetPickedCallback());
this.presetsSourcesDialog = new PresetsSourcesDialog($("#presets_sources_dialog"));
this.presetsDetailedDialog.load()
.then(() => this.presetsSourcesDialog.load())
.then(() => {
this.tryLoadPresets();
GUI.content_ready(callback);
});
};
TABS.presets.onPresetPickedCallback = function() {
this.enableSaveCancelButtons(true);
};
TABS.presets.activateCli = function() {
return new Promise(resolve => {
CONFIGURATOR.cliEngineActive = true;
this.cliEngine.setUi($('#presets_cli_window'), $('#presets_cli_window_wrapper'), $('#presets_cli_command'));
this.cliEngine.enterCliMode();
GUI.timeout_add('presets_enter_cli_mode_done', () => {
resolve();
}, 500);
});
};
TABS.presets.reload = function() {
this.resetInitialValues();
this.tryLoadPresets();
};
TABS.presets.tryLoadPresets = function() {
const presetSource = this.presetsSourcesDialog.getActivePresetSource();
if (PresetSource.isUrlGithubRepo(presetSource.url)) {
this.presetsRepo = new PresetsGithubRepo(presetSource.url, presetSource.gitHubBranch);
} else {
this.presetsRepo = new PresetsWebsiteRepo(presetSource.url);
}
this._divMainContent.toggle(false);
this._divGlobalLoadingError.toggle(false);
this._divGlobalLoading.toggle(true);
this._domWarningNotOfficialSource.toggle(!this.presetsSourcesDialog.isOfficialActive);
this.presetsRepo.loadIndex().then(() => {
this.prepareFilterFields();
this._divGlobalLoading.toggle(false);
this._divMainContent.toggle(true);
}).catch(err => {
this._divGlobalLoading.toggle(false);
this._divGlobalLoadingError.toggle(true);
console.error(err);
});
};
TABS.presets.prepareFilterFields = function() {
this._freezeSearch = true;
this.prepareFilterSelectField(this._selectCategory, this.presetsRepo.index.uniqueValues.category);
this.prepareFilterSelectField(this._selectKeyword, this.presetsRepo.index.uniqueValues.keywords);
this.prepareFilterSelectField(this._selectAuthor, this.presetsRepo.index.uniqueValues.author);
this.prepareFilterSelectField(this._selectFirmwareVersion, this.presetsRepo.index.uniqueValues.firmware_version);
this.prepareFilterSelectField(this._selectStatus, this.presetsRepo.index.settings.PresetStatusEnum);
this.preselectFilterFields();
this._inputTextFilter.on('input', () => this.updateSearchResults());
this._freezeSearch = false;
this.updateSearchResults();
};
TABS.presets.preselectFilterFields = function() {
this._selectCategory.multipleSelect('setSelects', ["TUNE"]);
const currentVersion = FC.CONFIG.flightControllerVersion;
const selectedVersions = [];
for(const bfVersion of this.presetsRepo.index.uniqueValues.firmware_version) {
if (currentVersion.startsWith(bfVersion)) {
selectedVersions.push(bfVersion);
}
}
this._selectFirmwareVersion.multipleSelect('setSelects', selectedVersions);
};
TABS.presets.prepareFilterSelectField = function(domSelectElement, selectOptions) {
domSelectElement.multipleSelect("destroy");
domSelectElement.multipleSelect({
data: selectOptions,
placeholder: i18n.getMessage("dropDownAll"),
onClick: () => { this.updateSearchResults(); },
onCheckAll: () => { this.updateSearchResults(); },
onUncheckAll: () => { this.updateSearchResults(); },
formatSelectAll () { return i18n.getMessage("dropDownSelectAll"); },
formatAllSelected() { return i18n.getMessage("dropDownAll"); },
});
};
TABS.presets.updateSearchResults = function() {
if (!this._freezeSearch)
{
const searchParams = {
categories: this._selectCategory.multipleSelect("getSelects", "text"),
keywords: this._selectKeyword.multipleSelect("getSelects", "text"),
authors: this._selectAuthor.multipleSelect("getSelects", "text"),
firmwareVersions: this._selectFirmwareVersion.multipleSelect("getSelects", "text"),
status: this._selectStatus.multipleSelect("getSelects", "text"),
searchString: this._inputTextFilter.val().trim(),
};
searchParams.authors = searchParams.authors.map(str => str.toLowerCase());
const fitPresets = this.getFitPresets(searchParams);
this.displayPresets(fitPresets);
}
};
TABS.presets.displayPresets = function(fitPresets) {
this._presetPanels.forEach(presetPanel => {
presetPanel.remove();
});
this._presetPanels = [];
const maxPresetsToShow = 60;
this._domListTooManyFound.toggle(fitPresets.length > maxPresetsToShow);
fitPresets.length = Math.min(fitPresets.length, maxPresetsToShow);
this._domListNoFound.toggle(fitPresets.length === 0);
fitPresets.forEach(preset => {
const presetPanel = new PresetTitlePanel(this._divPresetList, preset, true);
presetPanel.load();
this._presetPanels.push(presetPanel);
presetPanel.subscribeClick(this.presetsDetailedDialog, this.presetsRepo);
});
this._domListTooManyFound.appendTo(this._divPresetList);
};
TABS.presets.getFitPresets = function(searchParams) {
const result = [];
for(const preset of this.presetsRepo.index.presets) {
if(this.isPresetFitSearch(preset, searchParams)) {
result.push(preset);
}
}
return result;
};
TABS.presets.isPresetFitSearchStatuses = function(preset, searchParams) {
return 0 === searchParams.status.length || searchParams.status.includes(preset.status);
};
TABS.presets.isPresetFitSearchCategories = function(preset, searchParams) {
if (0 !== searchParams.categories.length) {
if (undefined === preset.category) {
return false;
}
if (!searchParams.categories.includes(preset.category)) {
return false;
}
}
return true;
};
TABS.presets.isPresetFitSearchKeywords = function(preset, searchParams) {
if (0 !== searchParams.keywords.length) {
if (!Array.isArray(preset.keywords)) {
return false;
}
const keywordsIntersection = searchParams.keywords.filter(value => preset.keywords.includes(value));
if (0 === keywordsIntersection.length) {
return false;
}
}
return true;
};
TABS.presets.isPresetFitSearchAuthors = function(preset, searchParams) {
if (0 !== searchParams.authors.length) {
if (undefined === preset.author) {
return false;
}
if (!searchParams.authors.includes(preset.author.toLowerCase())) {
return false;
}
}
return true;
};
TABS.presets.isPresetFitSearchFirmwareVersions = function(preset, searchParams) {
if (0 !== searchParams.firmwareVersions.length) {
if (!Array.isArray(preset.firmware_version)) {
return false;
}
const firmwareVersionsIntersection = searchParams.firmwareVersions.filter(value => preset.firmware_version.includes(value));
if (0 === firmwareVersionsIntersection.length) {
return false;
}
}
return true;
};
TABS.presets.isPresetFitSearchString = function(preset, searchParams) {
if (searchParams.searchString)
{
const allKeywords = preset.keywords.join(" ");
const totalLine = [preset.description, allKeywords, preset.title, preset.author].join("\n").toLowerCase().replace("''", "\"");
const allWords = searchParams.searchString.toLowerCase().replace("''", "\"").split(" ");
for (const word of allWords) {
if (!totalLine.includes(word)) {
return false;
}
}
}
return true;
};
TABS.presets.isPresetFitSearch = function(preset, searchParams) {
if (preset.hidden) {
return false;
}
if (!this.isPresetFitSearchStatuses(preset, searchParams)) {
return false;
}
if (!this.isPresetFitSearchCategories(preset, searchParams)) {
return false;
}
if (!this.isPresetFitSearchKeywords(preset, searchParams)) {
return false;
}
if (!this.isPresetFitSearchAuthors(preset, searchParams)) {
return false;
}
if (!this.isPresetFitSearchFirmwareVersions(preset, searchParams)) {
return false;
}
if (!this.isPresetFitSearchString(preset, searchParams)) {
return false;
}
return true;
};
TABS.presets.adaptPhones = function() {
if (GUI.isCordova()) {
UI_PHONES.initToolbar();
}
};
TABS.presets.read = function(readInfo) {
TABS.presets.cliEngine.readSerial(readInfo);
};
TABS.presets.cleanup = function(callback) {
this.resetInitialValues();
if (!(CONFIGURATOR.connectionValid && CONFIGURATOR.cliEngineActive && CONFIGURATOR.cliEngineValid)) {
if (callback) {
callback();
}
return;
}
TABS.presets.cliEngine.close(() => {
if (callback) {
callback();
}
});
};
TABS.presets.resetInitialValues = function() {
CONFIGURATOR.cliEngineActive = false;
CONFIGURATOR.cliEngineValid = false;
TABS.presets.presetsRepo = null;
TABS.presets.pickedPresetList.length = 0;
this._domProgressDialog.close();
};
TABS.presets.expertModeChanged = function(expertModeEnabled) {
this._domShowHideCli.toggle(expertModeEnabled);
};

View file

@ -6311,6 +6311,11 @@ multipipe@^0.1.2:
dependencies:
duplexer2 "0.0.2"
multiple-select@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/multiple-select/-/multiple-select-1.5.2.tgz#a7290785fc10fa4b9f26d2a92937effc63648bc8"
integrity sha512-sTNNRrjnTtB1b1+HTKcjQ/mjWY7Gvigo9F3C/3oTQCTFEpYzwaRYFPRAOu2SogfA1hEfyJTXjyS1VAbanJMsmA==
murmur-32@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/murmur-32/-/murmur-32-0.1.0.tgz#c1a79d4fc5fabf0405749d0aff77c41402055861"
@ -10155,10 +10160,10 @@ yargs@~3.10.0:
decamelize "^1.0.0"
window-size "0.1.0"
yarn@^1.22.0:
version "1.22.5"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.5.tgz#1933b7635429ca00847222dd9d38f05646e2df23"
integrity sha512-5uzKXwdMc++mYktXqkfpNYT9tY8ViWegU58Hgbo+KXzrzzhEyP1Ip+BTtXloLrXNcNlxFJbLiFKGaS9vK9ym6Q==
yarn@^1.22.17:
version "1.22.17"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.17.tgz#bf910747d22497b573131f7341c0e1d15c74036c"
integrity sha512-H0p241BXaH0UN9IeH//RT82tl5PfNraVpSpEoW+ET7lmopNC61eZ+A+IDvU8FM6Go5vx162SncDL8J1ZjRBriQ==
yauzl@2.4.1:
version "2.4.1"