Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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