Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/msp/MSPCodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ var MSPCodes = {
MSP2_INAV_LOGIC_CONDITIONS_SINGLE: 0x203B,
MSP2_INAV_LOGIC_CONDITIONS_CONFIGURED: 0x203C,

MSP2_INAV_MOTOR_LOCATE: 0x2042,

MSP2_INAV_LED_STRIP_CONFIG_EX: 0x2048,
MSP2_INAV_SET_LED_STRIP_CONFIG_EX: 0x2049,

Expand Down
10 changes: 9 additions & 1 deletion js/msp/MSPHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1620,7 +1620,11 @@ var mspHelper = (function () {
case MSPCodes.MSP2_INAV_GPS_UBLOX_COMMAND:
// Just and ACK from the fc.
break;


case MSPCodes.MSP2_INAV_MOTOR_LOCATE:
// Response is single byte: 1=success, 0=failure
break;
Comment on lines +1624 to +1626
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In the MSP2_INAV_MOTOR_LOCATE response handler, parse the success/failure byte from the response and invoke the provided callback with the result. [possible issue, importance: 8]

Suggested change
case MSPCodes.MSP2_INAV_MOTOR_LOCATE:
// Response is single byte: 1=success, 0=failure
break;
case MSPCodes.MSP2_INAV_MOTOR_LOCATE:
// Response is single byte: 1=success, 0=failure
const success = new Uint8Array(data.buffer)[0] === 1;
// Call the callback passed to sendMotorLocate
if (typeof callback === 'function') {
callback(success);
}
break;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly needed for current functionality. The wizard uses a "fire and forget" approach - if a motor doesn't twitch, the user simply won't click on it and can restart. All current callers pass empty callbacks function() {}. Parsing the response could be useful for future error handling but isn't required for the wizard to work correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

Not strictly needed for current functionality. The wizard uses a fire and forget approach - if a motor doesn't twitch, the user simply won't click on it and can restart. All current callers pass empty callbacks function() {}. Parsing the response could be useful for future error handling but isn't required for the wizard to work correctly.

Answer:

To reduce suggestions like “parse response byte and call callback” when the current product behavior is intentionally fire-and-forget (and callbacks are no-ops), add a repo-level constraint to the /improve prompt so the model avoids “future-proofing” changes unless they are required for correctness.

Add to .pr_agent.toml (or wiki config):

[pr_code_suggestions]
extra_instructions="""\
- Prefer changes required for current, user-visible correctness/bugs.
- Avoid “future error handling”, “nice-to-have”, or speculative refactors unless they prevent a real bug.
- If a callback/result is currently unused or callers pass no-op callbacks, do not suggest wiring/parsing return values unless it fixes an actual failure mode or is explicitly requested in the PR description/ticket.
- Treat fire-and-forget workflows as acceptable; don't propose adding response parsing/propagation unless there is evidence of missing error handling causing issues.
"""

Optional noise-reduction if you still see many “not strictly needed” items:

[pr_code_suggestions]
suggestions_score_threshold = 7  # use carefully; higher values may hide useful findings

Relevant Sources:


case MSPCodes.MSP2_INAV_GEOZONE:

if (data.buffer.byteLength == 0) {
Expand Down Expand Up @@ -3613,6 +3617,10 @@ var mspHelper = (function () {
MSP.send_message(MSPCodes.MSP2_INAV_GPS_UBLOX_COMMAND, ubloxData, false, callback);
};

self.sendMotorLocate = function (motorIndex, callback) {
MSP.send_message(MSPCodes.MSP2_INAV_MOTOR_LOCATE, [motorIndex], false, callback);
};

return self;
})();

Expand Down
33 changes: 33 additions & 0 deletions locale/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5006,6 +5006,39 @@
"mixerWizardMotorIndex": {
"message": "Motor index"
},
"mixerWizardLocateInfo": {
"message": "Click a motor to identify it"
},
"mixerWizardLocateHint": {
"message": "Click to make this motor beep and twitch"
},
"mixerWizardIntro": {
"message": "This wizard will help you identify which motor is connected to each output. The wizard will activate each motor in turn - click where you see movement."
},
"mixerWizardStep1": {
"message": "Remove all propellers for safety"
},
"mixerWizardStep2": {
"message": "Connect a battery to power the ESCs"
},
"mixerWizardStep3": {
"message": "Make sure you can see/hear all motors"
},
"mixerWizardLocating": {
"message": "Motor"
},
"mixerWizardClickPosition": {
"message": "Click where you see this motor moving."
},
"mixerWizardComplete": {
"message": "All motors identified! Click Apply to save the motor mapping."
},
"mixerWizardStart": {
"message": "Start Wizard"
},
"mixerWizardStop": {
"message": "Emergency Stop"
},
"settings": {
"message": "Settings"
},
Expand Down
16 changes: 16 additions & 0 deletions src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,22 @@ select {
display: inline;
}

.modal__button--warning {
background-color: #e74c3c;
text-shadow: 0 1px rgba(0, 0, 0, 0.25);
color: #fff;
border: 1px solid #c0392b;
transition: all ease 0.2s;
}

.modal__button--warning:hover {
background-color: #c0392b;
}

.modal__button--warning:active {
box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.35);
}

.modal__button--disabled {
cursor: default;
color: #fff;
Expand Down
144 changes: 144 additions & 0 deletions src/css/tabs/mixer.css
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,147 @@
.mixer-preview-image-numbers {
width: fit-content;
}

/* Motor Wizard Styles */

/* Wizard sections */
.wizard-section {
margin-bottom: 15px;
}

.wizard-checklist {
margin: 10px 0;
padding-left: 20px;
}

.wizard-checklist li {
margin: 5px 0;
}

.wizard-prompt {
font-size: 16px;
text-align: center;
margin-bottom: 10px;
}

.wizard-prompt strong {
color: #e74c3c;
font-size: 18px;
}

/* Progress bar */
.wizard-progress-bar {
display: flex;
justify-content: center;
gap: 10px;
margin: 15px 0;
}

.wizard-progress-step {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #ddd;
color: #666;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
transition: all 0.3s ease;
}

.wizard-progress-step.active {
background-color: #e74c3c;
color: white;
animation: pulse 0.5s infinite;
}

.wizard-progress-step.complete {
background-color: #27ae60;
color: white;
}

/* Motor preview area */
.wizard-preview {
text-align: center;
margin: 20px auto;
}

.wizard-motor-preview {
position: relative;
width: 200px;
height: 200px;
margin: 0 auto;
}

.wizard-motor-preview img {
width: 200px;
height: 200px;
}

/* Position buttons - clickable circles at motor positions */
.wizard-position-btn {
position: absolute;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #bdc3c7;
color: white;
border: 3px solid #95a5a6;
cursor: default;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
pointer-events: none;
}

/* Position buttons when wizard is active and waiting for click */
.wizard-position-btn.waiting {
background-color: #3498db;
border-color: #2980b9;
cursor: pointer;
pointer-events: auto;
animation: glow 1s infinite;
}

.wizard-position-btn.waiting:hover {
background-color: #2980b9;
transform: scale(1.15);
}

/* Position buttons when motor has been assigned */
.wizard-position-btn.assigned {
background-color: #27ae60;
border-color: #1e8449;
}

@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.15); }
}

@keyframes glow {
0%, 100% { box-shadow: 0 0 5px rgba(52, 152, 219, 0.5); }
50% { box-shadow: 0 0 15px rgba(52, 152, 219, 0.8); }
}

/* Position buttons at quad corners (Quad X layout) */
/* Position 0: Rear-Right */
#wizardPos0 { bottom: 10px; right: 10px; }
/* Position 1: Front-Right */
#wizardPos1 { top: 10px; right: 10px; }
/* Position 2: Rear-Left */
#wizardPos2 { bottom: 10px; left: 10px; }
/* Position 3: Front-Left */
#wizardPos3 { top: 10px; left: 10px; }

/* Wizard button container */
.wizard-buttons {
margin-top: 1em;
text-align: center;
}
117 changes: 61 additions & 56 deletions tabs/mixer.html
Original file line number Diff line number Diff line change
Expand Up @@ -217,65 +217,70 @@ <h1 class="modal__title modal__title--warning" data-i18n="presetsApplyHeader"></
<div class="modal__content">
<h1 class="modal__title modal__title--warning" data-i18n="mixerWizardModalTitle"></h1>
<div class="modal__text">
<p data-i18n="mixerWizardInfo"></p>
<table style="margin-top: 1em;" class="mixer-table">
<thead>
<th data-i18n="mixerWizardMotorPosition"></th>
<th data-i18n="mixerWizardMotorIndex"></th>
</thead>
<tbody>
<tr>
<th data-i18n="motorWizard0"></th>
<td>
<select class="wizard-motor-select" data-motor="0">
<option id="0" selected="selected">Motor #1</option>
<option id="1">Motor #2</option>
<option id="2">Motor #3</option>
<option id="3">Motor #4</option>
</select>
</td>
</tr>
<tr>
<th data-i18n="motorWizard1"></th>
<td>
<select class="wizard-motor-select" data-motor="1">
<option id="0">Motor #1</option>
<option id="1" selected="selected">Motor #2</option>
<option id="2">Motor #3</option>
<option id="3">Motor #4</option>
</select>
</td>
</tr>
<tr>
<th data-i18n="motorWizard2"></th>
<td>
<select class="wizard-motor-select" data-motor="2">
<option id="0">Motor #1</option>
<option id="1">Motor #2</option>
<option id="2" selected="selected">Motor #3</option>
<option id="3">Motor #4</option>
</select>
</td>
</tr>
<tr>
<th data-i18n="motorWizard3"></th>
<td>
<select class="wizard-motor-select" data-motor="3">
<option id="0">Motor #1</option>
<option id="1">Motor #2</option>
<option id="2">Motor #3</option>
<option id="3" selected="selected">Motor #4</option>
</select>
</td>
</tr>
</tbody>
</table>
<!-- Initial instructions (shown before wizard starts) -->
<div id="wizard-intro" class="wizard-section">
<p data-i18n="mixerWizardIntro"></p>
<ul class="wizard-checklist">
<li data-i18n="mixerWizardStep1"></li>
<li data-i18n="mixerWizardStep2"></li>
<li data-i18n="mixerWizardStep3"></li>
</ul>
<div class="wizard-buttons">
<a id="wizard-start-button" class="modal__button modal__button--main" data-i18n="mixerWizardStart"></a>
</div>
</div>

<!-- Wizard in progress (shown during motor identification) -->
<div id="wizard-progress" class="wizard-section is-hidden">
<p class="wizard-prompt">
<span data-i18n="mixerWizardLocating"></span>
<strong id="wizard-current-motor">1</strong>.
<span data-i18n="mixerWizardClickPosition"></span>
</p>
<div class="wizard-progress-bar">
<div class="wizard-progress-step" data-step="0">1</div>
<div class="wizard-progress-step" data-step="1">2</div>
<div class="wizard-progress-step" data-step="2">3</div>
<div class="wizard-progress-step" data-step="3">4</div>
</div>
<div class="wizard-buttons">
<a id="wizard-stop-button" class="modal__button modal__button--warning" data-i18n="mixerWizardStop"></a>
</div>
</div>

<!-- Wizard complete (shown when all motors identified) -->
<div id="wizard-complete" class="wizard-section is-hidden">
<p data-i18n="mixerWizardComplete"></p>
<div class="wizard-buttons">
<a id="wizard-apply-button" class="modal__button modal__button--main" data-i18n="mixerWizardModalApply"></a>
</div>
</div>

<!-- Motor position preview - always visible -->
<div class="wizard-preview">
<div class="wizard-motor-preview">
<img src="./resources/motor_order/custom.svg" id="wizard-preview-img"/>
<!-- Position 0: Rear-Right -->
<div class="wizard-position-btn" id="wizardPos0" data-position="0">
<span class="position-label"></span>
</div>
<!-- Position 1: Front-Right -->
<div class="wizard-position-btn" id="wizardPos1" data-position="1">
<span class="position-label"></span>
</div>
<!-- Position 2: Rear-Left -->
<div class="wizard-position-btn" id="wizardPos2" data-position="2">
<span class="position-label"></span>
</div>
<!-- Position 3: Front-Left -->
<div class="wizard-position-btn" id="wizardPos3" data-position="3">
<span class="position-label"></span>
</div>
</div>
</div>
</div>
</div>

<div class="modal__buttons--upbottom">
<a id="wizard-execute-button" class="modal__button modal__button--main" data-i18n="mixerWizardModalApply"></a>
</div>
</div>
<div class="content_toolbar">
<div class="btn save_btn">
Expand Down
Loading