1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-14 20:10:13 +03:00

Motor output reordering feature

Fixed Sonar warnings

renaming MOTOR_REMAP to MOTOR_OUTPUT_REORDER<ING>

Sonar warning fix

Code style fixes after the code review

moving styles to css from the motors tab dialog

Dialog size of Androind devices

Raneming MSP_<SET>_MOTOR_OUTPUT_REORDERING to MSP2

removing old styles and js files reference to motor_remap folder

adding FC.* where needed to accomodate new master changes

fixed alphabetical order for FC settings MOTOR_OUTPUT_REORDER

css fix for Android for motor reordering dialog
This commit is contained in:
Ivan Efimov 2020-06-28 03:54:39 -05:00 committed by Ivan Efimov
parent b596c5fc76
commit e4a85ccc2f
13 changed files with 930 additions and 3 deletions

View file

@ -2537,6 +2537,40 @@
"message": "<strong>I understand the risks</strong>, the propellers are removed - enable motor control and arming, and disable Runaway Takeoff Prevention."
},
"motorOutputReorderDialogClose": {
"message": "Cancel"
},
"motorOutputReorderDialogAgree": {
"message": "Start"
},
"motorsRemapDialogTitle": {
"message": "Reorder motors"
},
"motorOutputReorderDialogOpen": {
"message": "Reorder motors"
},
"motorOutputReorderDialogSelectSpinningMotor": {
"message": "Click on the spinning motor..."
},
"motorOutputReorderDialogRemapIsDone": {
"message": "Ready! Check the motors spinning order by clicking on the image."
},
"motorsRemapDialogUnderstandRisks": {
"message": "<strong>I understand the risks</strong>,<br />the propellers are removed."
},
"motorsRemapDialogRiskNotice": {
"message": "<strong>Safety notice</strong><br /><strong class=\"message-negative\">Remove all propellers to prevent injury!</strong><br />The motors will <strong>spin up!</strong>"
},
"motorsRemapDialogExplanations": {
"message": "<strong>Information notice</strong><br />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 <a href=\"https://github.com/betaflight/betaflight/wiki/Betaflight-resource-remapping\" target=\"_blank\" rel=\"noopener noreferrer\">Wiki page</a>."
},
"motorsRemapDialogSave": {
"message": "Save"
},
"motorsRemapDialogStartOver": {
"message": "Start over"
},
"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.<br />We recommend to only render graphs for sensors you are interested in while using reasonable update periods."
},

View file

@ -0,0 +1,30 @@
<div class="motorOutputReorderComponent">
<h3 i18n="motorsRemapDialogTitle" class="motorOutputReorderComponentHeader"></h3>
<div class="componentContent" id="dialogMotorOutputReorderMainContent">
<canvas id="motorOutputReorderCanvas"></canvas>
<div id="motorOutputReorderActionPanel">
<h4 id="motorOutputReorderActionHint"></h4>
</div>
<div id="motorOutputReorderSaveStartOverButtonsPanel">
<a href="#" id="motorsRemapDialogSave" class="regular-button left" i18n="motorsRemapDialogSave"></a>
<a href="#" id="motorsRemapDialogStartOver" class="regular-button left" i18n="motorsRemapDialogStartOver"></a>
</div>
</div>
<div class="componentContent" id="dialogMotorOutputReorderWarning">
<div class="notice">
<p class="motorsRemapDialogRiskNoticeText" i18n="motorsRemapDialogRiskNotice"></p>
<div class="motorsRemapToggleParentContainer">
<div class="motorsRemapToggleNarrow">
<input id="motorsEnableTestMode-dialogMotorOutputReorder" type="checkbox" class="toggle"/>
</div>
<div class="motorsRemapToggleWide">
<span class="motorsEnableTestMode motorsRemapDialogRiskNoticeText" i18n="motorsRemapDialogUnderstandRisks"></span>
</div>
</div>
<div class="motorsRemapDialogRExplanationText" i18n="motorsRemapDialogExplanations"></div>
</div>
<div class="buttons">
<a href="#" id="dialogMotorOutputReorderAgreeButton" class="regular-button" i18n="motorOutputReorderDialogAgree"></a>
</div>
</div>
</div>

View file

@ -0,0 +1,269 @@
'use strict';
class MotorOutputReorderCanvas
{
constructor(canvas, droneConfiguration, motorClickCallback, spinMotorCallback)
{
this._spinMotorCallback = spinMotorCallback;
this._canvas = canvas;
this._motorClickCallback = motorClickCallback;
this._width = this._canvas.width();
this._height = this._canvas.height();
this._screenSize = Math.min(this._width, this._height);
this._config = new MotorOutputReorderConfig(this._screenSize);
// no component resize allowing yet
this._canvas.prop({
width: this._width,
height: this._height,
});
this._droneConfiguration = droneConfiguration;
this._ctx = this._canvas[0].getContext("2d");
this._ctx.translate(this._width / 2, this._height / 2);
this._canvas.mousemove((event) =>
{
this._onMouseMove(event);
});
this._canvas.mouseleave(() =>
{
this._onMouseLeave();
});
this._canvas.mousedown(() =>
{
this._onMouseDown();
});
this._canvas.mouseup(() =>
{
this._onMouseUp(event);
});
this._canvas.click(() =>
{
this._onMouseClick();
});
this.startOver();
}
pause()
{
this._keepDrawing = false;
}
startOver()
{
this.readyMotors = []; //motors that already being selected for remapping by user
this.remappingReady = false;
this._motorIndexToSpinOnMouseDown = -1;
this._keepDrawing = true;
this._mouse = {x : 0, y: 0};
window.requestAnimationFrame(() =>
{
this._drawOnce();
});
}
_drawOnce()
{
this._ctx.clearRect(-this._width / 2, -this._height / 2, this._width, this._height);
this._drawFrame();
this._drawDirectionArrow();
this._markMotors();
this._drawMotors();
if (this._keepDrawing) {
window.requestAnimationFrame(() =>
{
this._drawOnce();
});
}
}
_onMouseDown()
{
if (this.remappingReady) {
this._motorIndexToSpinOnMouseDown = this._getMouseHoverMotorIndex();
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_onMouseUp()
{
if (-1 !== this._motorIndexToSpinOnMouseDown) {
this._motorIndexToSpinOnMouseDown = -1;
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_onMouseClick()
{
const motorIndex = this._getMouseHoverMotorIndex();
if (this._motorClickCallback && -1 !== motorIndex && !this.readyMotors.includes(motorIndex)) {
this._motorClickCallback(motorIndex);
}
}
_onMouseMove(event)
{
const boundingRect = this._canvas[0].getBoundingClientRect();
this._mouse.x = event.clientX - boundingRect.left - this._width / 2;
this._mouse.y = event.clientY - boundingRect.top - this._height / 2;
}
_onMouseLeave()
{
this._mouse.x = Number.MIN_SAFE_INTEGER;
this._mouse.y = Number.MIN_SAFE_INTEGER;
if (-1 !== this._motorIndexToSpinOnMouseDown) {
this._motorIndexToSpinOnMouseDown = -1;
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_markMotors()
{
const motors = this._config[this._droneConfiguration].Motors;
const mouseHoverMotorIndex = this._getMouseHoverMotorIndex();
if (-1 === this._motorIndexToSpinOnMouseDown) {
for (let i = 0; i < this.readyMotors.length; i++) {
const motorIndex = this.readyMotors[i];
this._ctx.beginPath();
this._ctx.arc(motors[motorIndex].x, motors[motorIndex].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fillStyle = this._config.MotorReadyColor;
this._ctx.fill();
}
if (-1 !== mouseHoverMotorIndex && !this.readyMotors.includes(mouseHoverMotorIndex)) {
this._ctx.beginPath();
this._ctx.arc(motors[mouseHoverMotorIndex].x, motors[mouseHoverMotorIndex].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fillStyle = this._config.MotorMouseHoverColor;
this._ctx.fill();
}
} else {
const spinningMotor = this._motorIndexToSpinOnMouseDown;
for (let i = 0; i < motors.length; i++) {
this._ctx.fillStyle = this._config.MotorReadyColor;
if (spinningMotor === i) {
this._ctx.fillStyle = this._config.MotorSpinningColor;
} else if (mouseHoverMotorIndex === i) {
this._ctx.fillStyle = this._config.MotorMouseHoverColor;
}
this._ctx.beginPath();
this._ctx.arc(motors[i].x, motors[i].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fill();
}
}
}
_getMouseHoverMotorIndex()
{
const x = this._mouse.x;
const y = this._mouse.y;
let result = -1;
let currentDist = Number.MAX_SAFE_INTEGER;
const motors = this._config[this._droneConfiguration].Motors;
for (let i = 0; i < motors.length; i++) {
const dist = Math.sqrt((x - motors[i].x) * (x - motors[i].x) + (y - motors[i].y) * (y - motors[i].y));
if (dist < this._config[this._droneConfiguration].PropRadius && dist < currentDist) {
currentDist = dist;
result = i;
}
}
return result;
}
_drawMotors()
{
this._ctx.lineWidth = this._config.PropEdgeLineWidth;
this._ctx.strokeStyle = this._config.PropEdgeColor;
const motors = this._config[this._droneConfiguration].Motors;
for (let i = 0; i < motors.length; i++) {
this._ctx.beginPath();
this._ctx.arc(motors[i].x, motors[i].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.stroke();
}
}
_drawDirectionArrow()
{
this._ctx.beginPath();
this._ctx.moveTo(this._config.DirectionArrowPoints[0].x, this._config.DirectionArrowPoints[0].y);
for (let i = 1; i < this._config.DirectionArrowPoints.length; i++) {
this._ctx.lineTo(this._config.DirectionArrowPoints[i].x, this._config.DirectionArrowPoints[i].y);
}
this._ctx.closePath();
this._ctx.fillStyle = this._config.ArrowColor;
this._ctx.fill();
}
_drawFrame()
{
this._ctx.beginPath();
this._ctx.lineWidth = this._config[this._droneConfiguration].ArmWidth;
this._ctx.lineCap = "round";
this._ctx.strokeStyle = this._config.FrameColor;
const motors = this._config[this._droneConfiguration].Motors;
switch(this._droneConfiguration) {
case "Quad X":
case "Quad +":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[3].x, motors[3].y);
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
break;
case "Quad X 1234":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[3].x, motors[3].y);
this._ctx.lineTo(motors[1].x, motors[1].y);
break;
case "Tricopter":
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[0].x, motors[2].y);
break;
case "Hex +":
case "Hex X":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[3].x, motors[3].y);
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[4].x, motors[4].y);
this._ctx.lineTo(motors[5].x, motors[5].y);
break;
}
this._ctx.stroke();
}
}

View file

@ -0,0 +1,258 @@
'use strict';
class MotorOutputReorderComponent
{
constructor(contentDiv, onLoadedCallback, droneConfiguration, motorStopValue, motorSpinValue)
{
this._contentDiv = contentDiv;
this._onLoadedCallback = onLoadedCallback;
this._droneConfiguration = droneConfiguration;
this._motorStopValue = motorStopValue;
this._motorSpinValue = motorSpinValue;
this._config = new MotorOutputReorderConfig(100);
this._currentJerkingTimeout = -1;
this._currentJerkingMotor = -1;
this._currentSpinningMotor = -1;
this._contentDiv.load("./Components/MotorOutputReordering/Body.html", () =>
{
this._setupdialog();
});
}
_readDom()
{
this._domAgreeSafetyCheckBox = $('#motorsEnableTestMode-dialogMotorOutputReorder');
this._domAgreeButton = $('#dialogMotorOutputReorderAgreeButton');
this._domStartOverButton = $('#motorsRemapDialogStartOver');
this._domSaveButton = $('#motorsRemapDialogSave');
this._domMainContentBlock = $('#dialogMotorOutputReorderMainContent');
this._domWarningContentBlock = $('#dialogMotorOutputReorderWarning');
this._domActionHintBlock = $('#motorOutputReorderActionHint');
this._domCanvas = $('#motorOutputReorderCanvas');
}
_setupdialog()
{
i18n.localizePage();
this._readDom();
this._resetGui();
this._domAgreeSafetyCheckBox.change(() =>
{
const enabled = this._domAgreeSafetyCheckBox.is(':checked');
this._domAgreeButton.toggle(enabled);
});
this._domAgreeButton.click(() =>
{
this._onAgreeButtonClicked();
});
this._domStartOverButton.click(() =>
{
this._startOver();
});
this._domSaveButton.click(() =>
{
this._save();
});
this._onLoadedCallback();
}
close()
{
this._stopAnyMotorJerking();
this._stopMotor();
this._stopUserInteraction();
this._resetGui();
}
_resetGui()
{
this._domMainContentBlock.hide();
this._domWarningContentBlock.show();
this._domAgreeButton.hide();
this._domAgreeSafetyCheckBox.prop('checked', false);
this._domAgreeSafetyCheckBox.change();
this._showSaveStartOverButtons(false);
}
_save()
{
function save_to_eeprom()
{
MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, reboot);
}
function reboot()
{
GUI.log(i18n.getMessage('configurationEepromSaved'));
GUI.tab_switch_cleanup(function()
{
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
reinitialiseConnection(self);
});
}
FC.MOTOR_OUTPUT_ORDER = Array.from(this._newMotorOutputReorder);
MSP.send_message(MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING, mspHelper.crunch(MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING));
save_to_eeprom();
}
_calculateNewMotorOutputReorder()
{
this._newMotorOutputReorder = [];
for (let i = 0; i < this.motorOutputReorderCanvas.readyMotors.length; i++) {
this._newMotorOutputReorder.push(this._remapMotorIndex(i));
}
}
_remapMotorIndex(motorIndex)
{
return FC.MOTOR_OUTPUT_ORDER[this.motorOutputReorderCanvas.readyMotors.indexOf(motorIndex)];
}
_startOver()
{
this._showSaveStartOverButtons(false);
this._startUserInteraction();
}
_showSaveStartOverButtons(show)
{
if (show) {
this._domStartOverButton.show();
this._domSaveButton.show();
} else {
this._domStartOverButton.hide();
this._domSaveButton.hide();
}
}
_onAgreeButtonClicked()
{
this._domActionHintBlock.text(i18n.getMessage("motorOutputReorderDialogSelectSpinningMotor"));
this._domWarningContentBlock.hide();
this._domMainContentBlock.show();
this._startUserInteraction();
}
_stopUserInteraction()
{
if (this.motorOutputReorderCanvas) {
this.motorOutputReorderCanvas.pause();
}
}
_startUserInteraction()
{
if (this.motorOutputReorderCanvas) {
this.motorOutputReorderCanvas.startOver();
} else {
this.motorOutputReorderCanvas = new MotorOutputReorderCanvas(this._domCanvas,
this._droneConfiguration,
(motorIndex) =>
{ // motor click callback
this._onMotorClick(motorIndex);
},
(motorIndex) =>
{ // motor spin callback
let indexToSpin = -1;
if (-1 !== motorIndex) {
indexToSpin = this.motorOutputReorderCanvas.readyMotors.indexOf(motorIndex);
}
this._spinMotor(indexToSpin);
},
);
}
this._startMotorJerking(0);
}
_stopAnyMotorJerking()
{
if (-1 !== this._currentJerkingTimeout) {
clearTimeout(this._currentJerkingTimeout);
this._currentJerkingTimeout = -1;
this._spinMotor(-1);
}
this._currentJerkingMotor = -1;
}
_startMotorJerking(motorIndex)
{
this._stopAnyMotorJerking();
this._currentJerkingMotor = motorIndex;
this._motorStartTimeout(motorIndex);
}
_motorStartTimeout(motorIndex)
{
this._spinMotor(motorIndex);
this._currentJerkingTimeout = setTimeout(() =>
{
this._motorStopTimeout(motorIndex);
}, 250);
}
_motorStopTimeout(motorIndex)
{
this._spinMotor(-1);
this._currentJerkingTimeout = setTimeout(() =>
{
this._motorStartTimeout(motorIndex);
}, 500);
}
_spinMotor(motorIndex)
{
this._currentSpinningMotor = motorIndex;
const buffer = [];
for (let i = 0; i < this._config[this._droneConfiguration].Motors.length; i++) {
if (i === motorIndex) {
buffer.push16(this._motorSpinValue);
} else {
buffer.push16(this._motorStopValue);
}
}
MSP.send_message(MSPCodes.MSP_SET_MOTOR, buffer);
}
_stopMotor()
{
if (-1 !== this._currentSpinningMotor) {
this._spinMotor(-1);
}
}
_onMotorClick(motorIndex)
{
this.motorOutputReorderCanvas.readyMotors.push(motorIndex);
this._currentJerkingMotor ++;
if (this._currentJerkingMotor < this._config[this._droneConfiguration].Motors.length) {
this._startMotorJerking(this._currentJerkingMotor);
} else {
this._stopAnyMotorJerking();
this._domActionHintBlock.text(i18n.getMessage("motorOutputReorderDialogRemapIsDone"));
this._calculateNewMotorOutputReorder();
this.motorOutputReorderCanvas.remappingReady = true;
this._showSaveStartOverButtons(true);
}
}
}

View file

@ -0,0 +1,140 @@
'use strict';
function MotorOutputReorderConfig(screenSize)
{
this.FrameColor = 'rgb(186, 186, 186)';
this.PropEdgeColor = 'rgb(255, 187, 0)';
this.PropEdgeLineWidth = 3;
this.MotorNumberTextFont = `${screenSize * 0.1}px 'Open Sans', 'Segoe UI', Tahoma, sans-serif`;
this.MotorNumberTextColor = 'rgb(0, 0, 0)';
this.MotorMouseHoverColor = 'rgba(255, 187, 0, 0.4)';
this.MotorSpinningColor = 'rgba(255, 0, 0, 0.4)';
this.MotorReadyColor = 'rgba(0,128,0,0.4)';
this.ArrowColor = 'rgb(182,67,67)';
this.DirectionArrowPoints = [
{x: -0.03 * screenSize, y: 0.11 * screenSize},
{x: -0.03 * screenSize, y: -0.01 * screenSize},
{x: -0.07 * screenSize, y: -0.01 * screenSize},
{x: 0.0 * screenSize, y: -0.13 * screenSize},
{x: 0.07 * screenSize, y: -0.01 * screenSize},
{x: 0.03 * screenSize, y: -0.01 * screenSize},
{x: 0.03 * screenSize, y: 0.11 * screenSize},
];
//===========================================
let frameRaduis = 0.28 * screenSize;
this["Quad X"] =
{
PropRadius: 0.2 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: frameRaduis, y: frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: -frameRaduis, y: frameRaduis},
{x: -frameRaduis, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.28 * screenSize;
this["Quad X 1234"] =
{
PropRadius: 0.2 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: -frameRaduis, y: -frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: frameRaduis, y: frameRaduis},
{x: -frameRaduis, y: frameRaduis},
],
};
//===========================================
frameRaduis = 0.32 * screenSize;
this["Quad +"] =
{
PropRadius: 0.15 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: 0, y: frameRaduis},
{x: frameRaduis, y: 0 },
{x: -frameRaduis, y: 0 },
{x: 0, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.30 * screenSize;
this["Tricopter"] =
{
PropRadius: 0.15 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: 0, y: frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: -frameRaduis, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.35 * screenSize;
this["Hex +"] =
{
PropRadius: 0.14 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors: [],
};
let dAngle = Math.PI / 3;
let angle = 0;
angle = dAngle * 1;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 2;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = -dAngle * 1;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = -dAngle * 2;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 3;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 0;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
//===========================================
frameRaduis = 0.35 * screenSize;
this["Hex X"] =
{
PropRadius: 0.14 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors: [],
};
dAngle = Math.PI / 3;
angle = dAngle * 1;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = -dAngle * 1;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 2;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = -dAngle * 2;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 0;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 3;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
}

View file

@ -0,0 +1,72 @@
.motorOutputReorderComponent {
display:flex;
height:100%;
flex-flow:column;
}
.motorOutputReorderComponentHeader {
border-bottom: 1px solid var(--accent);
margin-bottom: 10px;
padding-bottom: 12px;
}
#dialogMotorOutputReorderMainContent {
display:flex;
height:100%;
flex-flow:column;
}
#dialogMotorOutputReorderWarning {
display:flex;
height:100%;
flex-flow:column;
}
#motorOutputReorderCanvas {
width:100%;
flex-grow: 1;
}
#motorOutputReorderActionPanel {
height: 46px;
}
#dialogMotorOutputReorderSave {
margin-right: 0px;
}
.motorsRemapToggleParentContainer {
display: flex;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
#motorOutputReorderActionHint {
margin-top: 1.0em;
display: inline-block;
}
.motorsRemapToggleNarrow {
margin-right: 12px;
display: flex;
align-items: center;
}
.motorsRemapToggleWide {
flex: 1;
}
.motorsRemapDialogRiskNoticeText {
font-size: 1.2em;
}
.motorsRemapDialogRExplanationText {
font-size: 1.0em;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
#motorOutputReorderSaveStartOverButtonsPanel {
position: absolute;
bottom: 4px;
}

View file

@ -18,6 +18,27 @@
float: left;
}
.tab-motors #dialogMotorOutputReorder-closebtn {
margin-right: 0px;
margin-bottom: 0px;
}
.tab-motors #dialogMotorOutputReorder {
width: 400px;
height:440px
;}
.tab-motors #dialogMotorOutputReorderContentWrapper {
display: flex;
flex-flow: column;
width: 100%;
height: 100%;
}
.tab-motors #dialogMotorOutputReorderContent {
flex-grow: 1;
}
.tab-motors .mixerPreview img {
width: 120px;
height: 120px;
@ -424,4 +445,15 @@
.tab-motors .motor_testing .telemetry li {
font-size: 6px;
}
.tab-motors #dialogMotorOutputReorder {
position: fixed;
width: calc(100% - 2em);
bottom: 0;
top: 56px;
border-radius: unset;
border: none;
overflow: auto;
margin: 0;
height: auto;
}
}

View file

@ -41,6 +41,7 @@ const FC = {
MOTOR_3D_CONFIG: null,
MOTOR_CONFIG: null,
MOTOR_DATA: null,
MOTOR_OUTPUT_ORDER: null,
MOTOR_TELEMETRY_DATA: null,
MULTIPLE_MSP: null,
PID: null,
@ -605,6 +606,8 @@ const FC = {
vtxtable_powerlevel_label: 0,
};
this.MOTOR_OUTPUT_ORDER = [];
this.MULTIPLE_MSP = {
msp_commands: [],
};

View file

@ -179,4 +179,6 @@ var MSPCodes = {
// MSPv2 Betaflight specific
MSP2_BETAFLIGHT_BIND: 0x3000,
MSP2_MOTOR_OUTPUT_REORDERING: 0x3001,
MSP2_SET_MOTOR_OUTPUT_REORDERING: 0x3002,
};

View file

@ -147,6 +147,13 @@ MspHelper.prototype.process_data = function(dataHandler) {
FC.MOTOR_DATA[i] = data.readU16();
}
break;
case MSPCodes.MSP2_MOTOR_OUTPUT_REORDERING:
FC.MOTOR_OUTPUT_ORDER = [];
const arraySize = data.read8();
for (let i = 0; i < arraySize; i++) {
FC.MOTOR_OUTPUT_ORDER[i] = data.readU8();
}
break;
case MSPCodes.MSP_MOTOR_TELEMETRY:
var telemMotorCount = data.readU8();
for (let i = 0; i < telemMotorCount; i++) {
@ -1549,6 +1556,9 @@ MspHelper.prototype.process_data = function(dataHandler) {
case MSPCodes.MSP_SET_RTC:
console.log('Real time clock set');
break;
case MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING:
console.log('Motor output reordering set');
break;
case MSPCodes.MSP_MULTIPLE_MSP:
@ -2249,6 +2259,15 @@ MspHelper.prototype.crunch = function(code) {
break;
case MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING:
buffer.push8(FC.MOTOR_OUTPUT_ORDER.length);
for (let i = 0; i < FC.MOTOR_OUTPUT_ORDER.length; i++) {
buffer.push8(FC.MOTOR_OUTPUT_ORDER[i]);
}
break;
default:
return false;
}

View file

@ -57,7 +57,11 @@ TABS.motors.initialize = function (callback) {
}
function load_esc_protocol() {
MSP.send_message(MSPCodes.MSP_ADVANCED_CONFIG, false, false, load_motor_data);
MSP.send_message(MSPCodes.MSP_ADVANCED_CONFIG, false, false, load_motor_output_reordering);
}
function load_motor_output_reordering() {
MSP.send_message(MSPCodes.MSP2_MOTOR_OUTPUT_REORDERING, false, false, load_motor_data);
}
function load_motor_data() {
@ -222,6 +226,13 @@ TABS.motors.initialize = function (callback) {
}
$('.mixerPreview img').attr('src', './resources/motor_order/' + mixerList[mixer - 1].image + reverse + '.svg');
const motorOutputReorderConfig = new MotorOutputReorderConfig(100);
const domMotorOutputReorderDialogOpen = $('#motorOutputReorderDialogOpen');
const isMotorReorderingAvailable = (mixerList[mixer - 1].name in motorOutputReorderConfig)
&& (FC.MOTOR_OUTPUT_ORDER) && (FC.MOTOR_OUTPUT_ORDER.length > 0);
domMotorOutputReorderDialogOpen.toggle(isMotorReorderingAvailable);
}
function process_html() {
@ -726,8 +737,51 @@ TABS.motors.initialize = function (callback) {
// enable Status and Motor data pulling
GUI.interval_add('motor_and_status_pull', get_status, 50, true);
let zeroThrottleValue = rangeMin;
if (self.feature3DEnabled) {
zeroThrottleValue = neutral3d;
}
setup_motor_output_reordering_dialog(content_ready, zeroThrottleValue);
function content_ready() {
GUI.content_ready(callback);
}
GUI.content_ready(callback);
}
function setup_motor_output_reordering_dialog(callbackFunction, zeroThrottleValue)
{
const domDialogMotorOutputReorder = $('#dialogMotorOutputReorder');
const motorOutputReorderComponent = new MotorOutputReorderComponent($('#dialogMotorOutputReorderContent'),
callbackFunction, mixerList[FC.MIXER_CONFIG.mixer - 1].name,
zeroThrottleValue, zeroThrottleValue + 200);
$('#dialogMotorOutputReorder-closebtn').click(closeDialog);
function closeDialog()
{
domDialogMotorOutputReorder[0].close();
motorOutputReorderComponent.close();
$(document).off("keydown", onDocumentKeyPress);
}
function onDocumentKeyPress(event)
{
if (27 === event.which) {
closeDialog();
}
}
$('#motorOutputReorderDialogOpen').click(function()
{
$(document).on("keydown", onDocumentKeyPress);
domDialogMotorOutputReorder[0].showModal();
});
}
};
TABS.motors.cleanup = function (callback) {

View file

@ -38,6 +38,7 @@
<link type="text/css" rel="stylesheet" href="./css/dropdown-lists/css/style_lists.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./js/libraries/switchery/switchery.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./node_modules/@fortawesome/fontawesome-free/css/all.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./Components/MotorOutputReordering/Styles.css" media="all"/>
<link type="text/css" rel="stylesheet" href="./css/dark-theme.css" media="all" disabled/>
@ -135,6 +136,9 @@
<script type="text/javascript" src="./js/TuningSliders.js"></script>
<script type="text/javascript" src="./js/phones_ui.js"></script>
<script type="text/javascript" src="./node_modules/jquery-touchswipe/jquery.touchSwipe.min.js"></script>
<script type="text/javascript" src="./Components/MotorOutputReordering/MotorOutputReorderingComponent.js"></script>
<script type="text/javascript" src="./Components/MotorOutputReordering/MotorOutputReorderingCanvas.js"></script>
<script type="text/javascript" src="./Components/MotorOutputReordering/MotorOutputReorderingConfig.js"></script>
<title i18n="windowTitle"></title>
</head>
<body>

View file

@ -99,8 +99,8 @@
</div>
</div>
</div>
<div class="power_info">
<a href="#" id="motorOutputReorderDialogOpen" class="regular-button" i18n="motorOutputReorderDialogOpen"></a>
<span i18n="motorsVoltage" class="power_text"></span><span class="motors-bat-voltage power_value"></span>
<span i18n="motorsADrawing" class="power_text"></span><span class="motors-bat-mah-drawing power_value"></span>
<span i18n="motorsmAhDrawn" class="power_text"></span><span class="motors-bat-mah-drawn power_value"></span>
@ -190,4 +190,14 @@
</div>
<div class="clear-both"></div>
</div>
<dialog id="dialogMotorOutputReorder">
<div id="dialogMotorOutputReorderContentWrapper">
<div id="dialogMotorOutputReorderContent">
</div>
<div>
<a href="#" id="dialogMotorOutputReorder-closebtn" class="regular-button right" i18n="motorOutputReorderDialogClose"></a>
</div>
</div>
</dialog>
</div>