diff --git a/js/fc.js b/js/fc.js index 5866d2865..72b453e12 100644 --- a/js/fc.js +++ b/js/fc.js @@ -49,6 +49,8 @@ var FC = { SERVO_DATA: null, GPS_DATA: null, ADSB_VEHICLES: null, + ADSB_LIMITS: null, + ADSB_WARNING_ICAO: null, MISSION_PLANNER: null, ANALOG: null, ARMING_CONFIG: null, @@ -289,6 +291,17 @@ var FC = { vehicles: [] }; + this.ADSB_LIMITS = { + adsb_distance_alert: 0, + adsb_distance_warning: 0, + adsb_ignore_plane_above_me_limit: 0, + }; + + this.ADSB_WARNING_ICAO = { + icao: 0, + isAlert: 0, + }; + this.MISSION_PLANNER = new WaypointCollection(); this.ANALOG = { diff --git a/js/msp/MSPCodes.js b/js/msp/MSPCodes.js index e05d86b7a..7c7d8462e 100644 --- a/js/msp/MSPCodes.js +++ b/js/msp/MSPCodes.js @@ -237,6 +237,8 @@ var MSPCodes = { MSP2_INAV_SELECT_MIXER_PROFILE: 0x2080, MSP2_ADSB_VEHICLE_LIST: 0x2090, + MSP2_ADSB_LIMITS: 0x2091, + MSP2_ADSB_WARNING_VEHICLE_ICAO: 0x2092, MSP2_INAV_CUSTOM_OSD_ELEMENTS: 0x2100, MSP2_INAV_CUSTOM_OSD_ELEMENT: 0x2101, diff --git a/js/msp/MSPHelper.js b/js/msp/MSPHelper.js index fd1bbea3f..9cf1a2df9 100644 --- a/js/msp/MSPHelper.js +++ b/js/msp/MSPHelper.js @@ -234,7 +234,16 @@ var mspHelper = (function () { FC.ADSB_VEHICLES.vehicles.push(vehicle); } break; - + case MSPCodes.MSP2_ADSB_LIMITS: + FC.ADSB_LIMITS.adsb_distance_warning = data.getUint16(0, true); + FC.ADSB_LIMITS.adsb_distance_alert = data.getUint16(2, true); + FC.ADSB_LIMITS.adsb_ignore_plane_above_me_limit = data.getUint16(4, true); + break; + case MSPCodes.MSP2_ADSB_WARNING_VEHICLE_ICAO: + FC.ADSB_WARNING_ICAO = {} + FC.ADSB_WARNING_ICAO.icao = data.getUint32(0, true); + FC.ADSB_WARNING_ICAO.isAlert = data.getUint8(4, true); + break; case MSPCodes.MSP_ATTITUDE: FC.SENSOR_DATA.kinematics[0] = data.getInt16(0, true) / 10.0; // x FC.SENSOR_DATA.kinematics[1] = data.getInt16(2, true) / 10.0; // y @@ -2947,6 +2956,14 @@ var mspHelper = (function () { MSP.send_message(MSPCodes.MSPV2_INAV_MISC, false, false, callback); }; + self.loadADSBLimits = function (callback) { + MSP.send_message(MSPCodes.MSP2_ADSB_LIMITS, false, false, callback); + }; + + self.loadADSBWarningIcao = function (callback) { + MSP.send_message(MSPCodes.MSP2_ADSB_WARNING_VEHICLE_ICAO, false, false, callback); + }; + self.loadOutputMapping = function (callback) { console.warn('Warning: self.loadOutputMapping is obsolete and may be removed in future versions. Please update usage.'); MSP.send_message(MSPCodes.MSPV2_INAV_OUTPUT_MAPPING, false, false, callback); diff --git a/locale/en/messages.json b/locale/en/messages.json index d431c66f7..fd31ee91f 100644 --- a/locale/en/messages.json +++ b/locale/en/messages.json @@ -6215,6 +6215,39 @@ "adsbHeartbeatTotalMessages": { "message": "Heartbeat msgs" }, + "adsbCallsign": { + "message": "Callsign" + }, + "adsbAsl": { + "message": "Asl (m)" + }, + "adsbHeading": { + "message": "Heading°" + }, + "adsbType": { + "message": "Type" + }, + "adsbAlt": { + "message": "Alt" + }, + "adsbEmitter": { + "message": "Emitter" + }, + "adsbAlert": { + "message": "Alert" + }, + "adsbWarning": { + "message": "Warning" + }, + "adsbNoWarning": { + "message": "None" + }, + "adsbWarningIcao": { + "message": "Alert/Warning ICAO" + }, + "adsbWarningType": { + "message": "Alert/Warning type" + }, "currentLanguage": { "message": "en" }, diff --git a/src/css/tabs/gps.css b/src/css/tabs/gps.css index ad1c9a935..042bddb7b 100644 --- a/src/css/tabs/gps.css +++ b/src/css/tabs/gps.css @@ -1,3 +1,23 @@ + +.tab-gps .gps_content_wrapper { + flex: 1; + display: flex; + min-height: 0; +} + +.tab-gps .cf_column.twothird { + display: flex; + flex-direction: column; + min-height: 0; +} + +.tab-gps .gui_box.gps_map { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + .tab-gps progress { width: 100%; border-radius: 3px; @@ -44,12 +64,6 @@ margin-top: 10px; } -.tab-gps #loadmap { - height: 100%; - width: 100%; - float: left; -} - .map-button { z-index:1; position: absolute; @@ -118,7 +132,7 @@ } #gps-map { - height: 400px; + height: calc(100vh - 305px); width: 100%; float: left; position: relative; @@ -139,12 +153,55 @@ progress[value]::-webkit-progress-value { box-shadow: 0 0 3px rgba(0, 0, 0, 0.25) inset; } -@media only screen and (max-width: 1055px) , only screen and (max-device-width: 1055px) { +.adsb-table__wrapper { + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + display: block; +} - #gps-map { - height: 347px; - width: 100%; - float: left; - } +td.adsbVehicleList { + overflow: hidden; + max-width: 0; +} +.adsb-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +.adsb-table__header { + background: #e4e4e4; + color: #000000; + padding: 4px 8px; + border: 1px solid #ccc; + text-align: center; + white-space: nowrap; +} + +.adsb-table__cell { + padding: 3px 8px; + border: 1px solid #ccc; + text-align: center; + color: #252525; + background-color: #f9f9f9; +} + +.adsb-table__row--alert .adsb-table__cell { + background-color: #f66464; +} + +.adsb-table__row--warning .adsb-table__cell { + background-color: #fdbea9; +} + +.adsb-table__row--stale { + opacity: 0.5; +} + +.adsb-table__empty { + text-align: center; + padding: 8px; + color: #323232; } diff --git a/tabs/gps.html b/tabs/gps.html index a81c76e78..d36f889cd 100644 --- a/tabs/gps.html +++ b/tabs/gps.html @@ -187,6 +187,18 @@ 0 + + + + + + + + + + + + diff --git a/tabs/gps.js b/tabs/gps.js index 433ec2be0..c12314ae3 100644 --- a/tabs/gps.js +++ b/tabs/gps.js @@ -12,8 +12,10 @@ import Style from 'ol/style/Style' import Icon from 'ol/style/Icon'; import Text from 'ol/style/Text'; import Fill from 'ol/style/Fill'; +import Stroke from 'ol/style/Stroke'; import Point from 'ol/geom/Point.js'; import Feature from 'ol/Feature'; +import { circular } from 'ol/geom/Polygon'; import VectorSource from 'ol/source/Vector.js'; import VectorLayer from 'ol/layer/Vector.js'; @@ -71,7 +73,8 @@ TABS.gps.initialize = function (callback) { var loadChain = [ mspHelper.loadFeatures, mspHelper.loadSerialPorts, - mspHelper.loadMiscV2 + mspHelper.loadMiscV2, + mspHelper.loadADSBLimits, ]; loadChainer.setChain(loadChain); @@ -130,6 +133,92 @@ TABS.gps.initialize = function (callback) { let vehiclesCursorInitialized = false; let arrowIcon; + function renderAdsbListTable() { + // Create table only if it doesn't exist yet + if ($('.adsbVehicleList').find('.adsb-table').length === 0) { + var $table = $('').addClass('adsb-table'); + var $thead = $(''); + var $headerRow = $(''); + [ + i18n.getMessage('adsbCallsign'), + 'ICAO', + i18n.getMessage('gpsLat'), + i18n.getMessage('gpsLon'), + i18n.getMessage('adsbAlt'), + i18n.getMessage('adsbHeading'), + 'TSLC', + 'TTL', + i18n.getMessage('adsbEmitter') + ].forEach(function(col) { + $headerRow.append($('').addClass('adsb-table__body')); + + var $wrapper = $('
').addClass('adsb-table__wrapper').append($table); + $('.adsbVehicleList').empty().append($wrapper); + } + + var $tbody = $('.adsb-table__body'); + + if (FC.ADSB_VEHICLES.vehicles.length === 0) { + $tbody.empty(); + $tbody.append( + $('
').append( + $(''); + cells.forEach(function() { + $row.append($('
').addClass('adsb-table__header').text(col)); + }); + $thead.append($headerRow); + $table.append($thead); + $table.append($('
').attr('colspan', 9).addClass('adsb-table__empty').text('No vehicles') + ) + ); + return; + } + + // Remove extra rows if vehicle count decreased + var existingRows = $tbody.find('tr'); + if (existingRows.length > FC.ADSB_VEHICLES.vehicles.length) { + existingRows.slice(FC.ADSB_VEHICLES.vehicles.length).remove(); + } + + FC.ADSB_VEHICLES.vehicles.forEach(function(v, i) { + var cells = [ + v.callsign || '—', + '0x' + (v.icao >>> 0).toString(16).toUpperCase().padStart(6, '0'), + (v.lat / 1e7).toFixed(5), + (v.lon / 1e7).toFixed(5), + (v.altCM / 100).toFixed(0) + 'm', + v.headingDegrees + '°', + v.tslc + 's', + v.ttl, + v.emitterType + ]; + + var isAlert = FC.ADSB_WARNING_ICAO.icao == v.icao && FC.ADSB_WARNING_ICAO.isAlert == 1; + var isWarning = FC.ADSB_WARNING_ICAO.icao == v.icao && FC.ADSB_WARNING_ICAO.isAlert != 1; + var rowClass = isAlert ? 'adsb-table__row--alert' : isWarning ? 'adsb-table__row--warning' : 'adsb-table__row--normal'; + + var $row = $tbody.find('tr').eq(i); + + if ($row.length === 0) { + // Row doesn't exist yet — create it + $row = $('
').addClass('adsb-table__cell')); + }); + $tbody.append($row); + } + + // Update classes and cell values without rebuilding the DOM + $row.removeClass('adsb-table__row--alert adsb-table__row--warning adsb-table__row--normal adsb-table__row--stale'); + $row.addClass(rowClass); + if (v.tslc > 10) $row.addClass('adsb-table__row--stale'); + + $row.find('td').each(function(j) { + $(this).text(cells[j]); + }); + }); + + $('.adsbVehicleListRow').show(); + } + async function process_html(settingsPromise) { // Wait for settings to finish loading to avoid race conditions // where user changes are overwritten by background setting loads @@ -434,14 +523,15 @@ TABS.gps.initialize = function (callback) { if (feature && feature.get('data') && feature.get('name')) { TABS.gps.toolboxAdsbVehicle.setContent( - `callsign: ` + feature.get('name') + `
` - + `lat: `+ (feature.get('data').lat / 10000000) + `
` - + `lon: `+ (feature.get('data').lon / 10000000) + `
` - + `ASL: `+ (feature.get('data').altCM ) / 100 + `m
` - + `heading: `+ feature.get('data').headingDegrees + `°
` - + `type: `+ ADSB_VEHICLE_TYPE[feature.get('data').emitterType].name + `` + `ICAO: 0x${(feature.get('data').icao >>> 0).toString(16).toUpperCase().padStart(6, '0')}
+ ${i18n.getMessage('adsbCallsign')}: ${feature.get('name')}
+ ${i18n.getMessage('gpsLat')}: ${feature.get('data').lat / 10000000}
+ ${i18n.getMessage('gpsLon')}: ${feature.get('data').lon / 10000000}
+ ${i18n.getMessage('adsbAsl')}: ${(feature.get('data').altCM) / 100}m
+ ${i18n.getMessage('adsbHeading')}: ${feature.get('data').headingDegrees}°
+ ${i18n.getMessage('adsbType')}: ${ADSB_VEHICLE_TYPE[feature.get('data').emitterType].name}` ).open(); - }else{ + } else { TABS.gps.toolboxAdsbVehicle.close(); } }); @@ -463,7 +553,15 @@ TABS.gps.initialize = function (callback) { } function get_raw_adsb_data() { - MSP.send_message(MSPCodes.MSP2_ADSB_VEHICLE_LIST, false, false, update_adsb_ui); + MSP.send_message(MSPCodes.MSP2_ADSB_VEHICLE_LIST, false, false, get_adsb_warning); + } + + function get_adsb_warning() { + if(FC.ADSB_VEHICLES.vehiclesCount > 0) { + MSP.send_message(MSPCodes.MSP2_ADSB_WARNING_VEHICLE_ICAO, false, false, update_adsb_ui); + } else { + update_adsb_ui(); + } } function update_gps_ui() { @@ -550,6 +648,20 @@ TABS.gps.initialize = function (callback) { $('.adsbVehicleTotalMessages').html(FC.ADSB_VEHICLES.vehiclePacketCount); $('.adsbHeartbeatTotalMessages').html(FC.ADSB_VEHICLES.heartbeatPacketCount); + if(FC.ADSB_VEHICLES.vehiclePacketCount > 0){ + $('.adsbWarningIcao').html('0x' + (FC.ADSB_WARNING_ICAO.icao >>> 0).toString(16).toUpperCase().padStart(6, '0')); + $('.adsbWarningType').html(FC.ADSB_WARNING_ICAO.icao != 0 ? (FC.ADSB_WARNING_ICAO.isAlert == 1 ? i18n.getMessage('adsbAlert') : i18n.getMessage('adsbWarning')) : i18n.getMessage('adsbNoWarning')); + + $('.adsbWarningIcaoRow').show(); + $('.adsbWarningTypeRow').show(); + renderAdsbListTable(); + }else{ + $('.adsbWarningIcaoRow').hide(); + $('.adsbWarningTypeRow').hide(); + $('.adsbVehicleListRow').hide(); + } + + for (let key in FC.ADSB_VEHICLES.vehicles) { let vehicle = FC.ADSB_VEHICLES.vehicles[key]; @@ -572,6 +684,7 @@ TABS.gps.initialize = function (callback) { rotation: vehicle.headingDegrees * (Math.PI / 180), scale: 0.8, anchor: [0.5, 0.5], + color: ((FC.ADSB_WARNING_ICAO.icao != 0 && FC.ADSB_WARNING_ICAO.icao == vehicle.icao) ? (FC.ADSB_WARNING_ICAO.isAlert == 1 ? '#C81B1B' : '#FFA500') : '#ffffff'), src: ADSB_VEHICLE_TYPE[vehicle.emitterType].icon, })), text: new Text(({ @@ -598,6 +711,54 @@ TABS.gps.initialize = function (callback) { vehicleVectorSource.addFeature(iconFeature); } } + + ///////////////////////////////////////////////////////////////////////////////// + if (FC.GPS_DATA.fix >= 2 && vehiclesCursorInitialized && FC.ADSB_LIMITS.adsb_distance_alert > 0 && FC.ADSB_LIMITS.adsb_distance_warning > 0){ + let lat = FC.GPS_DATA.lat / 10000000; + let lon = FC.GPS_DATA.lon / 10000000; + + let circleWarning = circular( + [lon, lat], + FC.ADSB_LIMITS.adsb_distance_warning, + 64 + ); + circleWarning.transform('EPSG:4326', 'EPSG:3857'); + + let featureWarning = new Feature({ geometry: circleWarning }); + featureWarning.setStyle(new Style({ + fill: new Fill({ + color: 'rgba(255, 255, 0, 0.15)' + }), + stroke: new Stroke({ + color: '#ffff00', + width: 2 + }) + })); + + vehicleVectorSource.addFeature(featureWarning); + + let circleAlert = circular( + [lon, lat], + FC.ADSB_LIMITS.adsb_distance_alert, + 64 + ); + + circleAlert.transform('EPSG:4326', 'EPSG:3857'); + + let featureAlert = new Feature({ geometry: circleAlert }); + featureAlert.setStyle(new Style({ + fill: new Fill({ + color: 'rgba(255, 0, 0, 0.15)' + }), + stroke: new Stroke({ + color: '#ff0000', + width: 2 + }) + })); + + vehicleVectorSource.addFeature(featureAlert); + } +/////////////////////////////////////////////////////////////////////////////// } /*