1
0
Fork 0
mirror of https://github.com/iNavFlight/inav-configurator.git synced 2025-07-20 23:05:13 +03:00

Offline missions (with FC disconnected) on file (#720)

* Offline missions (with FC disconnected) are now available for load and save to file.
Supports XML file format, should be compatible enough with other software

* removed default file name

* removed code in early stage of future developments

* code style and fc buttons

* map resize handling
This commit is contained in:
mirko-it 2019-04-29 17:23:32 +02:00 committed by giacomo892
parent cec7997bfc
commit 3dcffdd292
6 changed files with 349 additions and 74 deletions

View file

@ -2719,6 +2719,18 @@
"saveEepromMissionButton": {
"message": "Save Eeprom mission"
},
"loadFileMissionButton": {
"message": "Load file"
},
"saveFileMissionButton": {
"message": "Save file"
},
"missionSettingsSave": {
"message": "Save"
},
"missionSettingsCancel": {
"message": "Cancel"
},
"editPointHead": {
"message": "Edit point"
},

View file

@ -13,6 +13,7 @@ var GUI_control = function () {
this.defaultAllowedTabsWhenDisconnected = [
'landing',
'firmware_flasher',
'mission_control',
'help'
];
this.defaultAllowedTabsWhenConnected = [

View file

@ -165,6 +165,7 @@
title="Welcome"></a></li>
<li class="tab_help"><a href="https://github.com/iNavFlight/inav/wiki" target="_blank" data-i18n="tabHelp" class="tabicon ic_help"
title="Documentation &amp; Support"></a></li>
<li class="tab_mission_control"><a href="#" data-i18n="tabMissionControl" class="tabicon ic_mission" title="Mission Control"></a></li>
<li class="tab_firmware_flasher"><a href="#" data-i18n="tabFirmwareFlasher" class="tabicon ic_flasher"
title="Firmware Flasher"></a></li>
</ul>

View file

@ -34,8 +34,10 @@
"minimist": "^1.2.0",
"nw": "^0.36.1-sdk",
"nw-builder": "^3.5.7",
"nw-dialog": "^1.0.7",
"openlayers": "^4.6.5",
"temp": "^0.8.3",
"three": "0.72.0"
"three": "0.72.0",
"xml2js": "^0.4.19"
}
}

View file

@ -14,16 +14,17 @@
</div>
<div class="spacer">
<div class="point">
<label class="point-label" for="pointAlt">Alt (cm): </label>
<label class="point-label" for="MPdefaultPointAlt">Alt (cm): </label>
<input id="MPdefaultPointAlt" type="text" value="0" required>
</div>
<div class="point">
<label class="point-label" for="pointSpeed">Speed (cm/s): </label>
<label class="point-label" for="MPdefaultPointSpeed">Speed (cm/s): </label>
<input id="MPdefaultPointSpeed" type="text" value="0" required>
</div>
<div>
<div id="saveSettings" class="btn save_btn" style="padding-top: 10px; display: inline-block">
<a class="save" href="#" data-i18n="editPointButtonSave" style="float: left">Save</a>
<div class="btn save_btn" style="padding-top: 10px;">
<a id="saveSettings" class="save" href="#" data-i18n="missionSettingsSave" style="float: left">Save</a>
<a id="cancelSettings" class="save" href="#" data-i18n="missionSettingsCancel" style="float: right">Cancel</a>
</div>
</div>
</div>
@ -33,15 +34,15 @@
<div class="spacer_box_title i18n-replaced" data-i18n="missionTotalInformationHead">Total information</div>
</div>
<div class="spacer">
<div style="padding-bottom: 2px;">
<div id="infoMissionDistance" style="padding-bottom: 2px;">
<span>Distance (m):</span>
<span id="missionDistance"></span>
</div>
<div style="padding-bottom: 2px;">
<div id="infoAvailablePoints" style="padding-bottom: 2px;">
<span>Available Points</span>
<span id="availablePoints">0/0</span>
</div>
<div style="padding-bottom: 2px;">
<div id="infoMissionValid" style="padding-bottom: 2px;">
<span>Mission valid</span>
<div id="missionValid" style="display: inline-block"></div>
</div>
@ -57,6 +58,10 @@
</div>
</div>
<hr>
<div class="btn save_btn">
<a id="loadFileMissionButton" class="save" href="#" data-i18n="loadFileMissionButton">Load file mission</a>
<a id="saveFileMissionButton" class="save" href="#" data-i18n="saveFileMissionButton">Save file mission</a>
</div>
<div class="btn save_btn">
<a id="loadMissionButton" class="save" href="#" data-i18n="loadMissionButton">Load mission from FC</a>
<a id="saveMissionButton" class="save" href="#" data-i18n="saveMissionButton">Save mission to FC</a>

View file

@ -1,5 +1,21 @@
'use strict';
// MultiWii NAV Protocol
var MWNP = MWNP || {};
// WayPoint type
MWNP.WPTYPE = {
WAYPOINT: 1,
PH_UNLIM: 2,
PH_TIME: 3,
RTH: 4,
SET_POI: 5,
JUMP: 6,
SET_HEAD: 7,
LAND: 8
};
TABS.mission_control = {};
TABS.mission_control.isYmapLoad = false;
TABS.mission_control.initialize = function (callback) {
@ -9,37 +25,46 @@ TABS.mission_control.initialize = function (callback) {
googleAnalytics.sendAppView('Mission Control');
}
if (CONFIGURATOR.connectionValid) {
var loadChainer = new MSPChainerClass();
loadChainer.setChain([
mspHelper.getMissionInfo
]);
loadChainer.setExitPoint(loadHtml);
loadChainer.execute();
} else {
// FC not connected, load page anyway
loadHtml();
}
function updateTotalInfo() {
if (CONFIGURATOR.connectionValid) {
$('#availablePoints').text(MISSION_PLANER.countBusyPoints + '/' + MISSION_PLANER.maxWaypoints);
$('#missionValid').html(MISSION_PLANER.isValidMission ? chrome.i18n.getMessage('armingCheckPass') : chrome.i18n.getMessage('armingCheckFail'));
}
}
function loadHtml() {
$('#content').load("./tabs/mission_control.html", process_html);
}
function process_html() {
if (typeof require !== "undefined") {
chrome.storage.local.get('missionPlanerSettings', function (result) {
if (result.missionPlanerSettings) {
$('#MPdefaultPointAlt').val(result.missionPlanerSettings.alt);
$('#MPdefaultPointSpeed').val(result.missionPlanerSettings.speed);
} else {
chrome.storage.local.set({'missionPlanerSettings': {speed: 0, alt: 5000}});
$('#MPdefaultPointAlt').val(5000);
$('#MPdefaultPointSpeed').val(0);
// set GUI for offline operations
if (!CONFIGURATOR.connectionValid) {
$('#infoAvailablePoints').hide();
$('#infoMissionValid').hide();
$('#loadMissionButton').hide();
$('#saveMissionButton').hide();
$('#loadEepromMissionButton').hide();
$('#saveEepromMissionButton').hide();
}
});
initMap();
if (typeof require !== "undefined") {
loadSettings();
// let the dom load finish, avoiding the resizing of the map
setTimeout(initMap, 200);
} else {
$('#missionMap, #missionControls').hide();
$('#notLoadMap').show();
@ -54,6 +79,7 @@ TABS.mission_control.initialize = function (callback) {
var map;
var selectedMarker = null;
var pointForSend = 0;
var settings = { speed: 0, alt: 5000 };
function clearEditForm() {
$('#pointLat').val('');
@ -64,6 +90,25 @@ TABS.mission_control.initialize = function (callback) {
$('#MPeditPoint').fadeOut(300);
}
function loadSettings() {
chrome.storage.local.get('missionPlanerSettings', function (result) {
if (result.missionPlanerSettings) {
settings = result.missionPlanerSettings;
}
refreshSettings();
});
}
function saveSettings() {
chrome.storage.local.set({'missionPlanerSettings': settings});
}
function refreshSettings() {
$('#MPdefaultPointAlt').val(settings.alt);
$('#MPdefaultPointSpeed').val(settings.speed);
}
function repaint() {
var oldPos;
for (var i in lines) {
@ -122,16 +167,16 @@ TABS.mission_control.initialize = function (callback) {
scale: 0.5,
src: '../images/icons/cf_icon_position' + (isEdit ? '_edit' : '') + '.png'
}))
// text: new ol.style.Text({
// text: '10',
// offsetX: -1,
// offsetY: -30,
// overflow: true,
// scale: 2,
// fill: new ol.style.Fill({
// color: 'black'
// })
// })
/*
text: new ol.style.Text({
text: '10',
offsetX: -1,
offsetY: -30,
overflow: true,
scale: 2,
fill: new ol.style.Fill({ color: 'black' })
})
*/
});
}
@ -315,8 +360,8 @@ TABS.mission_control.initialize = function (callback) {
return false;
};
var lat = GPS_DATA.lat / 10000000;
var lon = GPS_DATA.lon / 10000000;
var lat = (GPS_DATA ? (GPS_DATA.lat / 10000000) : 0);
var lon = (GPS_DATA ? (GPS_DATA.lon / 10000000) : 0);
let mapLayer;
@ -358,15 +403,26 @@ TABS.mission_control.initialize = function (callback) {
// Set the attribute link to open on an external browser window, so
// it doesn't interfere with the configurator.
var interval;
interval = setInterval(function() {
var anchor = $('.ol-attribution a');
if (anchor.length) {
anchor.attr('target', '_blank');
clearInterval(interval);
}
setTimeout(function() {
$('.ol-attribution a').attr('target', '_blank');
}, 100);
// save map view settings when user moves it
map.on('moveend', function (evt) {
chrome.storage.local.set({'missionPlanerLastValues': {
center: ol.proj.toLonLat(map.getView().getCenter()),
zoom: map.getView().getZoom()
}});
});
// load map view settings on startup
chrome.storage.local.get('missionPlanerLastValues', function (result) {
if (result.missionPlanerLastValues && result.missionPlanerLastValues.center) {
map.getView().setCenter(ol.proj.fromLonLat(result.missionPlanerLastValues.center));
map.getView().setZoom(result.missionPlanerLastValues.zoom);
}
});
map.on('click', function (evt) {
if (selectedMarker != null) {
try {
@ -392,14 +448,14 @@ TABS.mission_control.initialize = function (callback) {
selectedFeature.setStyle(getPointIcon(true));
$('#pointLon').val(coord[0]);
$('#pointLat').val(coord[1]);
$('#pointLon').val(Math.round(coord[0] * 10000000) / 10000000);
$('#pointLat').val(Math.round(coord[1] * 10000000) / 10000000);
$('#pointAlt').val(selectedMarker.alt);
$('#pointType').val(selectedMarker.action);
$('#pointSpeed').val(selectedMarker.speedValue);
$('#MPeditPoint').fadeIn(300);
} else {
map.addLayer(addMarker(evt.coordinate, $('#MPdefaultPointAlt').val(), 1, $('#MPdefaultPointSpeed').val()));
map.addLayer(addMarker(evt.coordinate, settings.alt, MWNP.WPTYPE.WAYPOINT, settings.speed));
repaint();
}
});
@ -417,8 +473,15 @@ TABS.mission_control.initialize = function (callback) {
}
});
// handle map size on container resize
setInterval(function () {
let width = $("#missionMap canvas").width(), height = $("#missionMap canvas").height();
if ((map.width_ != width) || (map.height_ != height)) map.updateSize();
map.width_ = width; map.height_ = height;
}, 200);
$('#removeAllPoints').on('click', function () {
if (confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) {
if (markers.length && confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) {
removeAllPoints();
}
});
@ -460,13 +523,28 @@ TABS.mission_control.initialize = function (callback) {
}
});
$('#loadMissionButton').on('click', function () {
if (markers.length) {
if (!confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) {
return;
}
$('#loadFileMissionButton').on('click', function () {
if (markers.length && !confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) return;
removeAllPoints();
var dialog = require('nw-dialog');
dialog.setContext(document);
dialog.openFileDialog(function(result) {
loadMissionFile(result);
})
});
$('#saveFileMissionButton').on('click', function () {
//if (!markers.length) return;
var dialog = require('nw-dialog');
dialog.setContext(document);
dialog.saveFileDialog('', '.mission', function(result) {
saveMissionFile(result);
})
});
$('#loadMissionButton').on('click', function () {
if (markers.length && !confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) return;
removeAllPoints();
}
$(this).addClass('disabled');
GUI.log('Start get point');
@ -483,12 +561,8 @@ TABS.mission_control.initialize = function (callback) {
});
$('#loadEepromMissionButton').on('click', function () {
if (markers.length) {
if (!confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) {
return;
}
if (markers.length && !confirm(chrome.i18n.getMessage('confirm_delete_all_points'))) return;
removeAllPoints();
}
GUI.log(chrome.i18n.getMessage('eeprom_load_ok'));
MSP.send_message(MSPCodes.MSP_WP_MISSION_LOAD, [0], getPointsFromEprom);
@ -507,15 +581,25 @@ TABS.mission_control.initialize = function (callback) {
});
$('#saveSettings').on('click', function () {
chrome.storage.local.set({'missionPlanerSettings': {speed: $('#MPdefaultPointSpeed').val(), alt: $('#MPdefaultPointAlt').val()}});
settings = { speed: $('#MPdefaultPointSpeed').val(), alt: $('#MPdefaultPointAlt').val() };
saveSettings();
closeSettingsPanel();
});
$('#cancelSettings').on('click', function () {
loadSettings();
closeSettingsPanel();
});
updateTotalInfo();
}
function closeSettingsPanel() {
$('#missionPlanerSettings').hide();
$('#missionPalnerTotalInfo').fadeIn(300);
if (selectedMarker !== null) {
$('#MPeditPoint').fadeIn(300);
}
});
updateTotalInfo();
}
function removeAllPoints() {
@ -524,9 +608,179 @@ TABS.mission_control.initialize = function (callback) {
}
markers = [];
clearEditForm();
updateTotalInfo();
$('#rthEndMission').prop('checked', false);
$('#rthSettings').fadeOut(300);
$('#rthLanding').prop('checked', false);
repaint();
}
function loadMissionFile(filename) {
const fs = require('fs-extra');
const xml2js = require('xml2js');
fs.readFile(filename, (err, data) => {
if (err) {
GUI.log('<span style="color: red">Error reading file</span>');
return console.error(err);
}
xml2js.Parser({ 'explicitChildren': true, 'preserveChildrenOrder': true }).parseString(data, (err, result) => {
if (err) {
GUI.log('<span style="color: red">Error parsing file</span>');
return console.error(err);
}
// parse mission file
var mission = { points: [] };
var node = null;
var nodemission = null;
for (var noderoot in result) {
if (!nodemission && noderoot.match(/mission/i)) {
nodemission = result[noderoot];
if (nodemission.$$ && nodemission.$$.length) {
for (var i = 0; i < nodemission.$$.length; i++) {
node = nodemission.$$[i];
if (node['#name'].match(/version/i) && node.$) {
for (var attr in node.$) {
if (attr.match(/value/i)) {
mission.version = node.$[attr]
}
}
} else if (node['#name'].match(/mwp/i) && node.$) {
mission.center = {};
for (var attr in node.$) {
if (attr.match(/zoom/i)) {
mission.center.zoom = parseInt(node.$[attr]);
} else if (attr.match(/cx/i)) {
mission.center.lon = parseFloat(node.$[attr]);
} else if (attr.match(/cy/i)) {
mission.center.lat = parseFloat(node.$[attr]);
}
}
} else if (node['#name'].match(/missionitem/i) && node.$) {
var point = {};
for (var attr in node.$) {
if (attr.match(/no/i)) {
point.index = parseInt(node.$[attr]);
} else if (attr.match(/action/i)) {
if (node.$[attr].match(/WAYPOINT/i)) {
point.action = MWNP.WPTYPE.WAYPOINT;
} else if (node.$[attr].match(/PH_UNLIM/i) || node.$[attr].match(/POSHOLD_UNLIM/i)) {
point.action = MWNP.WPTYPE.PH_UNLIM;
} else if (node.$[attr].match(/PH_TIME/i) || node.$[attr].match(/POSHOLD_TIME/i)) {
point.action = MWNP.WPTYPE.PH_TIME;
} else if (node.$[attr].match(/RTH/i)) {
point.action = MWNP.WPTYPE.RTH;
} else if (node.$[attr].match(/SET_POI/i)) {
point.action = MWNP.WPTYPE.SET_POI;
} else if (node.$[attr].match(/JUMP/i)) {
point.action = MWNP.WPTYPE.JUMP;
} else if (node.$[attr].match(/SET_HEAD/i)) {
point.action = MWNP.WPTYPE.SET_HEAD;
} else if (node.$[attr].match(/LAND/i)) {
point.action = MWNP.WPTYPE.LAND;
} else {
point.action = 0;
}
} else if (attr.match(/lat/i)) {
point.lat = parseFloat(node.$[attr]);
} else if (attr.match(/lon/i)) {
point.lon = parseFloat(node.$[attr]);
} else if (attr.match(/alt/i)) {
point.alt = (parseInt(node.$[attr]) * 100);
} else if (attr.match(/parameter1/i)) {
point.p1 = parseInt(node.$[attr]);
} else if (attr.match(/parameter2/i)) {
point.p2 = parseInt(node.$[attr]);
} else if (attr.match(/parameter3/i)) {
point.p3 = parseInt(node.$[attr]);
}
}
mission.points.push(point);
}
}
}
}
}
// draw actual mission
removeAllPoints();
for (var i = 0; i < mission.points.length; i++) {
//if ([MWNP.WPTYPE.WAYPOINT,MWNP.WPTYPE.PH_UNLIM,MWNP.WPTYPE.PH_TIME,MWNP.WPTYPE.LAND].includes(mission.points[i].action)) {
if (mission.points[i].action == MWNP.WPTYPE.WAYPOINT) {
var coord = ol.proj.fromLonLat([mission.points[i].lon, mission.points[i].lat]);
map.addLayer(addMarker(coord, mission.points[i].alt, mission.points[i].action, mission.points[i].p1));
if (i == 0) {
map.getView().setCenter(coord);
map.getView().setZoom(16);
}
} else if (mission.points[i].action == MWNP.WPTYPE.RTH) {
$('#rthEndMission').prop('checked', true);
$('#rthSettings').fadeIn(300);
if (mission.points[i].p1 > 0) {
$('#rthLanding').prop('checked', true);
}
}
}
if (mission.center) {
var coord = ol.proj.fromLonLat([mission.center.lon, mission.center.lat]);
map.getView().setCenter(coord);
if (mission.center.zoom) map.getView().setZoom(mission.center.zoom);
}
repaint();
updateTotalInfo();
});
});
}
function saveMissionFile(filename) {
const fs = require('fs-extra');
const xml2js = require('xml2js');
var center = ol.proj.toLonLat(map.getView().getCenter());
var zoom = map.getView().getZoom();
var data = {
'version': { $: { 'value': '2.3-pre8' } },
'mwp': { $: { 'cx': (Math.round(center[0] * 10000000) / 10000000), 'cy': (Math.round(center[1] * 10000000) / 10000000), 'zoom': zoom } },
'missionitem': []
};
for (var i = 0; i < markers.length; i++) {
var geometry = markers[i].getSource().getFeatures()[0].getGeometry();
var coordinate = ol.proj.toLonLat(geometry.getCoordinates());
var point = { $: {
'no': (i + 1),
'action': ((markers[i].action == MWNP.WPTYPE.WAYPOINT) ? 'WAYPOINT' : markers[i].action),
'lon': (Math.round(coordinate[0] * 10000000) / 10000000),
'lat': (Math.round(coordinate[1] * 10000000) / 10000000),
'alt': (markers[i].alt / 100)
} };
if ((markers[i].action == MWNP.WPTYPE.WAYPOINT) && (markers[i].speedValue > 0)) point.$['parameter1'] = markers[i].speedValue;
data.missionitem.push(point);
}
// add last RTH point
if ($('#rthEndMission').is(':checked')) {
data.missionitem.push({ $: { 'no': (markers.length + 1), 'action': 'RTH', 'lon': 0, 'lat': 0, 'alt': (settings.alt / 100), 'parameter1': ($('#rthLanding').is(':checked') ? 1 : 0) } });
}
var builder = new xml2js.Builder({ 'rootName': 'mission', 'renderOpts': { 'pretty': true, 'indent': '\t', 'newline': '\n' } });
var xml = builder.buildObject(data);
fs.writeFile(filename, xml, (err) => {
if (err) {
GUI.log('<span style="color: red">Error writing file</span>');
return console.error(err);
}
GUI.log('File saved');
});
}
function getPointsFromEprom() {
pointForSend = 0;
MSP.send_message(MSPCodes.MSP_WP_GETINFO, false, false, getNextPoint);
@ -551,10 +805,10 @@ TABS.mission_control.initialize = function (callback) {
// console.log(MISSION_PLANER.bufferPoint.alt);
// console.log(MISSION_PLANER.bufferPoint.action);
if (MISSION_PLANER.bufferPoint.action == 4) {
$('#rthEndMission').attr('checked', true);
$('#rthEndMission').prop('checked', true);
$('#rthSettings').fadeIn(300);
if (MISSION_PLANER.bufferPoint.p1 > 0) {
$('#rthLanding').attr('checked', true);
$('#rthLanding').prop('checked', true);
}
} else {
var coord = ol.proj.fromLonLat([MISSION_PLANER.bufferPoint.lon, MISSION_PLANER.bufferPoint.lat]);