'use strict'; //////////////////////////////////// // // global Parameters definition // //////////////////////////////////// // MultiWii NAV Protocol var MWNP = MWNP || {}; // WayPoint type MWNP.WPTYPE = { WAYPOINT: 1, POSHOLD_UNLIM: 2, POSHOLD_TIME: 3, RTH: 4, SET_POI: 5, JUMP: 6, SET_HEAD: 7, LAND: 8 }; // Reverse WayPoint type dictionary function swap(dict) { let rev_dict = {}; for (let key in dict) { rev_dict[dict[key]] = key; } return rev_dict; } MWNP.WPTYPE.REV = swap(MWNP.WPTYPE); // Dictionary of Parameter1,2,3 definition depending on type of action selected (refer to MWNP.WPTYPE) var dictOfLabelParameterPoint = { 1: {parameter1: 'Speed (cm/s)', parameter2: '', parameter3: 'Sea level Ref'}, 2: {parameter1: '', parameter2: '', parameter3: ''}, 3: {parameter1: 'Wait time (s)', parameter2: 'Speed (cm/s)', parameter3: 'Sea level Ref'}, 4: {parameter1: 'Force land (non zero)', parameter2: '', parameter3: ''}, 5: {parameter1: '', parameter2: '', parameter3: ''}, 6: {parameter1: 'Target WP number', parameter2: 'Number of repeat (-1: infinite)', parameter3: ''}, 7: {parameter1: 'Heading (deg)', parameter2: '', parameter3: ''}, 8: {parameter1: '', parameter2: '', parameter3: 'Sea level Ref'} }; var waypointOptions = ['JUMP','SET_HEAD','RTH']; //////////////////////////////////// // // Tab mission control block // //////////////////////////////////// TABS.mission_control = {}; TABS.mission_control.isYmapLoad = false; TABS.mission_control.initialize = function (callback) { let cursorInitialized = false; let curPosStyle; let curPosGeo; let rthGeo; let breadCrumbLS; let breadCrumbFeature; let breadCrumbStyle; let breadCrumbSource; let breadCrumbVector; let textStyle; let textFeature; var textGeom; let isOffline = false; let rthUpdateInterval = 0; if (GUI.active_tab != 'mission_control') { GUI.active_tab = 'mission_control'; googleAnalytics.sendAppView('Mission Control'); } if (CONFIGURATOR.connectionValid) { var loadChainer = new MSPChainerClass(); loadChainer.setChain([ mspHelper.getMissionInfo, //mspHelper.loadWaypoints, //mspHelper.loadSafehomes ]); loadChainer.setExitPoint(loadHtml); loadChainer.execute(); } else { // FC not connected, load page anyway loadHtml(); } function loadHtml() { GUI.load("./tabs/mission_control.html", process_html); } function process_html() { // set GUI for offline operations if (!CONFIGURATOR.connectionValid) { $('#infoAvailablePoints').hide(); $('#infoMissionValid').hide(); $('#loadMissionButton').hide(); $('#saveMissionButton').hide(); $('#loadEepromMissionButton').hide(); $('#saveEepromMissionButton').hide(); isOffline = true; } $safehomesTable = $('.safehomesTable'); $safehomesTableBody = $('#safehomesTableBody'); $waypointOptionsTable = $('.waypointOptionsTable'); $waypointOptionsTableBody = $('#waypointOptionsTableBody'); 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(); } localize(); function get_raw_gps_data() { MSP.send_message(MSPCodes.MSP_RAW_GPS, false, false, get_comp_gps_data); } function get_comp_gps_data() { MSP.send_message(MSPCodes.MSP_COMP_GPS, false, false, get_altitude_data); } function get_altitude_data() { MSP.send_message(MSPCodes.MSP_ALTITUDE, false, false, get_attitude_data); } function get_attitude_data() { MSP.send_message(MSPCodes.MSP_ATTITUDE, false, false, update_gpsTrack); } function update_gpsTrack() { let lat = GPS_DATA.lat / 10000000; let lon = GPS_DATA.lon / 10000000; //Update map if (GPS_DATA.fix >= 2) { if (!cursorInitialized) { cursorInitialized = true; ///////////////////////////////////// //create layer for current position curPosStyle = new ol.style.Style({ image: new ol.style.Icon(({ anchor: [0.5, 0.5], opacity: 1, scale: 0.6, src: '../images/icons/icon_mission_airplane.png' })) }); let currentPositionLayer; curPosGeo = new ol.geom.Point(ol.proj.fromLonLat([lon, lat])); let curPosFeature = new ol.Feature({ geometry: curPosGeo }); curPosFeature.setStyle(curPosStyle); let vectorSource = new ol.source.Vector({ features: [curPosFeature] }); currentPositionLayer = new ol.layer.Vector({ source: vectorSource }); /////////////////////////// //create layer for RTH Marker let rthStyle = new ol.style.Style({ image: new ol.style.Icon(({ anchor: [0.5, 1.0], opacity: 1, scale: 0.5, src: '../images/icons/cf_icon_RTH.png' })) }); rthGeo = new ol.geom.Point(ol.proj.fromLonLat([90, 0])); let rthFeature = new ol.Feature({ geometry: rthGeo }); rthFeature.setStyle(rthStyle); let rthVector = new ol.source.Vector({ features: [rthFeature] }); let rthLayer = new ol.layer.Vector({ source: rthVector }); ////////////////////////////// //create layer for bread crumbs breadCrumbLS = new ol.geom.LineString([ol.proj.fromLonLat([lon, lat]), ol.proj.fromLonLat([lon, lat])]); breadCrumbStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ffcc33', width: 6 }) }); breadCrumbFeature = new ol.Feature({ geometry: breadCrumbLS }); breadCrumbFeature.setStyle(breadCrumbStyle); breadCrumbSource = new ol.source.Vector({ features: [breadCrumbFeature] }); breadCrumbVector = new ol.layer.Vector({ source: breadCrumbSource }); ///////////////////////////// //create layer for heading, alt, groundspeed textGeom = new ol.geom.Point([0,0]); textStyle = new ol.style.Style({ text: new ol.style.Text({ font: 'bold 35px Calibri,sans-serif', fill: new ol.style.Fill({ color: '#fff' }), offsetX: map.getSize()[0]-260, offsetY: 80, textAlign: 'left', backgroundFill: new ol.style.Fill({ color: '#000' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }), text: 'H: XXX\nAlt: XXXm\nSpeed: XXXcm/s' }) }); textFeature = new ol.Feature({ geometry: textGeom }); textFeature.setStyle(textStyle); var textSource = new ol.source.Vector({ features: [textFeature] }); var textVector = new ol.layer.Vector({ source: textSource }); map.addLayer(rthLayer); map.addLayer(breadCrumbVector); map.addLayer(currentPositionLayer); map.addControl(textVector); } let gpsPos = ol.proj.fromLonLat([lon, lat]); curPosGeo.setCoordinates(gpsPos); breadCrumbLS.appendCoordinate(gpsPos); var coords = breadCrumbLS.getCoordinates(); if(coords.length > 100) { coords.shift(); breadCrumbLS.setCoordinates(coords); } curPosStyle.getImage().setRotation((SENSOR_DATA.kinematics[2]/360.0) * 6.28318); //update data text textGeom.setCoordinates(map.getCoordinateFromPixel([0,0])); let tmpText = textStyle.getText(); tmpText.setText(' \n' + 'H: ' + SENSOR_DATA.kinematics[2] + '\nAlt: ' + SENSOR_DATA.altitude + 'm\nSpeed: ' + GPS_DATA.speed + 'cm/s\n' + 'Dist: ' + GPS_DATA.distanceToHome + 'm'); //update RTH every 5th GPS update since it really shouldn't change if(rthUpdateInterval >= 5) { MISSION_PLANNER.bufferPoint.number = -1; //needed to get point 0 which id RTH MSP.send_message(MSPCodes.MSP_WP, mspHelper.crunch(MSPCodes.MSP_WP), false, function rth_update() { var coord = ol.proj.fromLonLat([MISSION_PLANNER.bufferPoint.lon, MISSION_PLANNER.bufferPoint.lat]); rthGeo.setCoordinates(coord); }); rthUpdateInterval = 0; } rthUpdateInterval++; } } /* * enable data pulling if not offline * Refreshing data at 5Hz... Could slow this down if we have performance issues */ if(!isOffline) { helper.mspBalancedInterval.add('gps_pull', 200, 3, function gps_update() { // avoid usage of the GPS commands until a GPS sensor is detected for targets that are compiled without GPS support. if (!have_sensor(CONFIG.activeSensors, 'gps')) { update_gpsTrack(); return; } if (helper.mspQueue.shouldDrop()) { return; } get_raw_gps_data(); }); } GUI.content_ready(callback); } /////////////////////////////////////////////// // // define & init parameters // /////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// // define & init parameters for Map Layer ////////////////////////////////////////////////////////////////////////////////////////////// var markers = []; // Layer for Waypoints var lines = []; // Layer for lines between waypoints var safehomeMarkers = []; // layer for Safehome points var map; ////////////////////////////////////////////////////////////////////////////////////////////// // define & init parameters for Selected Marker ////////////////////////////////////////////////////////////////////////////////////////////// var selectedMarker = null; var selectedFeature = null; var tempMarker = null; var disableMarkerEdit = false; ////////////////////////////////////////////////////////////////////////////////////////////// // define & init parameters for default Settings ////////////////////////////////////////////////////////////////////////////////////////////// var vMaxDistSH = 0; var settings = {}; if (CONFIGURATOR.connectionValid) { mspHelper.getSetting("safehome_max_distance").then(function (s) { if (s) { vMaxDistSH = Number(s.value)/100; settings = { speed: 0, alt: 5000, safeRadiusSH : 50, maxDistSH : vMaxDistSH}; } else { vMaxDistSH = 0; settings = { speed: 0, alt: 5000, safeRadiusSH : 50, maxDistSH : vMaxDistSH}; } }); } else { vMaxDistSH = 0; settings = { speed: 0, alt: 5000, safeRadiusSH : 50, maxDistSH : vMaxDistSH}; } ////////////////////////////////////////////////////////////////////////////////////////////// // define & init Waypoints parameters ////////////////////////////////////////////////////////////////////////////////////////////// var mission = new WaypointCollection(); ////////////////////////////////////////////////////////////////////////////////////////////// // define & init Multi Mission parameters ////////////////////////////////////////////////////////////////////////////////////////////// var multimission = new WaypointCollection(); var multimissionCount = 0; var maxMultimissionCount = 9; ////////////////////////////////////////////////////////////////////////////////////////////// // define & init home parameters ////////////////////////////////////////////////////////////////////////////////////////////// var HOME = new Waypoint(0,0,0,0); var homeMarkers =[]; // layer for home point ////////////////////////////////////////////////////////////////////////////////////////////// // define & init Safehome parameters ////////////////////////////////////////////////////////////////////////////////////////////// //var SAFEHOMES = new SafehomeCollection(); // TO COMMENT FOR RELEASE : DECOMMENT FOR DEBUG //SAFEHOMES.inflate(); // TO COMMENT FOR RELEASE : DECOMMENT FOR DEBUG //var safehomeRangeRadius = 200; //meters //var safehomeSafeRadius = 50; //meters ///////////////////////////////////////////// // // Reinit Jquery Form // ///////////////////////////////////////////// function clearEditForm() { $('#pointLat').val(''); $('#pointLon').val(''); $('#pointAlt').val(''); $('#pointP1').val(''); $('#pointP2').val(''); $('#pointP3').val(''); $('#missionDistance').text(0); $('#MPeditPoint').fadeOut(300); } function clearFilename() { $('#missionFilename').text(''); } ///////////////////////////////////////////// // // Manage Settings // ///////////////////////////////////////////// function loadSettings() { chrome.storage.local.get('missionPlannerSettings', function (result) { if (result.missionPlannerSettings) { settings = result.missionPlannerSettings; } refreshSettings(); }); } function saveSettings() { chrome.storage.local.set({'missionPlannerSettings': settings}); } function refreshSettings() { $('#MPdefaultPointAlt').val(String(settings.alt)); $('#MPdefaultPointSpeed').val(String(settings.speed)); $('#MPdefaultSafeRangeSH').val(String(settings.safeRadiusSH)); } function closeSettingsPanel() { $('#missionPlannerSettings').hide(); } ///////////////////////////////////////////// // // Manage Safehome // ///////////////////////////////////////////// function closeSafehomePanel() { $('#missionPlannerSafehome').hide(); cleanSafehomeLayers(); } function renderSafehomesTable() { /* * Process safehome table UI */ let safehomes = SAFEHOMES.get(); $safehomesTableBody.find("*").remove(); for (let safehomeIndex in safehomes) { if (safehomes.hasOwnProperty(safehomeIndex)) { const safehome = safehomes[safehomeIndex]; $safehomesTableBody.append('\ \
\ \
\ \ \ \ \ \ \ '); const $row = $safehomesTableBody.find('tr:last'); $row.find(".safehome-number").text(safehome.getNumber()+1); $row.find(".safehome-enabled-value").prop('checked',safehome.isUsed()).change(function () { safehome.setEnabled((($(this).prop('checked')) ? 1 : 0)); SAFEHOMES.updateSafehome(safehome); cleanSafehomeLayers(); renderSafehomesOnMap(); }); $row.find(".safehome-lon").val(safehome.getLonMap()).change(function () { safehome.setLon(Math.round(Number($(this).val()) * 10000000)); SAFEHOMES.updateSafehome(safehome); cleanSafehomeLayers(); renderSafehomesOnMap(); }); $row.find(".safehome-lat").val(safehome.getLatMap()).change(function () { safehome.setLat(Math.round(Number($(this).val()) * 10000000)); SAFEHOMES.updateSafehome(safehome); cleanSafehomeLayers(); renderSafehomesOnMap(); }); $row.find("[data-role='safehome-center']").attr("data-index", safehomeIndex); } } GUI.switchery(); localize(); } function renderSafehomesOnMap() { /* * Process safehome on Map */ SAFEHOMES.get().forEach(function (safehome) { map.addLayer(addSafeHomeMarker(safehome)); }); } function cleanSafehomeLayers() { for (var i in safehomeMarkers) { map.removeLayer(safehomeMarkers[i]); } safehomeMarkers = []; } function getSafehomeIcon(safehome) { /* * Process Safehome Icon */ return new ol.style.Style({ image: new ol.style.Icon(({ anchor: [0.5, 1], opacity: 1, scale: 0.5, src: '../images/icons/cf_icon_safehome' + (safehome.isUsed() ? '_used' : '')+ '.png' })), text: new ol.style.Text(({ text: String(Number(safehome.getNumber())+1), font: '12px sans-serif', offsetY: -15, offsetX: -2, fill: new ol.style.Fill({ color: '#FFFFFF' }), stroke: new ol.style.Stroke({ color: '#FFFFFF' }), })) }); } function addSafeHomeMarker(safehome) { /* * add safehome on Map */ let coord = ol.proj.fromLonLat([safehome.getLonMap(), safehome.getLatMap()]); var iconFeature = new ol.Feature({ geometry: new ol.geom.Point(coord), name: 'safehome' }); //iconFeature.setStyle(getSafehomeIcon(safehome, safehome.isUsed())); let circleStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgba(144, 12, 63, 0.5)', width: 3, lineDash : [10] }), // fill: new ol.style.Fill({ // color: 'rgba(251, 225, 155, 0.1)' // }) }); let circleSafeStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgba(136, 204, 62, 1)', width: 3, lineDash : [10] }), /* fill: new ol.style.Fill({ color: 'rgba(136, 204, 62, 0.1)' }) */ }); var vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ features: [iconFeature] }), style : function(iconFeature) { let styles = [getSafehomeIcon(safehome)]; if (safehome.isUsed()) { circleStyle.setGeometry(new ol.geom.Circle(iconFeature.getGeometry().getCoordinates(), getProjectedRadius(settings.maxDistSH))); circleSafeStyle.setGeometry(new ol.geom.Circle(iconFeature.getGeometry().getCoordinates(), getProjectedRadius(Number(settings.safeRadiusSH)))); styles.push(circleSafeStyle); styles.push(circleStyle); } return styles; } }); vectorLayer.kind = "safehome"; vectorLayer.number = safehome.getNumber(); vectorLayer.selection = false; safehomeMarkers.push(vectorLayer); return vectorLayer; } function getProjectedRadius(radius) { let projection = map.getView().getProjection(); let resolutionAtEquator = map.getView().getResolution(); let resolutionRate = resolutionAtEquator / ol.proj.getPointResolution(projection, resolutionAtEquator, map.getView().getCenter()); let radiusProjected = (radius / ol.proj.METERS_PER_UNIT.m) * resolutionRate; return radiusProjected; } ///////////////////////////////////////////// // // Manage Take Off Home // ///////////////////////////////////////////// function closeHomePanel() { $('#missionPlannerHome').hide(); $('#missionPlannerElevation').hide(); cleanHomeLayers(); } function cleanHomeLayers() { for (var i in homeMarkers) { map.removeLayer(homeMarkers[i]); } homeMarkers = []; } function renderHomeTable() { /* * Process home table UI */ $(".home-lat").val(HOME.getLatMap()).change(function () { HOME.setLat(Math.round(Number($(this).val()) * 10000000)); cleanHomeLayers(); renderHomeOnMap(); }); $(".home-lon").val(HOME.getLonMap()).change(function () { HOME.setLon(Math.round(Number($(this).val()) * 10000000)); cleanHomeLayers(); renderHomeOnMap(); }); if (HOME.getLatMap() == 0 && HOME.getLonMap() == 0) { HOME.setAlt("N/A"); } else { (async () => { const elevationAtHome = await HOME.getElevation(globalSettings); $('#elevationValueAtHome').text(elevationAtHome+' m'); HOME.setAlt(elevationAtHome); })() } if (globalSettings.mapProviderType == 'bing') { $('#elevationEarthModelclass').fadeIn(300); } else { $('#elevationEarthModelclass').fadeOut(300); } } function renderHomeOnMap() { /* * Process home on Map */ map.addLayer(addHomeMarker(HOME)); } function addHomeMarker(home) { /* * add safehome on Map */ let coord = ol.proj.fromLonLat([home.getLonMap(), home.getLatMap()]); var iconFeature = new ol.Feature({ geometry: new ol.geom.Point(coord), name: 'home' }); //iconFeature.setStyle(getSafehomeIcon(safehome, safehome.isUsed())); var vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ features: [iconFeature] }), style : function(iconFeature) { let styles = [getHomeIcon(home)]; return styles; } }); vectorLayer.kind = "home"; vectorLayer.number = home.getNumber(); vectorLayer.selection = false; homeMarkers.push(vectorLayer); return vectorLayer; } function getHomeIcon(home) { /* * Process Safehome Icon */ return new ol.style.Style({ image: new ol.style.Icon(({ anchor: [0.5, 1], opacity: 1, scale: 0.5, src: '../images/icons/cf_icon_home.png' })), }); } function updateHome() { renderHomeTable(); cleanHomeLayers(); renderHomeOnMap(); plotElevation(); } ///////////////////////////////////////////// // // Manage Multi Mission // ///////////////////////////////////////////// /* Multi Mission working method: * 'multimission' waypoint collection is a repository for all multi missions. * 'mission' WP collection remains as the WP source for the map display. * All missions can be displayed on the map or only a single mission. With all missions displayed 'mission' and * 'multimission' are copies containing all missions. When a single mission is displayed 'multimission' contains all * missions except the currently displayed mission. * On update to display all missions the current dislayed mission is merged back into 'multimission' and 'mission' * updated as a copy of 'multimission'. * When all missions are displayed WP data can be viewed but mission edit is disabled. * Mission WPs can be edited only when a single mission is loaded on the map. */ var startWPCount = 0; function renderMultimissionTable() { $('#multimissionOptionList').prop('options').length = 1; for (var i = 1; i <= multimissionCount; i++) { $('#multimissionOptionList').append($('