From ca09909d6efe718d9b961eac91030fbef49ad4e1 Mon Sep 17 00:00:00 2001 From: Ivan Efimov Date: Wed, 16 Dec 2020 22:58:43 -0600 Subject: [PATCH] Motor direction dialog for Dshot escs --- locales/en/messages.json | 78 +++- src/components/EscDshotDirection/Body.html | 66 +++ .../EscDshotDirection/EscDshotCommandQueue.js | 67 +++ .../EscDshotDirectionComponent.js | 383 ++++++++++++++++++ .../EscDshotDirectionMotorDriver.js | 155 +++++++ src/components/EscDshotDirection/Styles.css | 177 ++++++++ .../MotorOutputReorderingComponent.js | 12 +- .../MotorOutputReordering/Styles.css | 2 - src/css/dark-theme.css | 13 +- src/css/main.css | 36 +- src/css/tabs/motors.css | 39 +- src/js/msp/MSPCodes.js | 1 + src/js/msp/MSPHelper.js | 31 +- src/js/tabs/configuration.js | 39 +- src/js/tabs/motors.js | 91 +++-- src/js/utils/CommonUtils.js | 15 + src/js/utils/DshotCommand.js | 40 ++ src/js/utils/EscProtocols.js | 95 +++++ src/main.html | 7 + src/tabs/motors.html | 12 +- 20 files changed, 1255 insertions(+), 104 deletions(-) create mode 100644 src/components/EscDshotDirection/Body.html create mode 100644 src/components/EscDshotDirection/EscDshotCommandQueue.js create mode 100644 src/components/EscDshotDirection/EscDshotDirectionComponent.js create mode 100644 src/components/EscDshotDirection/EscDshotDirectionMotorDriver.js create mode 100644 src/components/EscDshotDirection/Styles.css create mode 100644 src/js/utils/CommonUtils.js create mode 100644 src/js/utils/DshotCommand.js create mode 100644 src/js/utils/EscProtocols.js diff --git a/locales/en/messages.json b/locales/en/messages.json index ce2a0f17..8b6f4ace 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -2570,7 +2570,7 @@ "message": "Safety notice
Remove all propellers to prevent injury!
The motors will spin up!" }, "motorsRemapDialogExplanations": { - "message": "Information notice
Motors will spin up one by one and you will be able to select which motor is spinning. The battery should be plugged in, correct ESC protocol should be selected. This utility can only re-arrange currently active motors. More complex re-mapping requires the CLI Resource command. Refer to this Wiki page." + "message": "Information notice
Motors will spin up one by one and you will be able to select which motor is spinning. The battery should be plugged in, correct ESC protocol should be selected. This utility can only re-arrange currently active motors. More complex re-mapping requires the CLI Resource command. Refer to this Wiki page." }, "motorsRemapDialogSave": { "message": "Save" @@ -2579,6 +2579,82 @@ "message": "Start over" }, + "escDshotDirectionDialog-Title": { + "message": "Motor Direction - Warning: Ensure props are removed!" + }, + "escDshotDirectionDialog-SelectMotor": { + "message": "Select one or all motors" + }, + "escDshotDirectionDialog-SelectMotorSafety": { + "message": "Motors will spin when selected!" + }, + "escDshotDirectionDialog-RiskNotice": { + "message": "Safety notice
Remove all propellers to prevent injury!
The motors will spin up immediately when selected!" + }, + "escDshotDirectionDialog-UnderstandRisks": { + "message": "I understand the risks,
all propellers are removed." + }, + "escDshotDirectionDialog-InformationNotice": { + "message": "Information notice
To change the motor directions, the battery must be plugged in and the correct ESC protocol must be set up in the $t(tabConfiguration.message) tab. Note that not all Dshot ESCs will work with this dialog. Check your ESC firmware." + }, + "escDshotDirectionDialog-NormalInformationNotice": { + "message": "Set motor spin direction by selecting and spinning each motor individually." + }, + "escDshotDirectionDialog-WizardInformationNotice": { + "message": "Resets all motor spin directions, then allows the user to choose which to reverse." + }, + "escDshotDirectionDialog-Open": { + "message": "Motor direction" + }, + "escDshotDirectionDialog-CommandNormal": { + "message": "Normal" + }, + "escDshotDirectionDialog-CommandReverse": { + "message": "Reverse" + }, + "escDshotDirectionDialog-CommandSpin": { + "message": "Test motor" + }, + "escDshotDirectionDialog-ReleaseButtonToStop": { + "message": "Release button to stop" + }, + "escDshotDirectionDialog-ReleaseToStop": { + "message": "Release to stop" + }, + "escDshotDirectionDialog-Start": { + "message": "Individually" + }, + "escDshotDirectionDialog-StartWizard": { + "message": "Wizard" + }, + "escDshotDirectionDialog-SetDirectionHint": { + "message": "Change direction of selected motor(s)" + }, + "escDshotDirectionDialog-SetDirectionHintSafety": { + "message": "Motors will spin when setting the direction!" + }, + "escDshotDirectionDialog-WrongProtocolText": { + "message": "Feature works with DSHOT ESCs only.
Verify that your ESC (electric speed controller) supports DSHOT protocol and change it on $t(tabConfiguration.message) tab." + }, + "escDshotDirectionDialog-WrongMixerText": { + "message": "Number of motors is 0.
Verify the current Mixer on $t(tabConfiguration.message) tab or setup a custom one through CLI. Refer to this Wiki page." + }, + "escDshotDirectionDialog-WrongFirmwareText": { + "message": "Update the firmware.
Make sure you are using the latest firmware: Betaflight 4.3 or newer." + }, + "escDshotDirectionDialog-WizardActionHint": { + "message": "Click on motor numbers individually to change spin direction" + }, + "escDshotDirectionDialog-WizardActionHintSecondLine": { + "message": "Verify all motors are spinning correctly" + }, + "escDshotDirectionDialog-SpinWizard": { + "message": "Start / spin motors" + }, + "escDshotDirectionDialog-StopWizard": { + "message": "Stop motors" + }, + "sensorsInfo": { "message": "Keep in mind that using fast update periods and rendering multiple graphs at the same time is resource heavy and will burn your battery quicker if you use a laptop.
We recommend to only render graphs for sensors you are interested in while using reasonable update periods." }, diff --git a/src/components/EscDshotDirection/Body.html b/src/components/EscDshotDirection/Body.html new file mode 100644 index 00000000..3ccb715a --- /dev/null +++ b/src/components/EscDshotDirection/Body.html @@ -0,0 +1,66 @@ +
+

+
+
+
+
+
+
+
+ +
+
+

+

+
+
+
+

+

+
+ + +
+
+
+
+ +
+

+

+
+
+ +
+
+
+
+
+

+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
diff --git a/src/components/EscDshotDirection/EscDshotCommandQueue.js b/src/components/EscDshotDirection/EscDshotCommandQueue.js new file mode 100644 index 00000000..63d48577 --- /dev/null +++ b/src/components/EscDshotDirection/EscDshotCommandQueue.js @@ -0,0 +1,67 @@ +'use strict'; + +class EscDshotCommandQueue +{ + constructor (intervalMs) + { + this._intervalId = null; + this._interval = intervalMs; + this._queue = []; + this._purging = false; + } + + pushCommand(command, buffer) + { + this._queue.push([command, buffer]); + } + + pushPause(milliseconds) + { + const counter = Math.ceil(milliseconds / this._interval); + + for (let i = 0; i < counter; i++) { + this.pushCommand(null, null); + } + } + + start() + { + if (null === this._intervalId) { + this._intervalId = setInterval( + () => { this._checkQueue(); }, + this._interval); + } + } + + stop() + { + if(null !== this._intervalId) { + clearInterval(this._intervalId); + this._intervalId = null; + } + } + + stopWhenEmpty() + { + this._purging = true; + } + + clear() + { + this._queue = []; + } + + _checkQueue() + { + if (0 !== this._queue.length) { + const command = this._queue.shift(); + + if (null !== command[0]) { + MSP.send_message(command[0], command[1]); + } + } else if (this._purging) { + this._purging = false; + this.stop(); + } + } +} diff --git a/src/components/EscDshotDirection/EscDshotDirectionComponent.js b/src/components/EscDshotDirection/EscDshotDirectionComponent.js new file mode 100644 index 00000000..66656d8b --- /dev/null +++ b/src/components/EscDshotDirection/EscDshotDirectionComponent.js @@ -0,0 +1,383 @@ +'use strict'; + +class EscDshotDirectionComponent +{ + constructor(contentDiv, onLoadedCallback, motorConfig) + { + this._buttonTimeoutMs = 400; + const motorDriverQueueIntervalMs = 100; + const motorDriverStopMotorsPauseMs = 400; + + this._motorDriver = new EscDshotDirectionMotorDriver(motorConfig, + motorDriverQueueIntervalMs, motorDriverStopMotorsPauseMs); + this._escProtocolIsDshot = motorConfig.escProtocolIsDshot; + this._numberOfMotors = motorConfig.numberOfMotors; + this._contentDiv = contentDiv; + this._onLoadedCallback = onLoadedCallback; + this._currentSpinningMotor = -1; + this._selectedMotor = -1; + this._motorIsSpinning = false; + this._allMotorsAreSpinning = false; + this._spinDirectionToggleIsActive = true; + this._activationButtonTimeoutId = null; + + this._contentDiv.load("./components/EscDshotDirection/Body.html", () => + { + this._initializeDialog(); + }); + } + + static get PUSHED_BUTTON_CLASS() { return "pushed"; } + static get HIGHLIGHTED_BUTTON_CLASS() { return "highlighted"; } + static get RED_TEXT_CLASS() { return "red-text"; } + + _readDom() + { + this._domAgreeSafetyCheckBox = $("#escDshotDirectionDialog-safetyCheckbox"); + this._domStartButton = $("#escDshotDirectionDialog-Start"); + this._domStartWizardButton = $("#escDshotDirectionDialog-StartWizard"); + this._domMainContentBlock = $("#escDshotDirectionDialog-MainContent"); + this._domWarningContentBlock = $("#escDshotDirectionDialog-Warning"); + this._domMixerImg = $("#escDshotDirectionDialog-MixerPreviewImg"); + this._domMotorButtonsBlock = $("#escDshotDirectionDialog-SelectMotorButtonsWrapper"); + this._domSpinDirectionWrapper = $("#escDshotDirectionDialog-CommandsWrapper"); + this._domActionHint = $("#escDshotDirectionDialog-ActionHint"); + this._domSpinNormalButton = $("#escDshotDirectionDialog-RotationNormal"); + this._domSpinReverseButton = $("#escDshotDirectionDialog-RotationReverse"); + this._domSecondHint = $("#escDshotDirectionDialog-SecondHint"); + this._domSecondActionDiv = $("#escDshotDirectionDialog-SecondActionBlock"); + this._domConfigErrors = $("#escDshotDirectionDialog-ConfigErrors"); + this._domWrongProtocolMessage = $("#escDshotDirectionDialog-WrongProtocol"); + this._domWrongMixerMessage = $("#escDshotDirectionDialog-WrongMixer"); + this._domWrongFirmwareMessage = $("#escDshotDirectionDialog-WrongFirmware"); + this._domWizardBlock = $("#escDshotDirectionDialog-WizardDialog"); + this._domNormalDialogBlock = $("#escDshotDirectionDialog-NormalDialog"); + this._domSpinningWizard = $("#escDshotDirectionDialog-SpinningWizard"); + this._domSpinWizardButton = $("#escDshotDirectionDialog-SpinWizard"); + this._domStopWizardButton = $("#escDshotDirectionDialog-StopWizard"); + this._domWizardMotorButtonsBlock = $("#escDshotDirectionDialog-WizardMotorButtons"); + this._domStartWizardBlock = $("#escDshotDirectionDialog-StartWizardBlock"); + this._domStartNormalBlock = $("#escDshotDirectionDialog-StartNormalBlock"); + + this._topHintText = i18n.getMessage("escDshotDirectionDialog-SelectMotor"); + this._releaseToStopText = i18n.getMessage("escDshotDirectionDialog-ReleaseToStop"); + this._releaseButtonToStopText = i18n.getMessage("escDshotDirectionDialog-ReleaseButtonToStop"); + this._normalText = i18n.getMessage("escDshotDirectionDialog-CommandNormal"); + this._reverseText = i18n.getMessage("escDshotDirectionDialog-CommandReverse"); + this._secondHintText = i18n.getMessage("escDshotDirectionDialog-SetDirectionHint"); + } + + _initializeDialog() + { + this._readDom(); + this._createMotorButtons(); + this._createWizardMotorButtons(); + this._domSecondActionDiv.toggle(false); + i18n.localizePage(); + + this._resetGui(); + + this._domAgreeSafetyCheckBox.on("change", () => { + const enabled = this._domAgreeSafetyCheckBox.is(':checked'); + this._domStartNormalBlock.toggle(enabled); + this._domStartWizardBlock.toggle(enabled); + }); + + this._domStartButton.on("click", () => { + this._onStartButtonClicked(); + }); + + this._domStartWizardButton.on("click", () => { + this._onStartWizardButtonClicked(); + }); + + this._domSpinWizardButton.on("click", () => { + this._onSpinWizardButtonClicked(); + }); + + this._domStopWizardButton.on("click", () => { + this._onStopWizardButtonClicked(); + }); + + const imgSrc = CommonUtils.GetMixerImageSrc(FC.MIXER_CONFIG.mixer, FC.MIXER_CONFIG.reverseMotorDir, FC.CONFIG.apiVersion); + this._domMixerImg.attr('src', imgSrc); + + this._onLoadedCallback(); + } + + _activateNormalReverseButtons(timeoutMs) + { + this._activationButtonTimeoutId = setTimeout(() => { + this._subscribeDirectionSpinButton(this._domSpinNormalButton, + DshotCommand.dshotCommands_e.DSHOT_CMD_SPIN_DIRECTION_1, this._normalText); + this._subscribeDirectionSpinButton(this._domSpinReverseButton, + DshotCommand.dshotCommands_e.DSHOT_CMD_SPIN_DIRECTION_2, this._reverseText); + }, timeoutMs); + } + + _deactivateNormalReverseButtons() + { + if (null !== this._activationButtonTimeoutId) + { + clearTimeout(this._activationButtonTimeoutId); + } + + this._domSpinNormalButton.off(); + this._domSpinReverseButton.off(); + } + + _subscribeDirectionSpinButton(button, direction, buttonText) + { + button.on("mousedown touchstart", () => { + this._sendCurrentEscSpinDirection(direction); + this._motorIsSpinning = true; + button.text(this._releaseToStopText); + button.addClass(EscDshotDirectionComponent.HIGHLIGHTED_BUTTON_CLASS); + this._motorDriver.spinMotor(this._selectedMotor); + this._domSecondHint.html(this._releaseButtonToStopText); + this._domSecondHint.addClass(EscDshotDirectionComponent.RED_TEXT_CLASS); + }); + + button.on("mouseup mouseout touchend", () => { + if (this._motorIsSpinning) { + button.text(buttonText); + this._motorIsSpinning = false; + button.removeClass(EscDshotDirectionComponent.HIGHLIGHTED_BUTTON_CLASS); + this._motorDriver.stopAllMotors(); + this._domSecondHint.text(this._secondHintText); + this._domSecondHint.removeClass(EscDshotDirectionComponent.RED_TEXT_CLASS); + + this._deactivateNormalReverseButtons(); + this._activateNormalReverseButtons(this._buttonTimeoutMs); + } + }); + } + + _sendCurrentEscSpinDirection(direction) + { + this._motorDriver.setEscSpinDirection(this._selectedMotor, direction); + } + + _createMotorButtons() + { + this._motorButtons = {}; + + for (let i = 0; i < this._numberOfMotors; i++) { + this._addMotorButton(i + 1, i); + } + + this._addMotorButton("All", DshotCommand.ALL_MOTORS); + } + + _addMotorButton(buttonText, motorIndex) + { + const button = $(``).text(buttonText); + this._domMotorButtonsBlock.append(button); + this._motorButtons[motorIndex] = button; + + button.on("mousedown touchstart", () => { + this._domSecondActionDiv.toggle(true); + this._motorIsSpinning = true; + this._domActionHint.html(this._releaseButtonToStopText); + this._domActionHint.addClass(EscDshotDirectionComponent.RED_TEXT_CLASS); + this._changeSelectedMotor(motorIndex); + button.addClass(EscDshotDirectionComponent.HIGHLIGHTED_BUTTON_CLASS); + this._motorDriver.spinMotor(this._selectedMotor); + }); + + button.on("mouseup mouseout touchend", () => { + if (this._motorIsSpinning) { + this._domActionHint.html(this._topHintText); + this._domActionHint.removeClass(EscDshotDirectionComponent.RED_TEXT_CLASS); + this._motorIsSpinning = false; + button.removeClass(EscDshotDirectionComponent.HIGHLIGHTED_BUTTON_CLASS); + this._motorDriver.stopAllMotors(); + + this._deactivateNormalReverseButtons(); + this._activateNormalReverseButtons(this._buttonTimeoutMs); + } + }); + } + + _createWizardMotorButtons() + { + this._wizardMotorButtons = {}; + + for (let i = 0; i < this._numberOfMotors; i++) { + this._addWizardMotorButton(i + 1, i); + } + } + + _activateWizardMotorButtons(timeoutMs) + { + this._activationButtonTimeoutId = setTimeout(() => { + for (let i = 0; i < this._numberOfMotors; i++) { + this._activateWizardMotorButton(i); + } + }, timeoutMs); + } + + _deactivateWizardMotorButtons() + { + if (null !== this._activationButtonTimeoutId) + { + clearTimeout(this._activationButtonTimeoutId); + } + + for (let i = 0; i < this._numberOfMotors; i++) { + const button = this._wizardMotorButtons[i]; + button.off(); + } + } + + _addWizardMotorButton(buttonText, motorIndex) + { + const button = $(``).text(buttonText); + this._domWizardMotorButtonsBlock.append(button); + this._wizardMotorButtons[motorIndex] = button; + } + + _activateWizardMotorButton(motorIndex) + { + const button = this._wizardMotorButtons[motorIndex]; + + button.on("click", () => { + this._wizardMotorButtonClick(button, motorIndex); + }); + } + + _wizardMotorButtonClick(button, motorIndex) + { + this._deactivateWizardMotorButtons(); + const currentlyDown = button.hasClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + + if (currentlyDown) { + button.removeClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + this._motorDriver.setEscSpinDirection(motorIndex, DshotCommand.dshotCommands_e.DSHOT_CMD_SPIN_DIRECTION_1); + } else { + this._motorDriver.setEscSpinDirection(motorIndex, DshotCommand.dshotCommands_e.DSHOT_CMD_SPIN_DIRECTION_2); + button.addClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + } + + this._activateWizardMotorButtons(this._buttonTimeoutMs); + } + + _changeSelectedMotor(newIndex) + { + if (this._selectedMotor >= 0) { + this._motorButtons[this._selectedMotor].addClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + } + + this._selectedMotor = newIndex; + + if (this._selectedMotor > -1) { + this._motorButtons[this._selectedMotor].removeClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + } + } + + close() + { + this._motorDriver.stopAllMotorsNow(); + this._motorDriver.deactivate(); + this._resetGui(); + } + + _resetGui() + { + this._toggleMainContent(false); + this._domStartNormalBlock.hide(); + this._domStartWizardBlock.hide(); + + this._domAgreeSafetyCheckBox.prop('checked', false); + this._domAgreeSafetyCheckBox.trigger('change'); + this._domSecondActionDiv.toggle(false); + this._changeSelectedMotor(-1); + + this._checkForConfigurationErrors(); + } + + _checkForConfigurationErrors() + { + let anyError = false; + + this._domWrongProtocolMessage.hide(); + this._domWrongMixerMessage.hide(); + this._domWrongFirmwareMessage.hide(); + + if (!this._escProtocolIsDshot) { + anyError = true; + this._domWrongProtocolMessage.show(); + } + + if (this._numberOfMotors <= 0) { + anyError = true; + this._domWrongMixerMessage.show(); + } + + if (!semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_44)) { + // if BF4.2 or older - show the error message + anyError = true; + this._domWrongFirmwareMessage.show(); + } + + if (anyError) { + this._domMainContentBlock.hide(); + this._domWarningContentBlock.hide(); + this._domStartNormalBlock.hide(); + this._domStartWizardBlock.hide(); + this._domConfigErrors.show(); + } else { + this._domConfigErrors.hide(); + } + } + + + _onStartButtonClicked() + { + this._toggleMainContent(true); + this._domWizardBlock.toggle(false); + this._domNormalDialogBlock.toggle(true); + this._motorDriver.activate(); + } + + _onStartWizardButtonClicked() + { + this._domSpinningWizard.toggle(false); + this._domSpinWizardButton.toggle(true); + this._toggleMainContent(true); + this._domWizardBlock.toggle(true); + this._domNormalDialogBlock.toggle(false); + this._motorDriver.activate(); + } + + _onSpinWizardButtonClicked() + { + for (let i = 0; i < this._numberOfMotors; i++) { + this._wizardMotorButtons[i].removeClass(EscDshotDirectionComponent.PUSHED_BUTTON_CLASS); + } + + this._motorDriver.setEscSpinDirection(DshotCommand.ALL_MOTORS, DshotCommand.dshotCommands_e.DSHOT_CMD_SPIN_DIRECTION_1); + + this._domSpinWizardButton.toggle(false); + this._domSpinningWizard.toggle(true); + this._motorDriver.spinAllMotors(); + + this._activateWizardMotorButtons(0); + } + + _onStopWizardButtonClicked() + { + this._domSpinWizardButton.toggle(true); + this._domSpinningWizard.toggle(false); + this._motorDriver.stopAllMotorsNow(); + this._deactivateWizardMotorButtons(); + } + + _toggleMainContent(value) + { + this._domWarningContentBlock.toggle(!value); + this._domMainContentBlock.toggle(value); + this._domConfigErrors.toggle(false); + } + +} diff --git a/src/components/EscDshotDirection/EscDshotDirectionMotorDriver.js b/src/components/EscDshotDirection/EscDshotDirectionMotorDriver.js new file mode 100644 index 00000000..fb652a0f --- /dev/null +++ b/src/components/EscDshotDirection/EscDshotDirectionMotorDriver.js @@ -0,0 +1,155 @@ +'use strict'; + +class EscDshotDirectionMotorDriver +{ + constructor(motorConfig, motorDriverQueueIntervalMs, motorDriverStopMotorsPauseMs) + { + this._numberOfMotors = motorConfig.numberOfMotors; + this._motorStopValue = motorConfig.motorStopValue; + this._motorSpinValue = motorConfig.motorSpinValue; + this._motorDriverStopMotorsPauseMs = motorDriverStopMotorsPauseMs; + + this._state = []; + + for (let i = 0; i < this._numberOfMotors; i++) + { + this._state.push(this._motorStopValue); + } + + this._stateStack = []; + + this._EscDshotCommandQueue = new EscDshotCommandQueue(motorDriverQueueIntervalMs); + } + + activate() + { + this._EscDshotCommandQueue.start(); + } + + deactivate() + { + this._EscDshotCommandQueue.stopWhenEmpty(); + } + + stopMotor(motorIndex) + { + this._spinMotor(motorIndex, this._motorStopValue); + } + + + spinMotor(motorIndex) + { + this._spinMotor(motorIndex, this._motorSpinValue); + } + + spinAllMotors() + { + this._spinAllMotors(this._motorSpinValue); + } + + stopAllMotors() + { + this._spinAllMotors(this._motorStopValue); + } + + stopAllMotorsNow() + { + this._EscDshotCommandQueue.clear(); + this._spinAllMotors(this._motorStopValue); + } + + setEscSpinDirection(motorIndex, direction) + { + let needStopMotor = false; + + if (DshotCommand.ALL_MOTORS === motorIndex) { + needStopMotor = this._isAnythingSpinning(); + } else { + needStopMotor = this._isMotorSpinning(motorIndex); + } + + if (needStopMotor) { + this._pushState(); + this._spinMotor(motorIndex, this._motorStopValue); + this._EscDshotCommandQueue.pushPause(this._motorDriverStopMotorsPauseMs); + this._sendEscSpinDirection(motorIndex, direction); + this._popState(); + this._sendState(); + } else { + this._sendEscSpinDirection(motorIndex, direction); + } + } + + _pushState() + { + const state = [...this._state]; + this._stateStack.push(state); + } + + _popState() + { + const state = this._stateStack.pop(); + this._state = [...state]; + } + + _isAnythingSpinning() + { + let result = false; + + for (let i = 0; i < this._numberOfMotors; i++) { + if (this._motorStopValue !== this._state[i]) { + result = true; + break; + } + } + + return result; + } + + _isMotorSpinning(motorIndex) + { + return (this._motorStopValue !== this._state[motorIndex]); + } + + _sendEscSpinDirection(motorIndex, direction) + { + const buffer = []; + buffer.push8(DshotCommand.dshotCommandType_e.DSHOT_CMD_TYPE_BLOCKING); + buffer.push8(motorIndex); + buffer.push8(2); // two commands + buffer.push8(direction); + buffer.push8(DshotCommand.dshotCommands_e.DSHOT_CMD_SAVE_SETTINGS); + this._EscDshotCommandQueue.pushCommand(MSPCodes.MSP2_SEND_DSHOT_COMMAND, buffer); + } + + _spinMotor(motorIndex, value) + { + if (DshotCommand.ALL_MOTORS === motorIndex) { + this._spinAllMotors(value); + } else { + this._state[motorIndex] = value; + this._sendState(); + } + } + + _spinAllMotors(value) + { + for (let i = 0; i < this._numberOfMotors; i++) { + this._state[i] = value; + } + + this._sendState(); + } + + _sendState() + { + const buffer = []; + + for (let i = 0; i < this._numberOfMotors; i++) { + buffer.push16(this._state[i]); + } + + this._EscDshotCommandQueue.pushCommand(MSPCodes.MSP_SET_MOTOR, buffer); + } + +} diff --git a/src/components/EscDshotDirection/Styles.css b/src/components/EscDshotDirection/Styles.css new file mode 100644 index 00000000..06130a0e --- /dev/null +++ b/src/components/EscDshotDirection/Styles.css @@ -0,0 +1,177 @@ +.escDshotDirection-Component { + display: flex; + height: 100%; + flex-flow: column; +} + +.escDshotDirection-ComponentHeader { + padding-bottom: 12px; +} + +#escDshotDirectionDialog-MainContent { + display: flex; + height: 100%; + flex-flow: column; +} + +#escDshotDirectionDialog-Warning { + display: flex; + height: 100%; + flex-flow: column; + border-top: 1px solid var(--superSubtleAccent); + padding-top: 16px; +} + +.escDshotDirectionToggleParentContainer { + display: flex; + margin-bottom: 1.5em; + margin-top: 1.5em; +} + +.escDshotDirectionToggleNarrow { + margin-right: 12px; + display: flex; + align-items: center; +} + +.escDshotDirectionDialog-ToggleWide { + flex: 1; +} + +.escDshotDirectionDialog-RiskNoticeText { + font-size: 1.2em; +} + +.escDshotDirectionDialogInformationNotice { + font-size: 1.0em; + margin-bottom: 1.5em; + margin-top: 1.5em; +} + +#escDshotDirectionDialog-MixerPreview { + width: 100%; + padding-top: 8px; + padding-bottom: 9px; + margin-bottom: 8px; +} + +#escDshotDirectionDialog-MixerPreviewImg { + display: block; + width: 160px; + height: 160px; + margin-left: auto; + margin-right: auto; + margin-top: auto; + margin-bottom: auto; +} + +#escDshotDirectionDialog-MainContent h4 { + margin-left: auto; + margin-right: auto; + font-weight: 500; +} + +#escDshotDirectionDialog-MainContent .red-text { + color: #EE0000; +} + +#escDshotDirectionDialog-ActionHint, #escDshotDirectionDialog-SecondHint, #escDshotDirectionDialog-WizardActionHint { + margin-top: 10px; +} + +#escDshotDirectionDialog-ActionHintSafety, #escDshotDirectionDialog-SecondHintSafety { + margin-top: 0px; +} + +#escDshotDirectionDialog-SelectMotorButtonsWrapper, #escDshotDirectionDialog-WizardMotorButtons { + margin-left: auto; + margin-right: auto; +} + +#escDshotDirectionDialog-SelectMotorButtonsWrapper .regular-button, #escDshotDirectionDialog-WizardMotorButtons .regular-button { + font-size: 15px; + line-height: 34px; + padding: 0px; + margin-left: 4px; + margin-right: 4px; + border-radius: 17px; + width: 34px; + height: 34px; + text-align: center; +} + +#escDshotDirectionDialog-NormalDialog .regular-button.pushed:hover { + background-color: #993333; +} + +#escDshotDirectionDialog-NormalDialog .regular-button:hover { + background-color: #993333; +} + +#escDshotDirectionDialog-MainContent .regular-button.highlighted { + background-color: #EE2222; +} + +#escDshotDirectionDialog-CommandsWrapper { + margin-left: auto; + margin-right: auto; +} + +#escDshotDirectionDialog-CommandSpin { + margin-left: auto; + margin-right: auto; + width: 224px; + display: block; + text-align: center; +} + +#escDshotDirectionDialog-CommandsWrapper .regular-button { + width: 130px; + text-align: center; + margin-left: 5px; + margin-right: 5px; +} + + +.escDshotDirectionErrorTextBlock { + margin-top: 12px; + font-weight: 500; +} + +.display-contents { + display: contents; +} + +#escDshotDirectionDialog-SpinWizard, #escDshotDirectionDialog-StopWizard { + margin-left: auto; + margin-right: auto; + width: 160px; + text-align: center; +} + +.escDshotDirectionDialog-InformationNotice { + margin-top: 18px; + padding-top: 16px; + padding-bottom: 16px; + border-top: 1px solid var(--superSubtleAccent); + border-bottom: 1px solid var(--superSubtleAccent); +} + +.escDshotDirectionDialog-StartButton { + width: 80px; + text-align: center; + margin-left: 0px; + margin-right: 16px; + margin-top: 0px; + margin-bottom: 0px; +} + +.escDshotDirectionDialog-Buttons { + float: left; + margin: 0px; +} + +.escDshotDirectionDialog-StartBlock { + display: flex; + margin-top: 16px; +} diff --git a/src/components/MotorOutputReordering/MotorOutputReorderingComponent.js b/src/components/MotorOutputReordering/MotorOutputReorderingComponent.js index 56f66409..fe7b147f 100644 --- a/src/components/MotorOutputReordering/MotorOutputReorderingComponent.js +++ b/src/components/MotorOutputReordering/MotorOutputReorderingComponent.js @@ -25,7 +25,7 @@ class MotorOutputReorderComponent _readDom() { this._domAgreeSafetyCheckBox = $('#motorsEnableTestMode-dialogMotorOutputReorder'); - this._domAgreeButton = $('#dialogMotorOutputReorderAgreeButton'); + this._domStartButton = $('#dialogMotorOutputReorderAgreeButton'); this._domStartOverButton = $('#motorsRemapDialogStartOver'); this._domSaveButton = $('#motorsRemapDialogSave'); this._domMainContentBlock = $('#dialogMotorOutputReorderMainContent'); @@ -44,12 +44,12 @@ class MotorOutputReorderComponent this._domAgreeSafetyCheckBox.change(() => { const enabled = this._domAgreeSafetyCheckBox.is(':checked'); - this._domAgreeButton.toggle(enabled); + this._domStartButton.toggle(enabled); }); - this._domAgreeButton.click(() => + this._domStartButton.click(() => { - this._onAgreeButtonClicked(); + this._onStartButtonClicked(); }); this._domStartOverButton.click(() => { @@ -75,7 +75,7 @@ class MotorOutputReorderComponent { this._domMainContentBlock.hide(); this._domWarningContentBlock.show(); - this._domAgreeButton.hide(); + this._domStartButton.hide(); this._domAgreeSafetyCheckBox.prop('checked', false); this._domAgreeSafetyCheckBox.change(); @@ -138,7 +138,7 @@ class MotorOutputReorderComponent } } - _onAgreeButtonClicked() + _onStartButtonClicked() { this._domActionHintBlock.text(i18n.getMessage("motorOutputReorderDialogSelectSpinningMotor")); this._domWarningContentBlock.hide(); diff --git a/src/components/MotorOutputReordering/Styles.css b/src/components/MotorOutputReordering/Styles.css index 7afacb0c..9a547d48 100644 --- a/src/components/MotorOutputReordering/Styles.css +++ b/src/components/MotorOutputReordering/Styles.css @@ -5,8 +5,6 @@ } .motorOutputReorderComponentHeader { - border-bottom: 1px solid var(--accent); - margin-bottom: 10px; padding-bottom: 12px; } diff --git a/src/css/dark-theme.css b/src/css/dark-theme.css index d382366b..1be8e01e 100644 --- a/src/css/dark-theme.css +++ b/src/css/dark-theme.css @@ -14,6 +14,10 @@ --gimbalBackground: var(--subtleAccent); --gimbalCrosshair: var(--mutedText); --switcherysecond: #858585; + --pushedButton-background: #616161; + --pushedButton-fontColor: #ffffff; + --hoverButton-background: #ffcc3e; + --superSubtleAccent: #595959; } .background_paper { @@ -24,6 +28,11 @@ body { color: white; } +::backdrop { + background-image: none; + background-color: rgba(0, 0, 0, 0.5); +} + #options-window { background-color: #393b3a; } @@ -219,7 +228,7 @@ button { } .tab-auxiliary .buttons a:hover { - background-color: #393b3a; + background-color: var(--hoverButton-background); } @@ -357,7 +366,7 @@ button { } .tab-gps #loadmap .controls a:hover { - background-color: #393b3a; + background-color: var(--hoverButton-background); } .tab-gps #loadmap .controls a:active { diff --git a/src/css/main.css b/src/css/main.css index 05e3bfbe..b8440d81 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -16,6 +16,10 @@ --gimbalBackground: #eee; --gimbalCrosshair: var(--subtleAccent); --switcherysecond: #c4c4c4; + --pushedButton-background: #c4c4c4; + --pushedButton-fontColor: #000000; + --hoverButton-background: #ffcc3e; + --superSubtleAccent: #CCCCCC; } * { @@ -40,6 +44,11 @@ body { overflow: hidden; } +::backdrop { + background-image: none; + background-color: rgba(1, 1, 1, 0.5); +} + a { text-decoration: none; color: var(--linkText); @@ -140,7 +149,11 @@ input[type="number"]::-webkit-inner-spin-button { font-weight: bold !important; } - +.message-negative-italic { + color: var(--error) !important; + font-weight: bold !important; + font-style: italic; +} /** Main wrapper **/ #main-wrapper { @@ -1471,7 +1484,7 @@ dialog .dialog_toolbar .btn a { } dialog .dialog_toolbar .btn a:hover { - background-color: #ffcc3e; + background-color: var(--hoverButton-background); transition: all ease 0.2s; } @@ -1567,7 +1580,7 @@ dialog .dialog_toolbar .btn a.disabled { } .content_toolbar .btn a:hover { - background-color: #ffcc3e; + background-color: var(--hoverButton-background); transition: all ease 0.2s; } @@ -1855,7 +1868,7 @@ dialog .dialog_toolbar .btn a.disabled { } .fixed_band .save_btn a:hover { - background-color: #ffcc3f; + background-color: var(--hoverButton-background); transition: all ease 0.2s; } @@ -1891,7 +1904,7 @@ dialog .dialog_toolbar .btn a.disabled { } .default_btn a:hover { - background-color: #ffcc3f; + background-color: var(--hoverButton-background); color: #000; text-shadow: 0 1px rgba(255, 255, 255, 0.25); transition: all ease 0.2s; @@ -1900,7 +1913,7 @@ dialog .dialog_toolbar .btn a.disabled { } .default_btn a:active { - background-color: #ffcc3f; + background-color: var(--hoverButton-background); transition: all ease 0.0s; box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.35); } @@ -1910,6 +1923,7 @@ dialog .dialog_toolbar .btn a.disabled { } .regular-button { + -webkit-user-drag: none; margin-top: 8px; margin-bottom: 8px; margin-right: 10px; @@ -1927,6 +1941,16 @@ dialog .dialog_toolbar .btn a.disabled { line-height: 28px; } +.regular-button:hover { + background-color: var(--hoverButton-background); +} + +.regular-button.pushed { + background-color: var(--pushedButton-background); + color: var(--pushedButton-fontColor); + border-radius: 3px; +} + .small { width: auto; position: relative; diff --git a/src/css/tabs/motors.css b/src/css/tabs/motors.css index 42831f57..1c04dda2 100644 --- a/src/css/tabs/motors.css +++ b/src/css/tabs/motors.css @@ -25,7 +25,7 @@ .tab-motors #dialogMotorOutputReorder { width: 400px; - height:440px + height: 440px ;} .tab-motors #dialogMotorOutputReorderContentWrapper { @@ -39,6 +39,31 @@ flex-grow: 1; } +.tab-motors #escDshotDirectionDialog-closebtn { + margin-right: 0px; + margin-bottom: 0px; + position: absolute; + right: 0px; + bottom: 0px; +} + +.tab-motors #escDshotDirectionDialog { + width: 400px; + height: 440px; +} + +.tab-motors #escDshotDirectionDialog-ContentWrapper { + display: flex; + flex-flow: column; + width: 100%; + height: 100%; + position: relative; +} + +.tab-motors #escDshotDirectionDialog-Content { + flex-grow: 1; +} + .tab-motors .mixerPreview img { width: 120px; height: 120px; @@ -457,4 +482,16 @@ margin: 0; height: auto; } + + .tab-motors #escDshotDirectionDialog- { + position: fixed; + width: calc(100% - 2em); + bottom: 0; + top: 56px; + border-radius: unset; + border: none; + overflow: auto; + margin: 0; + height: auto; + } } diff --git a/src/js/msp/MSPCodes.js b/src/js/msp/MSPCodes.js index bd32cfeb..46ea32a3 100644 --- a/src/js/msp/MSPCodes.js +++ b/src/js/msp/MSPCodes.js @@ -181,4 +181,5 @@ const MSPCodes = { MSP2_BETAFLIGHT_BIND: 0x3000, MSP2_MOTOR_OUTPUT_REORDERING: 0x3001, MSP2_SET_MOTOR_OUTPUT_REORDERING: 0x3002, + MSP2_SEND_DSHOT_COMMAND: 0x3003, }; diff --git a/src/js/msp/MSPHelper.js b/src/js/msp/MSPHelper.js index f8e00c15..a41f6f9f 100644 --- a/src/js/msp/MSPHelper.js +++ b/src/js/msp/MSPHelper.js @@ -57,26 +57,6 @@ function MspHelper() { self.mspMultipleCache = []; } -MspHelper.prototype.reorderPwmProtocols = function (protocol) { - let result = protocol; - if (semver.lt(FC.CONFIG.apiVersion, "1.26.0")) { - switch (protocol) { - case 5: - result = 7; - - break; - case 7: - result = 5; - - break; - default: - break; - } - } - - return result; -} - MspHelper.prototype.process_data = function(dataHandler) { const self = this; const data = dataHandler.dataView; // DataView (allowing us to view arrayBuffer as struct/union) @@ -1039,7 +1019,7 @@ MspHelper.prototype.process_data = function(dataHandler) { FC.PID_ADVANCED_CONFIG.gyro_sync_denom = data.readU8(); FC.PID_ADVANCED_CONFIG.pid_process_denom = data.readU8(); FC.PID_ADVANCED_CONFIG.use_unsyncedPwm = data.readU8(); - FC.PID_ADVANCED_CONFIG.fast_pwm_protocol = self.reorderPwmProtocols(data.readU8()); + FC.PID_ADVANCED_CONFIG.fast_pwm_protocol = EscProtocols.ReorderPwmProtocols(FC.CONFIG.apiVersion, data.readU8()); FC.PID_ADVANCED_CONFIG.motor_pwm_rate = data.readU16(); if (semver.gte(FC.CONFIG.apiVersion, "1.24.0")) { FC.PID_ADVANCED_CONFIG.digitalIdlePercent = data.readU16() / 100; @@ -1569,6 +1549,9 @@ MspHelper.prototype.process_data = function(dataHandler) { case MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING: console.log('Motor output reordering set'); break; + case MSPCodes.MSP2_SEND_DSHOT_COMMAND: + console.log('DSHOT command sent'); + break; case MSPCodes.MSP_MULTIPLE_MSP: @@ -1981,7 +1964,7 @@ MspHelper.prototype.crunch = function(code) { buffer.push8(FC.PID_ADVANCED_CONFIG.gyro_sync_denom) .push8(FC.PID_ADVANCED_CONFIG.pid_process_denom) .push8(FC.PID_ADVANCED_CONFIG.use_unsyncedPwm) - .push8(self.reorderPwmProtocols(FC.PID_ADVANCED_CONFIG.fast_pwm_protocol)) + .push8(EscProtocols.ReorderPwmProtocols(FC.CONFIG.apiVersion, FC.PID_ADVANCED_CONFIG.fast_pwm_protocol)) .push16(FC.PID_ADVANCED_CONFIG.motor_pwm_rate); if (semver.gte(FC.CONFIG.apiVersion, "1.24.0")) { buffer.push16(FC.PID_ADVANCED_CONFIG.digitalIdlePercent * 100); @@ -2278,6 +2261,10 @@ MspHelper.prototype.crunch = function(code) { break; + case MSPCodes.MSP2_SEND_DSHOT_COMMAND: + buffer.push8(1); + break; + default: return false; } diff --git a/src/js/tabs/configuration.js b/src/js/tabs/configuration.js index 7e8fbaf6..1b674ec6 100644 --- a/src/js/tabs/configuration.js +++ b/src/js/tabs/configuration.js @@ -213,14 +213,8 @@ TABS.configuration.initialize = function (callback, scrollPosition) { } function refreshMixerPreview() { - const mixer = FC.MIXER_CONFIG.mixer - let reverse = ""; - - if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) { - reverse = FC.MIXER_CONFIG.reverseMotorDir ? "_reversed" : ""; - } - - $('.mixerPreview img').attr('src', './resources/motor_order/' + mixerList[mixer - 1].image + reverse + '.svg'); + const imgSrc = CommonUtils.GetMixerImageSrc(FC.MIXER_CONFIG.mixer, FC.MIXER_CONFIG.reverseMotorDir, FC.CONFIG.apiVersion); + $('.mixerPreview img').attr('src', imgSrc); } const reverseMotorSwitch_e = $('#reverseMotorSwitch'); @@ -462,34 +456,7 @@ TABS.configuration.initialize = function (callback, scrollPosition) { } // ESC protocols - const escProtocols = [ - 'PWM', - 'ONESHOT125', - 'ONESHOT42', - 'MULTISHOT', - ]; - - if (semver.gte(FC.CONFIG.apiVersion, "1.20.0")) { - escProtocols.push('BRUSHED'); - } - - if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_31)) { - escProtocols.push('DSHOT150'); - escProtocols.push('DSHOT300'); - escProtocols.push('DSHOT600'); - - if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_42)) { - escProtocols.push('DSHOT1200'); - } - } - - if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) { - escProtocols.push('PROSHOT1000'); - } - - if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_43)) { - escProtocols.push('DISABLED'); - } + const escProtocols = EscProtocols.GetAvailableProtocols(FC.CONFIG.apiVersion); const esc_protocol_e = $('select.escprotocol'); diff --git a/src/js/tabs/motors.js b/src/js/tabs/motors.js index 4614b672..343c95aa 100644 --- a/src/js/tabs/motors.js +++ b/src/js/tabs/motors.js @@ -31,7 +31,8 @@ TABS.motors = { // These are translated into proper Dshot values on the flight controller DSHOT_DISARMED_VALUE: 1000, DSHOT_MAX_VALUE: 2000, - DSHOT_3D_NEUTRAL: 1500 + DSHOT_3D_NEUTRAL: 1500, + numberOfValidOutputs: -1, }; TABS.motors.initialize = function (callback) { @@ -105,7 +106,7 @@ TABS.motors.initialize = function (callback) { } function initDataArray(length) { - const data = new Array(length); + const data = Array.from({length: length}); for (let i = 0; i < length; i++) { data[i] = []; data[i].min = -1; @@ -126,8 +127,8 @@ TABS.motors.initialize = function (callback) { } } while (data[0].length > 300) { - for (let i = 0; i < data.length; i++) { - data[i].shift(); + for (const item of data) { + item.shift(); } } return sampleNumber + 1; @@ -220,13 +221,8 @@ TABS.motors.initialize = function (callback) { } function update_model(mixer) { - let reverse = ""; - - if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_36)) { - reverse = FC.MIXER_CONFIG.reverseMotorDir ? "_reversed" : ""; - } - - $('.mixerPreview img').attr('src', './resources/motor_order/' + mixerList[mixer - 1].image + reverse + '.svg'); + const imgSrc = CommonUtils.GetMixerImageSrc(mixer, FC.MIXER_CONFIG.reverseMotorDir, FC.CONFIG.apiVersion); + $('.mixerPreview img').attr('src', imgSrc); const motorOutputReorderConfig = new MotorOutputReorderConfig(100); const domMotorOutputReorderDialogOpen = $('#motorOutputReorderDialogOpen'); @@ -234,6 +230,8 @@ TABS.motors.initialize = function (callback) { const isMotorReorderingAvailable = (mixerList[mixer - 1].name in motorOutputReorderConfig) && (FC.MOTOR_OUTPUT_ORDER) && (FC.MOTOR_OUTPUT_ORDER.length > 0); domMotorOutputReorderDialogOpen.toggle(isMotorReorderingAvailable); + + self.escProtocolIsDshot = EscProtocols.IsProtocolDshot(FC.CONFIG.apiVersion, FC.PID_ADVANCED_CONFIG.fast_pwm_protocol); } function process_html() { @@ -244,12 +242,6 @@ TABS.motors.initialize = function (callback) { self.feature3DEnabled = FC.FEATURE_CONFIG.features.isEnabled('3D'); - if (FC.PID_ADVANCED_CONFIG.fast_pwm_protocol >= TABS.configuration.DSHOT_PROTOCOL_MIN_VALUE) { - self.escProtocolIsDshot = true; - } else { - self.escProtocolIsDshot = false; - } - $('#motorsEnableTestMode').prop('checked', false); if (semver.lt(FC.CONFIG.apiVersion, API_VERSION_1_42) || !(FC.MOTOR_CONFIG.use_dshot_telemetry || FC.MOTOR_CONFIG.use_esc_sensor)) { @@ -417,7 +409,7 @@ TABS.motors.initialize = function (callback) { function computeAndUpdate(sensor_data, data, max_read) { let sum = 0.0; for (let j = 0, jlength = data.length; j < jlength; j++) { - for (let k = 0, klength = data[j].length; k < klength; k++){ + for (let k = 0, klength = data[j].length; k < klength; k++) { sum += data[j][k][1]*data[j][k][1]; } } @@ -455,8 +447,8 @@ TABS.motors.initialize = function (callback) { motorVoltage.text(i18n.getMessage('motorsVoltageValue', [FC.ANALOG.voltage])); motor_mah_drawing_e.text(i18n.getMessage('motorsADrawingValue', [FC.ANALOG.amperage.toFixed(2)])); motor_mah_drawn_e.text(i18n.getMessage('motorsmAhDrawnValue', [FC.ANALOG.mAhdrawn])); - } + GUI.interval_add('motors_power_data_pull_slow', power_data_pull, 250, true); // 4 fps $('a.reset_max').click(function () { @@ -465,7 +457,7 @@ TABS.motors.initialize = function (callback) { accelOffsetEstablished = false; }); - const numberOfValidOutputs = (FC.MOTOR_DATA.indexOf(0) > -1) ? FC.MOTOR_DATA.indexOf(0) : 8; + self.numberOfValidOutputs = (FC.MOTOR_DATA.indexOf(0) > -1) ? FC.MOTOR_DATA.indexOf(0) : 8; let rangeMin; let rangeMax; let neutral3d; @@ -528,7 +520,7 @@ TABS.motors.initialize = function (callback) { function setSlidersEnabled(isEnabled) { if (isEnabled && !self.armed) { - $('div.sliders input').slice(0, numberOfValidOutputs).prop('disabled', false); + $('div.sliders input').slice(0, self.numberOfValidOutputs).prop('disabled', false); // unlock master slider $('div.sliders input:last').prop('disabled', false); @@ -585,14 +577,14 @@ TABS.motors.initialize = function (callback) { const val = $(this).val(); $('div.sliders input:not(:disabled, :last)').val(val); - $('div.values li:not(:last)').slice(0, numberOfValidOutputs).text(val); + $('div.values li:not(:last)').slice(0, self.numberOfValidOutputs).text(val); $('div.sliders input:not(:last):first').trigger('input'); }); // check if motors are already spinning let motorsRunning = false; - for (let i = 0; i < numberOfValidOutputs; i++) { + for (let i = 0; i < self.numberOfValidOutputs; i++) { if (!self.feature3DEnabled) { if (FC.MOTOR_DATA[i] > rangeMin) { motorsRunning = true; @@ -751,7 +743,11 @@ TABS.motors.initialize = function (callback) { zeroThrottleValue = neutral3d; } - setup_motor_output_reordering_dialog(content_ready, zeroThrottleValue); + setup_motor_output_reordering_dialog(SetupEscDshotDirectionDialogCallback, zeroThrottleValue); + + function SetupEscDshotDirectionDialogCallback() { + SetupdescDshotDirectionDialog(content_ready, zeroThrottleValue); + } function content_ready() { GUI.content_ready(callback); @@ -768,9 +764,9 @@ TABS.motors.initialize = function (callback) { callbackFunction, mixerList[FC.MIXER_CONFIG.mixer - 1].name, zeroThrottleValue, zeroThrottleValue + 200); - $('#dialogMotorOutputReorder-closebtn').click(closeDialog); + $('#dialogMotorOutputReorder-closebtn').click(closeDialogMotorOutputReorder); - function closeDialog() + function closeDialogMotorOutputReorder() { domDialogMotorOutputReorder[0].close(); motorOutputReorderComponent.close(); @@ -780,7 +776,7 @@ TABS.motors.initialize = function (callback) { function onDocumentKeyPress(event) { if (27 === event.which) { - closeDialog(); + closeDialogMotorOutputReorder(); } } @@ -790,6 +786,47 @@ TABS.motors.initialize = function (callback) { domDialogMotorOutputReorder[0].showModal(); }); } + + function SetupdescDshotDirectionDialog(callbackFunction, zeroThrottleValue) + { + const domEscDshotDirectionDialog = $('#escDshotDirectionDialog'); + + const idleThrottleValue = zeroThrottleValue + FC.PID_ADVANCED_CONFIG.digitalIdlePercent * 1000 / 100; + + const motorConfig = { + numberOfMotors: self.numberOfValidOutputs, + motorStopValue: zeroThrottleValue, + motorSpinValue: idleThrottleValue, + escProtocolIsDshot: self.escProtocolIsDshot, + }; + + const escDshotDirectionComponent = new EscDshotDirectionComponent( + $('#escDshotDirectionDialog-Content'), callbackFunction, motorConfig); + + $('#escDshotDirectionDialog-closebtn').on("click", closeEscDshotDirectionDialog); + + function closeEscDshotDirectionDialog() + { + domEscDshotDirectionDialog[0].close(); + escDshotDirectionComponent.close(); + $(document).off("keydown", onDocumentKeyPress); + } + + function onDocumentKeyPress(event) + { + if (27 === event.which) { + closeEscDshotDirectionDialog(); + } + } + + $('#escDshotDirectionDialog-Open').click(function() + { + $(document).on("keydown", onDocumentKeyPress); + domEscDshotDirectionDialog[0].showModal(); + }); + + callbackFunction(); + } }; TABS.motors.cleanup = function (callback) { diff --git a/src/js/utils/CommonUtils.js b/src/js/utils/CommonUtils.js new file mode 100644 index 00000000..b56e906f --- /dev/null +++ b/src/js/utils/CommonUtils.js @@ -0,0 +1,15 @@ +'use strict'; + +class CommonUtils +{ + static GetMixerImageSrc(mixerIndex, reverseMotorDir, apiVersion) + { + let reverse = ""; + + if (semver.gte(apiVersion, API_VERSION_1_36)) { + reverse = reverseMotorDir ? "_reversed" : ""; + } + + return `./resources/motor_order/${mixerList[mixerIndex - 1].image}${reverse}.svg`; + } +} diff --git a/src/js/utils/DshotCommand.js b/src/js/utils/DshotCommand.js new file mode 100644 index 00000000..76f3047c --- /dev/null +++ b/src/js/utils/DshotCommand.js @@ -0,0 +1,40 @@ +'use strict'; + +class DshotCommand +{ + static get ALL_MOTORS() { return 255; } +} + +DshotCommand.dshotCommands_e = { + DSHOT_CMD_MOTOR_STOP: 0, + DSHOT_CMD_BEACON1: 1, + DSHOT_CMD_BEACON2: 2, + DSHOT_CMD_BEACON3: 3, + DSHOT_CMD_BEACON4: 4, + DSHOT_CMD_BEACON5: 5, + DSHOT_CMD_ESC_INFO: 6, // V2 includes settings + DSHOT_CMD_SPIN_DIRECTION_1: 7, + DSHOT_CMD_SPIN_DIRECTION_2: 8, + DSHOT_CMD_3D_MODE_OFF: 9, + DSHOT_CMD_3D_MODE_ON: 10, + DSHOT_CMD_SETTINGS_REQUEST: 11, // Currently not implemented + DSHOT_CMD_SAVE_SETTINGS: 12, + DSHOT_CMD_SPIN_DIRECTION_NORMAL: 20, + DSHOT_CMD_SPIN_DIRECTION_REVERSED: 21, + DSHOT_CMD_LED0_ON: 22, // BLHeli32 only + DSHOT_CMD_LED1_ON: 23, // BLHeli32 only + DSHOT_CMD_LED2_ON: 24, // BLHeli32 only + DSHOT_CMD_LED3_ON: 25, // BLHeli32 only + DSHOT_CMD_LED0_OFF: 26, // BLHeli32 only + DSHOT_CMD_LED1_OFF: 27, // BLHeli32 only + DSHOT_CMD_LED2_OFF: 28, // BLHeli32 only + DSHOT_CMD_LED3_OFF: 29, // BLHeli32 only + DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF: 30, // KISS audio Stream mode on/Off + DSHOT_CMD_SILENT_MODE_ON_OFF: 31, // KISS silent Mode on/Off + DSHOT_CMD_MAX: 47, +}; + +DshotCommand.dshotCommandType_e = { + DSHOT_CMD_TYPE_INLINE: 0, // dshot commands sent inline with motor signal (motors must be enabled) + DSHOT_CMD_TYPE_BLOCKING: 1, // dshot commands sent in blocking method (motors must be disabled) +}; diff --git a/src/js/utils/EscProtocols.js b/src/js/utils/EscProtocols.js new file mode 100644 index 00000000..ef5688ff --- /dev/null +++ b/src/js/utils/EscProtocols.js @@ -0,0 +1,95 @@ +'use strict'; + +class EscProtocols +{ + static get PROTOCOL_PWM() { return "PWM"; } + static get PROTOCOL_ONESHOT125() { return "ONESHOT125"; } + static get PROTOCOL_ONESHOT42() { return "ONESHOT42"; } + static get PROTOCOL_MULTISHOT() { return "MULTISHOT"; } + static get PROTOCOL_BRUSHED() { return "BRUSHED"; } + static get PROTOCOL_DSHOT150() { return "DSHOT150"; } + static get PROTOCOL_DSHOT300() { return "DSHOT300"; } + static get PROTOCOL_DSHOT600() { return "DSHOT600"; } + static get PROTOCOL_DSHOT1200() { return "DSHOT1200"; } + static get PROTOCOL_PROSHOT1000() { return "PROSHOT1000"; } + static get PROTOCOL_DISABLED() { return "DISABLED"; } + + static get DSHOT_PROTOCOLS_SET() + { + return [ + EscProtocols.PROTOCOL_DSHOT150, + EscProtocols.PROTOCOL_DSHOT300, + EscProtocols.PROTOCOL_DSHOT600, + EscProtocols.PROTOCOL_DSHOT1200, + EscProtocols.PROTOCOL_PROSHOT1000, + ]; + } + + static GetProtocolName(apiVersion, protocolIndex) + { + const escProtocols = EscProtocols.GetAvailableProtocols(apiVersion); + return escProtocols[protocolIndex]; + } + + static IsProtocolDshot(apiVersion, protocolIndex) + { + const protocolName = EscProtocols.GetProtocolName(apiVersion, protocolIndex); + return EscProtocols.DSHOT_PROTOCOLS_SET.includes(protocolName); + } + + static GetAvailableProtocols(apiVersion) + { + const escProtocols = [ + EscProtocols.PROTOCOL_PWM, + EscProtocols.PROTOCOL_ONESHOT125, + EscProtocols.PROTOCOL_ONESHOT42, + EscProtocols.PROTOCOL_MULTISHOT, + ]; + + if (semver.gte(apiVersion, "1.20.0")) { + escProtocols.push(EscProtocols.PROTOCOL_BRUSHED); + } + + if (semver.gte(apiVersion, API_VERSION_1_31)) { + escProtocols.push(EscProtocols.PROTOCOL_DSHOT150); + escProtocols.push(EscProtocols.PROTOCOL_DSHOT300); + escProtocols.push(EscProtocols.PROTOCOL_DSHOT600); + + if (semver.lt(apiVersion, API_VERSION_1_42)) { + escProtocols.push(EscProtocols.PROTOCOL_DSHOT1200); + } + } + + if (semver.gte(apiVersion, API_VERSION_1_36)) { + escProtocols.push(EscProtocols.PROTOCOL_PROSHOT1000); + } + + if (semver.gte(apiVersion, API_VERSION_1_43)) { + escProtocols.push(EscProtocols.PROTOCOL_DISABLED); + } + + return escProtocols; + } + + static ReorderPwmProtocols(apiVersion, protocolIndex) + { + let result = protocolIndex; + + if (semver.lt(apiVersion, "1.26.0")) { + switch (protocolIndex) { + case 5: + result = 7; + break; + case 7: + result = 5; + break; + default: + break; + } + } + + return result; + } + + +} diff --git a/src/main.html b/src/main.html index ee42864c..929f98dc 100644 --- a/src/main.html +++ b/src/main.html @@ -42,6 +42,7 @@ + @@ -71,6 +72,7 @@ + @@ -140,6 +142,11 @@ + + + + + diff --git a/src/tabs/motors.html b/src/tabs/motors.html index db97ccbc..15414d3d 100644 --- a/src/tabs/motors.html +++ b/src/tabs/motors.html @@ -101,6 +101,7 @@
+ @@ -200,4 +201,13 @@
- + + +
+
+
+ +
+
+ +