1
0
Fork 0
mirror of https://github.com/iNavFlight/inav-configurator.git synced 2025-07-23 08:15:19 +03:00

UI for smix rules

This commit is contained in:
Pawel Spychalski (DzikuVx) 2018-01-24 16:25:39 +01:00
parent 912864bc19
commit cf3a5b2a64
11 changed files with 257 additions and 76 deletions

View file

@ -2315,5 +2315,14 @@
}, },
"downloadUpdatesBtn": { "downloadUpdatesBtn": {
"message": "Download new app" "message": "Download new app"
},
"servoMixer": {
"message": "Servo mixer"
},
"servoMixerDelete": {
"message": "Delete"
},
"servoMixerAdd": {
"message": "Add new mixer rule"
} }
} }

View file

@ -91,6 +91,7 @@ sources.js = [
'./js/boards.js', './js/boards.js',
'./js/tasks.js', './js/tasks.js',
'./js/servoMixRule.js', './js/servoMixRule.js',
'./js/servoMixRuleCollection.js',
'./main.js', './main.js',
'./tabs/*.js', './tabs/*.js',
'./js/eventFrequencyAnalyzer.js', './js/eventFrequencyAnalyzer.js',

View file

@ -150,7 +150,7 @@ var FC = {
ADJUSTMENT_RANGES = []; ADJUSTMENT_RANGES = [];
SERVO_CONFIG = []; SERVO_CONFIG = [];
SERVO_RULES = []; SERVO_RULES = new ServoMixRuleCollection();
SERIAL_CONFIG = { SERIAL_CONFIG = {
ports: [], ports: [],
@ -935,5 +935,27 @@ var FC = {
}, },
getRcMapLetters: function () { getRcMapLetters: function () {
return ['A', 'E', 'R', 'T', '5', '6', '7', '8']; return ['A', 'E', 'R', 'T', '5', '6', '7', '8'];
},
getServoMixInputNames: function () {
return [
'Stabilised Roll',
'Stabilised Pitch',
'Stabilised Yaw',
'Stabilised Throttle',
'RC Roll',
'RC Pitch',
'RC Yaw',
'RC Throttle',
'RC Channel 5',
'RC Channel 6',
'RC Channel 7',
'RC Channel 8',
'Gimbal Pitch',
'Gimbal Roll',
'Flaps'
];
},
getServoMixInputName: function (input) {
return getServoMixInputNames()[input];
} }
}; };

View file

@ -2,29 +2,29 @@
// generate mixer // generate mixer
var mixerList = [ var mixerList = [
{name: 'Tricopter', model: 'tricopter', image: 'tri'}, // 1 {name: 'Tricopter', model: 'tricopter', image: 'tri', hasCustomServoMixer: false}, // 1
{name: 'Quad +', model: 'quad_x', image: 'quad_p'}, // 2 {name: 'Quad +', model: 'quad_x', image: 'quad_p', hasCustomServoMixer: false}, // 2
{name: 'Quad X', model: 'quad_x', image: 'quad_x'}, // 3 {name: 'Quad X', model: 'quad_x', image: 'quad_x', hasCustomServoMixer: false}, // 3
{name: 'Bicopter', model: 'custom', image: 'bicopter'}, // 4 {name: 'Bicopter', model: 'custom', image: 'bicopter', hasCustomServoMixer: false}, // 4
{name: 'Gimbal', model: 'custom', image: 'custom'}, // 5 {name: 'Gimbal', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 5
{name: 'Y6', model: 'y6', image: 'y6'}, // 6 {name: 'Y6', model: 'y6', image: 'y6', hasCustomServoMixer: false}, // 6
{name: 'Hex +', model: 'hex_plus', image: 'hex_p'}, // 7 {name: 'Hex +', model: 'hex_plus', image: 'hex_p', hasCustomServoMixer: false}, // 7
{name: 'Flying Wing', model: 'custom', image: 'flying_wing'}, // 8 {name: 'Flying Wing', model: 'custom', image: 'flying_wing', hasCustomServoMixer: false}, // 8
{name: 'Y4', model: 'y4', image: 'y4'}, // 9 {name: 'Y4', model: 'y4', image: 'y4', hasCustomServoMixer: false}, // 9
{name: 'Hex X', model: 'hex_x', image: 'hex_x'}, // 10 {name: 'Hex X', model: 'hex_x', image: 'hex_x', hasCustomServoMixer: false}, // 10
{name: 'Octo X8', model: 'custom', image: 'octo_x8'}, // 11 {name: 'Octo X8', model: 'custom', image: 'octo_x8', hasCustomServoMixer: false}, // 11
{name: 'Octo Flat +', model: 'custom', image: 'octo_flat_p'}, // 12 {name: 'Octo Flat +', model: 'custom', image: 'octo_flat_p', hasCustomServoMixer: false}, // 12
{name: 'Octo Flat X', model: 'custom', image: 'octo_flat_x'}, // 13 {name: 'Octo Flat X', model: 'custom', image: 'octo_flat_x', hasCustomServoMixer: false}, // 13
{name: 'Airplane', model: 'custom', image: 'airplane'}, // 14 {name: 'Airplane', model: 'custom', image: 'airplane', hasCustomServoMixer: false}, // 14
{name: 'Heli 120', model: 'custom', image: 'custom'}, // 15 {name: 'Heli 120', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 15
{name: 'Heli 90', model: 'custom', image: 'custom'}, // 16 {name: 'Heli 90', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 16
{name: 'V-tail Quad', model: 'quad_vtail', image: 'vtail_quad'}, // 17 {name: 'V-tail Quad', model: 'quad_vtail', image: 'vtail_quad', hasCustomServoMixer: false}, // 17
{name: 'Hex H', model: 'custom', image: 'custom'}, // 18 {name: 'Hex H', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 18
{name: 'PPM to SERVO', model: 'custom', image: 'custom'}, // 19 {name: 'PPM to SERVO', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 19
{name: 'Dualcopter', model: 'custom', image: 'custom'}, // 20 {name: 'Dualcopter', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 20
{name: 'Singlecopter', model: 'custom', image: 'custom'}, // 21 {name: 'Singlecopter', model: 'custom', image: 'custom', hasCustomServoMixer: false}, // 21
{name: 'A-tail Quad', model: 'quad_atail', image: 'atail_quad'}, // 22 {name: 'A-tail Quad', model: 'quad_atail', image: 'atail_quad', hasCustomServoMixer: false}, // 22
{name: 'Custom', model: 'custom', image: 'custom'}, // 23 {name: 'Custom', model: 'custom', image: 'custom', hasCustomServoMixer: true}, // 23
{name: 'Custom Airplane', model: 'custom', image: 'custom'}, // 24 {name: 'Custom Airplane', model: 'custom', image: 'custom', hasCustomServoMixer: true}, // 24
{name: 'Custom Tricopter', model: 'custom', image: 'custom'} // 25 {name: 'Custom Tricopter', model: 'custom', image: 'custom', hasCustomServoMixer: true} // 25
]; ];

View file

@ -350,11 +350,11 @@ var mspHelper = (function (gui) {
} }
break; break;
case MSPCodes.MSP_SERVO_MIX_RULES: case MSPCodes.MSP_SERVO_MIX_RULES:
SERVO_RULES = []; SERVO_RULES.flush();
if (data.byteLength % 7 === 0) { if (data.byteLength % 7 === 0) {
for (i = 0; i < data.byteLength; i += 7) { for (i = 0; i < data.byteLength; i += 7) {
SERVO_RULES.push(new ServoMixRule( SERVO_RULES.put(new ServoMixRule(
data.getInt8(i + 0, true), data.getInt8(i + 0, true),
data.getInt8(i + 1, true), data.getInt8(i + 1, true),
data.getInt8(i + 2, true), data.getInt8(i + 2, true),
@ -362,6 +362,7 @@ var mspHelper = (function (gui) {
)); ));
} }
} }
SERVO_RULES.cleanup();
break; break;

View file

@ -5,27 +5,41 @@ var ServoMixRule = function (target, input, rate, speed) {
var self = {}; var self = {};
// self.target = target;
// self.input = input;
// self.rate = rate;
// self.speed = speed;
self.getTarget = function () { self.getTarget = function () {
return target; return target;
}; };
self.setTarget = function (data) {
target = data;
};
self.getInput = function () { self.getInput = function () {
return input; return input;
}; };
self.setInput = function (data) {
input = data;
};
self.getRate = function () { self.getRate = function () {
return rate; return rate;
}; };
self.setRate = function (data) {
rate = data;
};
self.getSpeed = function () { self.getSpeed = function () {
return speed; return speed;
}; };
self.setSpeed = function (data) {
speed = data;
};
self.isUsed = function () {
return rate !== 0;
};
return self; return self;
}; };

View file

@ -0,0 +1,49 @@
/*global $, ServoMixRule*/
'use strict';
var ServoMixRuleCollection = function () {
var self = {};
var data = [];
self.put = function (element) {
data.push(element);
};
self.get = function () {
return data;
};
self.drop = function (index) {
data[index].setRate(0);
self.cleanup();
};
self.flush = function () {
data = [];
};
self.cleanup = function () {
var tmpData = [];
data.forEach(function (element) {
if (element.isUsed()) {
tmpData.push(element);
}
});
data = tmpData;
};
self.inflate = function () {
while (self.hasFreeSlots()) {
self.put(new ServoMixRule(0, 0, 0, 0));
}
}
self.hasFreeSlots = function () {
return data.length < 16;
};
return self;
};

View file

@ -1315,6 +1315,15 @@ dialog {
float: left; float: left;
} }
.default_btn.narrow {
width: auto;
margin-bottom: 0;
}
.default_btn.narrow a {
padding: 5px;
}
.default_btn a { .default_btn a {
padding: 5px 0 5px 0; padding: 5px 0 5px 0;
text-align: center; text-align: center;

View file

@ -50,7 +50,16 @@
background-color: #f9f9f9; background-color: #f9f9f9;
} }
.tab-servos table tr:first-child { #servo-mix-table input {
width: 75px;
}
#servo-mix-table .btn {
float: none;
}
#servo-config-table tr:first-child,
#servo-mix-table thead tr {
border-left: 1px solid #e4e4e4; border-left: 1px solid #e4e4e4;
border-right: 1px solid #e4e4e4; border-right: 1px solid #e4e4e4;
background-color: #828885; background-color: #828885;
@ -154,22 +163,6 @@
bottom: 10px; bottom: 10px;
} }
.tab-servos .require-support {
display: none;
}
.tab-servos.supported .require-support {
display: block;
}
.tab-servos .require-upgrade {
display: block;
}
.tab-servos.supported .require-upgrade {
display: none;
}
.tab-servos .live span { .tab-servos .live span {
margin-right: 10px; margin-right: 10px;
} }

View file

@ -1,13 +1,12 @@
<!--suppress HtmlFormInputWithoutLabel -->
<div class="tab-servos toolbar_fixed_bottom"> <div class="tab-servos toolbar_fixed_bottom">
<div class="content_wrapper"> <div class="content_wrapper">
<div class="tab_title" data-i18n="tabServos">Servos</div> <div class="tab_title" data-i18n="tabServos">Servos</div>
<div class="cf_doc_version_bt"> <div class="cf_doc_version_bt">
<a id="button-documentation" href="" target="_blank"></a> <a id="button-documentation" href="" target="_blank"></a>
</div> </div>
<div class="require-support"> <div>
<div class="title" data-i18n="servosChangeDirection"></div> <div class="title" data-i18n="servosChangeDirection"></div>
<table class="fields"> <table id="servo-config-table" class="fields">
<tr class="main"> <tr class="main">
<th width="110px" data-i18n="servosName"></th> <th width="110px" data-i18n="servosName"></th>
<th data-i18n="servosMid"></th> <th data-i18n="servosMid"></th>
@ -23,6 +22,30 @@
<input type="checkbox" class="togglemedium" /> <span data-i18n="servosLiveMode"></span> <input type="checkbox" class="togglemedium" /> <span data-i18n="servosLiveMode"></span>
</div> </div>
</div> </div>
<div class="clear-both"></div>
<div id="servo-mix-table-wrapper" class="margin-top">
<div class="tab_title" data-i18n="servoMixer">Servo mixer</div>
<div class="btn default_btn narrow pull-right" style="margin-bottom: 15px">
<a href="#" data-role="role-add" data-i18n="servoMixerAdd"></a>
</div>
<table id="servo-mix-table">
<thead>
<tr>
<th style="width: 75px">Servo</th>
<th>Input</th>
<th>Weight</th>
<th>Speed</th>
<th style="width: 75px"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="btn default_btn narrow pull-right" style="margin-bottom: 15px">
<a href="#" data-role="role-add" data-i18n="servoMixerAdd"></a>
</div>
</div>
</div> </div>
<div class="content_toolbar"> <div class="content_toolbar">
<div class="btn save_btn"> <div class="btn save_btn">

View file

@ -14,6 +14,7 @@ TABS.servos.initialize = function (callback) {
loadChainer.setChain([ loadChainer.setChain([
mspHelper.loadServoConfiguration, mspHelper.loadServoConfiguration,
mspHelper.loadRcData, mspHelper.loadRcData,
mspHelper.loadBfConfig,
mspHelper.loadServoMixRules mspHelper.loadServoMixRules
]); ]);
loadChainer.setExitPoint(load_html); loadChainer.setExitPoint(load_html);
@ -36,19 +37,20 @@ TABS.servos.initialize = function (callback) {
function update_ui() { function update_ui() {
var i, var i,
$tabServos = $(".tab-servos"); $tabServos = $(".tab-servos"),
$servoConfigTable = $('#servo-config-table'),
$servoMixTable = $('#servo-mix-table'),
$servoMixTableBody = $servoMixTable.find('tbody');
if (SERVO_CONFIG.length == 0) { if (SERVO_CONFIG.length == 0) {
$tabServos.removeClass("supported"); $tabServos.addClass("is-hidden");
return; return;
} }
$tabServos.addClass("supported");
var servoCheckbox = ''; var servoCheckbox = '';
var servoHeader = ''; var servoHeader = '';
for (i = 0; i < RC.active_channels-4; i++) { for (i = 0; i < RC.active_channels - 4; i++) {
servoHeader = servoHeader + '<th class="short">CH' + (i+5) + '</th>'; servoHeader = servoHeader + '<th class="short">CH' + (i + 5) + '</th>';
} }
servoHeader = servoHeader + '<th data-i18n="servosDirectionAndRate"></th>'; servoHeader = servoHeader + '<th data-i18n="servosDirectionAndRate"></th>';
@ -56,18 +58,16 @@ TABS.servos.initialize = function (callback) {
servoCheckbox = servoCheckbox + '<td class="channel"><input type="checkbox"/></td>'; servoCheckbox = servoCheckbox + '<td class="channel"><input type="checkbox"/></td>';
} }
$('div.tab-servos table.fields tr.main').append(servoHeader); $servoConfigTable.find('tr.main').append(servoHeader);
function process_servos(name, alternate, obj) { function process_servos(name, alternate, obj) {
$('div.supported_wrapper').show(); $servoConfigTable.append('\
$('div.tab-servos table.fields').append('\
<tr> \ <tr> \
<td style="text-align: center">' + name + '</td>\ <td style="text-align: center">' + name + '</td>\
<td class="middle"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].middle + '" /></td>\ <td class="middle"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].middle + '" /></td>\
<td class="min"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].min +'" /></td>\ <td class="min"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].min + '" /></td>\
<td class="max"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].max +'" /></td>\ <td class="max"><input type="number" min="500" max="2500" value="' + SERVO_CONFIG[obj].max + '" /></td>\
' + servoCheckbox + '\ ' + servoCheckbox + '\
<td class="direction">\ <td class="direction">\
</td>\ </td>\
@ -75,13 +75,13 @@ TABS.servos.initialize = function (callback) {
'); ');
if (SERVO_CONFIG[obj].indexOfChannelToForward >= 0) { if (SERVO_CONFIG[obj].indexOfChannelToForward >= 0) {
$('div.tab-servos table.fields tr:last td.channel input').eq(SERVO_CONFIG[obj].indexOfChannelToForward).prop('checked', true); $servoConfigTable.find('tr:last td.channel input').eq(SERVO_CONFIG[obj].indexOfChannelToForward).prop('checked', true);
} }
// adding select box and generating options // adding select box and generating options
$('div.tab-servos table.fields tr:last td.direction').append('<select class="rate" name="rate"></select>'); $servoConfigTable.find('tr:last td.direction').append('<select class="rate" name="rate"></select>');
var select = $('div.tab-servos table.fields tr:last td.direction select'); var select = $servoConfigTable.find('tr:last td.direction select');
for (var i = FC.MAX_SERVO_RATE; i >= FC.MIN_SERVO_RATE; i--) { for (var i = FC.MAX_SERVO_RATE; i >= FC.MIN_SERVO_RATE; i--) {
select.append('<option value="' + i + '">Rate: ' + i + '%</option>'); select.append('<option value="' + i + '">Rate: ' + i + '%</option>');
@ -90,20 +90,20 @@ TABS.servos.initialize = function (callback) {
// select current rate // select current rate
select.val(SERVO_CONFIG[obj].rate); select.val(SERVO_CONFIG[obj].rate);
$('div.tab-servos table.fields tr:last').data('info', {'obj': obj}); $servoConfigTable.find('tr:last').data('info', { 'obj': obj });
// UI hooks // UI hooks
// only one checkbox for indicating a channel to forward can be selected at a time, perhaps a radio group would be best here. // only one checkbox for indicating a channel to forward can be selected at a time, perhaps a radio group would be best here.
$('div.tab-servos table.fields tr:last td.channel input').click(function () { $servoConfigTable.find('tr:last td.channel input').click(function () {
if($(this).is(':checked')) { if ($(this).is(':checked')) {
$(this).parent().parent().find('.channel input').not($(this)).prop('checked', false); $(this).parent().parent().find('.channel input').not($(this)).prop('checked', false);
} }
}); });
} }
function servos_update(save_configuration_to_eeprom) { function servos_update(save_configuration_to_eeprom) {
$('div.tab-servos table.fields tr:not(".main")').each(function () { $servoConfigTable.find('tr:not(".main")').each(function () {
var info = $(this).data('info'); var info = $(this).data('info');
@ -115,7 +115,6 @@ TABS.servos.initialize = function (callback) {
SERVO_CONFIG[info.obj].indexOfChannelToForward = channelIndex; SERVO_CONFIG[info.obj].indexOfChannelToForward = channelIndex;
SERVO_CONFIG[info.obj].middle = parseInt($('.middle input', this).val()); SERVO_CONFIG[info.obj].middle = parseInt($('.middle input', this).val());
SERVO_CONFIG[info.obj].min = parseInt($('.min input', this).val()); SERVO_CONFIG[info.obj].min = parseInt($('.min input', this).val());
SERVO_CONFIG[info.obj].max = parseInt($('.max input', this).val()); SERVO_CONFIG[info.obj].max = parseInt($('.max input', this).val());
@ -128,14 +127,14 @@ TABS.servos.initialize = function (callback) {
} }
// drop previous table // drop previous table
$('div.tab-servos table.fields tr:not(:first)').remove(); $servoConfigTable.find('tr:not(:first)').remove();
for (var servoIndex = 0; servoIndex < 8; servoIndex++) { for (var servoIndex = 0; servoIndex < 8; servoIndex++) {
process_servos('Servo ' + servoIndex, '', servoIndex, false); process_servos('Servo ' + servoIndex, '', servoIndex, false);
} }
// UI hooks for dynamically generated elements // UI hooks for dynamically generated elements
$('table.directions select, table.directions input, table.fields select, table.fields input').change(function () { $('table.directions select, table.directions input, #servo-config-table select, #servo-config-table input').change(function () {
if ($('div.live input').is(':checked')) { if ($('div.live input').is(':checked')) {
// apply small delay as there seems to be some funky update business going wrong // apply small delay as there seems to be some funky update business going wrong
helper.timeout.add('servos_update', servos_update, 10); helper.timeout.add('servos_update', servos_update, 10);
@ -146,6 +145,67 @@ TABS.servos.initialize = function (callback) {
servos_update(true); servos_update(true);
}); });
$servoMixTableBody.on('click', "[data-role='role-delete']", function (event) {
SERVO_RULES.drop($(event.currentTarget).attr("data-index"));
renderServoMixRules();
});
$("[data-role='role-add']").click(function () {
if (SERVO_RULES.hasFreeSlots()) {
SERVO_RULES.put(new ServoMixRule(0, 0, 0, 0));
renderServoMixRules();
}
});
function renderServoMixRules() {
/*
* Process servo mix table UI
*/
var rules = SERVO_RULES.get();
$servoMixTableBody.find("*").remove();
for (servoRuleIndex in rules) {
if (rules.hasOwnProperty(servoRuleIndex)) {
const servoRule = rules[servoRuleIndex];
$servoMixTableBody.append('\
<tr>\
<td><input type="number" class="mix-rule-servo" step="1" min="0" max="7" /></td>\
<td><select class="mix-rule-input"></select></td>\
<td><input type="number" class="mix-rule-rate" step="1" min="-100" max="100" /></td>\
<td><input type="number" class="mix-rule-speed" step="1" min="0" max="255" /></td>\
<td><span class="btn default_btn narrow"><a href="#" data-role="role-delete" data-i18n="servoMixerDelete"></a></span></td>\
</tr>\
');
const $row = $servoMixTableBody.find('tr:last');
GUI.fillSelect($row.find(".mix-rule-input"), FC.getServoMixInputNames(), servoRule.getInput());
$row.find(".mix-rule-input").val(servoRule.getInput()).change(function () {
servoRule.setInput($(this).val());
});
$row.find(".mix-rule-servo").val(servoRule.getTarget()).change(function () {
servoRule.setTarget($(this).val());
});
$row.find(".mix-rule-rate").val(servoRule.getRate()).change(function () {
servoRule.setRate($(this).val());
});
$row.find(".mix-rule-speed").val(servoRule.getSpeed()).change(function () {
servoRule.setSpeed($(this).val());
});
$row.find("[data-role='role-delete']").attr("data-index", servoRuleIndex);
}
}
localize();
}
renderServoMixRules();
} }
function process_html() { function process_html() {