1# ============================================================================== 2# Boeing Navigation Display by Gijs de Rooy 3# See: http://wiki.flightgear.org/Canvas_ND_Framework 4# ============================================================================== 5 6 7 8## 9# this file contains a hash that declares features in a generic fashion 10# we want to get rid of the bloated update() method sooner than later 11# PLEASE DO NOT ADD any code to update() !! 12# Instead, help clean up the file and move things over to the navdisplay.styles file 13# 14# This is the only sane way to keep on generalizing the framework, so that we can 15# also support different makes/models of NDs in the future 16# 17# a huge bloated update() method is going to make that basically IMPOSSIBLE 18# 19io.include("Nasal/canvas/map/navdisplay.styles"); 20 21## 22# encapsulate hdg/lat/lon source, so that the ND may also display AI/MP aircraft in a pilot-view at some point (aka stress-testing) 23# TODO: this predates aircraftpos.controller (MapStructure) should probably be unified to some degree ... 24 25var wxr_live_tree = "/instrumentation/wxr"; 26 27var NDSourceDriver = {}; 28NDSourceDriver.new = func { 29 var m = {parents:[NDSourceDriver]}; 30 m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg"); 31 m.get_hdg_tru= func getprop("/orientation/heading-deg"); 32 m.get_hgg = func getprop("instrumentation/afds/settings/heading"); 33 m.get_trk_mag= func 34 { 35 if(getprop("/velocities/groundspeed-kt") > 80) 36 getprop("/orientation/track-magnetic-deg"); 37 else 38 getprop("/orientation/heading-magnetic-deg"); 39 }; 40 m.get_trk_tru = func 41 { 42 if(getprop("/velocities/groundspeed-kt") > 80) 43 getprop("/orientation/track-deg"); 44 else 45 getprop("/orientation/heading-deg"); 46 }; 47 m.get_lat= func getprop("/position/latitude-deg"); 48 m.get_lon= func getprop("/position/longitude-deg"); 49 m.get_spd= func getprop("/instrumentation/airspeed-indicator/true-speed-kt"); 50 m.get_gnd_spd= func getprop("/velocities/groundspeed-kt"); 51 m.get_vspd= func getprop("/velocities/vertical-speed-fps"); 52 return m; 53} 54 55## 56# configure aircraft specific cockpit switches here 57# these are some defaults, can be overridden when calling NavDisplay.new() - 58# see the 744 ND.nas file the backend code should never deal directly with 59# aircraft specific properties using getprop. 60# To get started implementing your own ND, just copy the switches hash to your 61# ND.nas file and map the keys to your cockpit properties - and things will just work. 62 63# TODO: switches are ND specific, so move to the NDStyle hash! 64 65var default_switches = { 66 'toggle_range': {path: '/inputs/range-nm', value:40, type:'INT'}, 67 'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'}, 68 'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'}, 69 'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'}, 70 'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'}, 71 'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'}, 72 'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'}, 73 'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'}, 74 'toggle_traffic': {path: '/inputs/tfc',value:0, type:'BOOL'}, 75 'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'}, 76 'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'}, 77 'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'}, 78 'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR 79 'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD 80 'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'}, 81 'toggle_rangearc': {path: '/mfd/rangearc', value:0, type:'BOOL'}, 82 'toggle_track_heading':{path: '/trk-selected', value:0, type:'BOOL'}, 83 'toggle_weather_live': {path: '/mfd/wxr-live-enabled', value: 0, type: 'BOOL'}, 84 'toggle_chrono': {path: '/inputs/CHRONO', value: 0, type: 'INT'}, 85 'toggle_xtrk_error': {path: '/mfd/xtrk-error', value: 0, type: 'BOOL'}, 86 'toggle_trk_line': {path: '/mfd/trk-line', value: 0, type: 'BOOL'}, 87 'toggle_hdg_bug_only': {path: '/mfd/hdg-bug-only', value: 0, type: 'BOOL'}, 88}; 89 90## 91# TODO: 92# - introduce a MFD class (use it also for PFD/EICAS) 93# - introduce a SGSubsystem class and use it here 94# - introduce a Boeing NavDisplay class 95var NavDisplay = { 96 # static 97 id:0, 98 99 del: func { 100 print("Cleaning up NavDisplay"); 101 # shut down all timers and other loops here 102 me.update_timer.stop(); 103 foreach(var t; me.timers) 104 t.stop(); 105 foreach(var l; me.listeners) 106 removelistener(l); 107 # clean up MapStructure 108 me.map.del(); 109 # call(canvas.Map.del, [], me.map); 110 # destroy the canvas 111 if (me.canvas_handle != nil) 112 me.canvas_handle.del(); 113 me.inited = 0; 114 NavDisplay.id -= 1; 115 }, 116 117 addtimer: func(interval, cb) { 118 append(me.timers, var job=maketimer(interval, cb)); 119 return job; # so that we can directly work with the timer (start/stop) 120 }, 121 122 listen: func(p,c) { 123 append(me.listeners, setlistener(p,c)); 124 }, 125 126 # listeners for cockpit switches 127 listen_switch: func(s,c) { 128 # print("event setup for: ", id(c)); 129 if (!contains(me.efis_switches, s)) { 130 print('EFIS Switch not defined: '~ s); 131 return; 132 } 133 me.listen( me.get_full_switch_path(s), func { 134 # print("listen_switch triggered:", s, " callback id:", id(c) ); 135 c(); 136 }); 137 }, 138 139 # get the full property path for a given switch 140 get_full_switch_path: func (s) { 141 # debug.dump( me.efis_switches[s] ); 142 return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~ 143 }, 144 145 # helper method for getting configurable cockpit switches (which are usually different in each aircraft) 146 get_switch: func(s) { 147 var switch = me.efis_switches[s]; 148 if(switch == nil) return nil; 149 var path = me.efis_path ~ switch.path ; 150 #print(s,":Getting switch prop:", path); 151 152 return getprop( path ); 153 }, 154 155 # helper method for setting configurable cockpit switches (which are usually different in each aircraft) 156 set_switch: func(s, v) { 157 var switch = me.efis_switches[s]; 158 if(switch == nil) return nil; 159 var path = me.efis_path ~ switch.path ; 160 #print(s,":Getting switch prop:", path); 161 162 setprop( path, v ); 163 }, 164 165 # for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!) 166 connectAI: func(source=nil) { 167 me.aircraft_source = { 168 get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(), 169 get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(), 170 get_lat: func source.getNode('position/latitude-deg').getValue(), 171 get_lon: func source.getNode('position/longitude-deg').getValue(), 172 get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(), 173 get_gnd_spd: func source.getNode('velocities/groundspeed-kt').getValue(), 174 }; 175 }, # of connectAI 176 177 setTimerInterval: func(update_time=0.05) me.update_timer.restart(update_time), 178 onDisplay: func {me.setTimerInterval();}, 179 offDisplay: func {me.update_timer.stop();}, 180 # TODO: the ctor should allow customization, for different aircraft 181 # especially properties and SVG files/handles (747, 757, 777 etc) 182 new : func(prop1, switches=default_switches, style='Boeing') { 183 NavDisplay.id +=1; 184 var m = { parents : [NavDisplay]}; 185 186 var df_toggles = keys(default_switches); 187 foreach(var toggle_name; df_toggles){ 188 if(!contains(switches, toggle_name)) 189 switches[toggle_name] = default_switches[toggle_name]; 190 } 191 192 m.inited = 0; 193 194 m.timers=[]; 195 m.listeners=[]; # for cleanup handling 196 m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading) 197 198 m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc) 199 m.style_name = style; 200 201 m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies", 202 "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"]; 203 m.mfd_mode_list=["APP","VOR","MAP","PLAN"]; 204 205 m.efis_path = prop1; 206 m.efis_switches = switches; 207 208 # just an alias, to avoid having to rewrite the old code for now 209 m.rangeNm = func m.get_switch('toggle_range'); 210 211 m.efis = props.globals.initNode(prop1); 212 m.mfd = m.efis.initNode("mfd"); 213 214 # TODO: unify this with switch handling 215 m.mfd_mode_num = m.mfd .initNode("mode-num",2,"INT"); 216 m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL"); 217 m.previous_set = m.efis.initNode("inhg-previous",29.92); 218 m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL"); 219 m.kpa_output = m.efis.initNode("inhg-kpa",29.92); 220 m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92); 221 m.temp = m.efis.initNode("fixed-temp",0); 222 m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL"); 223 m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL"); 224 225 m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL"); 226 m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING"); 227 m.minimums = m.efis.initNode("minimums",250,"INT"); 228 m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height"); 229 230 # TODO: these are switches, can be unified with switch handling hash above (eventually): 231 m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", -1, "INT"); # not yet in switches hash 232 233 ### 234 # initialize all switches based on the defaults specified in the switch hash 235 # 236 foreach(var switch; keys( m.efis_switches ) ) 237 props.globals.initNode 238 ( m.get_full_switch_path (switch), 239 m.efis_switches[switch].value, 240 m.efis_switches[switch].type 241 ); 242 243 244 return m; 245 }, 246 newMFD: func(canvas_group, parent=nil, nd_options=nil, update_time=0.05) 247 { 248 if (me.inited) die("MFD already was added to scene"); 249 me.inited = 1; 250 me.range_dependant_layers = []; 251 me.always_update_layers = {}; 252 me.update_timer = maketimer(update_time, func me.update() ); 253 me.nd = canvas_group; 254 me.canvas_handle = parent; 255 me.df_options = nil; 256 if (contains(me.nd_style, 'options')) 257 me.df_options = me.nd_style.options; 258 nd_options = canvas.default_hash(nd_options, me.df_options); 259 me.options = nd_options; 260 me.route_driver = nil; 261 if (me.options == nil) me.options = {}; 262 if (contains(me.options, 'route_driver')) { 263 me.route_driver = me.options.route_driver; 264 } 265 elsif (contains(me.options, 'defaults')) { 266 if(contains(me.options.defaults, 'route_driver')) 267 me.route_driver = me.options.defaults.route_driver; 268 } 269 270 # load the specified SVG file into the me.nd group and populate all sub groups 271 272 canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper}); 273 me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here) 274 275 foreach(var feature; me.nd_style.features ) { 276 me.symbols[feature.id] = me.nd.getElementById(feature.id).updateCenter(); 277 if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter) 278 } 279 280 me.nd_style.initialize_elements(me); 281 282 283 var map_rect = [124, 1024, 1024, 0]; 284 var map_opts = me.options['map']; 285 if (map_opts == nil) map_opts = {}; 286 if (typeof(map_opts['rect']) == 'vector') 287 map_rect = map_opts.rect; 288 map_rect = string.join(', ', map_rect); 289 290 me.map = me.nd.createChild("map","map") 291 .set("clip", map_rect) 292 .set("screen-range", 700); 293 var z_idx = map_opts['z-index']; 294 if (z_idx != nil) me.map.set('z-index', z_idx); 295 296 me.update_sub(); # init some map properties based on switches 297 298 # predicate for the draw controller 299 var is_tuned = func(freq) { 300 var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz"); 301 var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz"); 302 if (freq == nav1 or freq == nav2) return 1; 303 return 0; 304 } 305 306 # another predicate for the draw controller 307 var get_course_by_freq = func(freq) { 308 if (freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz")) 309 return getprop("instrumentation/nav[0]/radials/selected-deg"); 310 else 311 return getprop("instrumentation/nav[1]/radials/selected-deg"); 312 } 313 314 var get_current_position = func { 315 delete(caller(0)[0], "me"); # remove local me, inherit outer one 316 return [ 317 me.aircraft_source.get_lat(), me.aircraft_source.get_lon() 318 ]; 319 } 320 321 # a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance 322 # the point being that draw routines don't know anything about their frontends (instrument or GUI dialog) 323 # so we need some simple way to communicate between frontend<->backend until we have real controllers 324 # for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files 325 # 326 327 var controller = { 328 parents: [canvas.Map.Controller], 329 _pos: nil, _time: nil, 330 is_tuned:is_tuned, 331 get_tuned_course:get_course_by_freq, 332 get_position: get_current_position, 333 new: func(map) return { parents:[controller], map:map }, 334 del: func() {print("cleaning up nd controller");}, 335 should_update_all: func { 336 # TODO: this is just copied from aircraftpos.controller, 337 # it really should be moved to somewhere common and reused 338 # and extended to fully differentiate between "static" 339 # and "volatile" layers. 340 var pos = me.map.getPosCoord(); 341 if (pos == nil) return 0; 342 var time = systime(); 343 if (me._pos == nil) 344 me._pos = geo.Coord.new(pos); 345 else { 346 var dist_m = me._pos.direct_distance_to(pos); 347 # 2 NM until we update again 348 if (dist_m < 2 * NM2M) return 0; 349 # Update at most every 4 seconds to avoid excessive stutter: 350 elsif (time - me._time < 4) return 0; 351 } 352 #print("update aircraft position"); 353 var (x,y,z) = pos.xyz(); 354 me._pos.set_xyz(x,y,z); 355 me._time = time; 356 return 1; 357 }, 358 }; 359 me.map.setController(controller); 360 361 ### 362 # set up various layers, controlled via callbacks in the controller hash 363 # revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated 364 365 # helper / closure generator 366 var make_event_handler = func(predicate, layer) func predicate(me, layer); 367 368 me.layers={}; # storage container for all ND specific layers 369 # look up all required layers as specified per the NDStyle hash and do the initial setup for event handling 370 var default_opts = me.options != nil and contains(me.options, 'defaults') ? me.options.defaults : nil; 371 foreach(var layer; me.nd_style.layers) { 372 if(layer['disabled']) continue; # skip this layer 373 #print("newMFD(): Setting up ND layer:", layer.name); 374 375 var the_layer = nil; 376 if(!layer['isMapStructure']) # set up an old INEFFICIENT and SLOW layer 377 the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( me.map, layer.name, controller ); 378 else { 379 logprint(canvas._MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name); 380 var opt = me.options != nil and me.options[layer.name] != nil ? me.options[layer.name] : nil; 381 if (opt == nil and contains(layer, 'options')) 382 opt = layer.options; 383 if (opt != nil and default_opts != nil) 384 opt = canvas.default_hash(opt, default_opts); 385 #elsif(default_opts != nil) 386 # opt = default_opts; 387 var style = nil; 388 if(contains(layer, 'style')) 389 style = layer.style; 390 #print("Options is: ", opt!=nil?"enabled":"disabled"); 391 #debug.dump(opt); 392 me.map.addLayer( 393 factory: canvas.SymbolLayer, 394 type_arg: layer.name, 395 opts: opt, 396 visible:0, 397 style: style, 398 priority: layer['z-index'] 399 ); 400 the_layer = me.layers[layer.name] = me.map.getLayer(layer.name); 401 if(opt != nil and contains(opt, 'range_dependant')){ 402 if(opt.range_dependant) 403 append(me.range_dependant_layers, the_layer); 404 } 405 if(contains(layer, 'always_update')) 406 me.always_update_layers[layer.name] = layer.always_update; 407 if (1) (func { 408 var l = layer; 409 var _predicate = l.predicate; 410 l.predicate = func { 411 var t = systime(); 412 call(_predicate, arg, me); 413 logprint(canvas._MP_dbg_lvl, "Took "~((systime()-t)*1000)~"ms to update layer "~l.name); 414 } 415 })(); 416 } 417 418 # now register all layer specific notification listeners and their corresponding update predicate/callback 419 # pass the ND instance and the layer handle to the predicate when it is called 420 # so that it can directly access the ND instance and its own layer (without having to know the layer's name) 421 var event_handler = make_event_handler(layer.predicate, the_layer); 422 foreach(var event; layer.update_on) { 423 # this handles timers 424 if (typeof(event)=='hash' and contains(event, 'rate_hz')) { 425 #print("FIXME: navdisplay.mfd timer handling is broken ATM"); 426 var job=me.addtimer(1/event.rate_hz, event_handler); 427 job.start(); 428 } 429 # and this listeners 430 else 431 # print("Setting up subscription:", event, " for ", layer.name, " handler id:", id(event_handler) ); 432 me.listen_switch(event, event_handler); 433 } # foreach event subscription 434 # and now update/init each layer once by calling its update predicate for initialization 435 event_handler(); 436 } # foreach layer 437 438 #print("navdisplay.mfd:ND layer setup completed"); 439 440 # start the update timer, which makes sure that the update() will be called 441 me.update_timer.start(); 442 443 # TODO: move this to RTE.lcontroller ? 444 me.listen("/autopilot/route-manager/current-wp", func(activeWp) { 445 canvas.updatewp( activeWp.getValue() ); 446 }); 447 448 }, 449 450 in_mode:func(switch, modes) 451 { 452 foreach(var m; modes) if(me.get_switch(switch)==m) return 1; 453 return 0; 454 }, 455 # Helper function for below (update()) and above (newMFD()) 456 # to ensure position etc. are correct. 457 update_sub: func() 458 { 459 # Variables: 460 var userLat = me.aircraft_source.get_lat(); 461 var userLon = me.aircraft_source.get_lon(); 462 var userGndSpd = me.aircraft_source.get_gnd_spd(); 463 var userVSpd = me.aircraft_source.get_vspd(); 464 var dispLCD = me.get_switch('toggle_display_type') == "LCD"; 465 # Heading update 466 var userHdgMag = me.aircraft_source.get_hdg_mag(); 467 var userHdgTru = me.aircraft_source.get_hdg_tru(); 468 var userTrkMag = me.aircraft_source.get_trk_mag(); 469 var userTrkTru = me.aircraft_source.get_trk_tru(); 470 471 if(me.get_switch('toggle_true_north')) { 472 var userHdg=userHdgTru; 473 me.userHdg=userHdgTru; 474 var userTrk=userTrkTru; 475 me.userTrk=userTrkTru; 476 } else { 477 var userHdg=userHdgMag; 478 me.userHdg=userHdgMag; 479 var userTrk=userTrkMag; 480 me.userTrk=userTrkMag; 481 } 482 # this should only ever happen when testing the experimental AI/MP ND driver hash (not critical) 483 # or when an error occurs (critical) 484 if (!userHdg or !userTrk or !userLat or !userLon) { 485 print("aircraft source invalid, returning !"); 486 return; 487 } 488 if (me.aircraft_source.get_gnd_spd() < 80) { 489 userTrk = userHdg; 490 me.userTrk=userHdg; 491 } 492 493 if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT") 494 or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD")) 495 { 496 userHdgTrk = userTrk; 497 me.userHdgTrk = userTrk; 498 userHdgTrkTru = userTrkTru; 499 me.symbols.hdgTrk.setText("TRK"); 500 } else { 501 userHdgTrk = userHdg; 502 me.userHdgTrk = userHdg; 503 userHdgTrkTru = userHdgTru; 504 me.symbols.hdgTrk.setText("HDG"); 505 } 506 507 # First, update the display position of the map 508 var oldRange = me.map.getRange(); 509 var pos = { 510 lat: nil, lon: nil, 511 alt: nil, hdg: nil, 512 range: nil, 513 }; 514 # reposition the map, change heading & range: 515 var pln_wpt_idx = getprop(me.efis_path ~ "/inputs/plan-wpt-index"); 516 if(me.in_mode('toggle_display_mode', ['PLAN']) and pln_wpt_idx >= 0) { 517 if(me.route_driver != nil){ 518 var wp = me.route_driver.getPlanModeWP(pln_wpt_idx); 519 if(wp != nil){ 520 pos.lat = wp.wp_lat; 521 pos.lon = wp.wp_lon; 522 } else { 523 pos.lat = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/latitude-deg"); 524 pos.lon = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/longitude-deg"); 525 } 526 } else { 527 pos.lat = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/latitude-deg"); 528 pos.lon = getprop("/autopilot/route-manager/route/wp["~pln_wpt_idx~"]/longitude-deg"); 529 } 530 } else { 531 pos.lat = userLat; 532 pos.lon = userLon; 533 } 534 if(me.in_mode('toggle_display_mode', ['PLAN'])) { 535 pos.hdg = 0; 536 pos.range = me.rangeNm()*2 537 } else { 538 pos.range = me.rangeNm(); # avoid this here, use a listener instead 539 pos.hdg = userHdgTrkTru; 540 } 541 if(me.options != nil and (var pos_callback = me.options['position_callback']) != nil) 542 pos_callback(me, pos); 543 call(me.map.setPos, [pos.lat, pos.lon], me.map, pos); 544 if(pos.range != oldRange){ 545 foreach(l; me.range_dependant_layers){ 546 l.update(); 547 } 548 } 549 }, 550 # each model should keep track of when it last got updated, using current lat/lon 551 # in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range) 552 # and update each model accordingly 553 # TODO: Hooray is still waiting for a really rainy weekend to clean up all the mess here... so plz don't add to it! 554 update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft 555 { 556 var _time = systime(); 557 # Disables WXR Live if it's not enabled. The toggle_weather_live should be common to all 558 # ND instances. 559 var wxr_live_enabled = getprop(wxr_live_tree~'/enabled'); 560 if(wxr_live_enabled == nil or wxr_live_enabled == '') 561 wxr_live_enabled = 0; 562 me.set_switch('toggle_weather_live', wxr_live_enabled); 563 564 call(me.update_sub, nil, nil, caller(0)[0]); # call this in the same namespace to "steal" its variables 565 566 # MapStructure update! 567 if (me.map.controller.should_update_all()) { 568 me.map.update(); 569 } else { 570 # TODO: ugly list here 571 # FIXME: use a VOLATILE layer helper here that handles TFC, APS, WXR etc ? 572 var update_layers = me.always_update_layers; 573 me.map.update(func(layer) contains(update_layers, layer.type)); 574 } 575 576 # Other symbol update 577 # TODO: should be refactored! 578 var translation_callback = nil; 579 if(me.options != nil) 580 translation_callback = me.options['translation_callback']; 581 if(typeof(translation_callback) == 'func'){ 582 var trsl = translation_callback(me); 583 me.map.setTranslation(trsl.x, trsl.y); 584 } else { 585 if(me.in_mode('toggle_display_mode', ['PLAN'])) 586 me.map.setTranslation(512,512); 587 elsif(me.get_switch('toggle_centered')) 588 me.map.setTranslation(512,565); 589 else 590 me.map.setTranslation(512,824); 591 } 592 593 if(me.get_switch('toggle_rh_vor_adf') == 1) { 594 me.symbols.vorR.setText("VOR R"); 595 me.symbols.vorR.setColor(0.195,0.96,0.097); 596 me.symbols.dmeR.setText("DME"); 597 me.symbols.dmeR.setColor(0.195,0.96,0.097); 598 if(getprop("instrumentation/nav[1]/in-range")) 599 me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/nav-id")); 600 else 601 me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/frequencies/selected-mhz-fmt")); 602 me.symbols.vorRId.setColor(0.195,0.96,0.097); 603 if(getprop("instrumentation/dme[1]/in-range")) 604 me.symbols.dmeRDist.setText(sprintf("%3.1f",getprop("instrumentation/dme[1]/indicated-distance-nm"))); 605 else me.symbols.dmeRDist.setText(" ---"); 606 me.symbols.dmeRDist.setColor(0.195,0.96,0.097); 607 } elsif(me.get_switch('toggle_rh_vor_adf') == -1) { 608 me.symbols.vorR.setText("ADF R"); 609 me.symbols.vorR.setColor(0,0.6,0.85); 610 me.symbols.dmeR.setText(""); 611 me.symbols.dmeR.setColor(0,0.6,0.85); 612 if((var navident=getprop("instrumentation/adf[1]/ident")) != "") 613 me.symbols.vorRId.setText(navident); 614 else me.symbols.vorRId.setText(sprintf("%3d",getprop("instrumentation/adf[1]/frequencies/selected-khz"))); 615 me.symbols.vorRId.setColor(0,0.6,0.85); 616 me.symbols.dmeRDist.setText(""); 617 me.symbols.dmeRDist.setColor(0,0.6,0.85); 618 } else { 619 me.symbols.vorR.setText(""); 620 me.symbols.dmeR.setText(""); 621 me.symbols.vorRId.setText(""); 622 me.symbols.dmeRDist.setText(""); 623 } 624 625 # Hide heading bug 10 secs after change 626 var vhdg_bug = getprop("autopilot/settings/heading-bug-deg") or 0; 627 var hdg_bug_active = getprop("autopilot/settings/heading-bug-active"); 628 if (hdg_bug_active == nil) 629 hdg_bug_active = 1; 630 631 if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT") 632 or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD")) 633 { 634 me.symbols.trkInd.setRotation(0); 635 me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R); 636 me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R); 637 } 638 else 639 { 640 me.symbols.trkInd.setRotation((userTrk-userHdg)*D2R); 641 me.symbols.curHdgPtr.setRotation(0); 642 me.symbols.curHdgPtr2.setRotation(0); 643 } 644 if(!me.in_mode('toggle_display_mode', ['PLAN'])) 645 { 646 var hdgBugRot = (vhdg_bug-userHdgTrk)*D2R; 647 me.symbols.selHdgLine.setRotation(hdgBugRot); 648 me.symbols.hdgBug.setRotation(hdgBugRot); 649 me.symbols.hdgBug2.setRotation(hdgBugRot); 650 me.symbols.selHdgLine2.setRotation(hdgBugRot); 651 } 652 653 var staPtrVis = !me.in_mode('toggle_display_mode', ['PLAN']); 654 if((me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_display_type') == "CRT") 655 or (me.get_switch('toggle_track_heading') and me.get_switch('toggle_display_type') == "LCD")) 656 { 657 var vorheading = userTrkTru; 658 var adfheading = userTrkMag; 659 } 660 else 661 { 662 var vorheading = userHdgTru; 663 var adfheading = userHdgMag; 664 } 665 if(getprop("instrumentation/nav/heading-deg") != nil) 666 var nav0hdg=getprop("instrumentation/nav/heading-deg") - vorheading; 667 if(getprop("instrumentation/nav[1]/heading-deg") != nil) 668 var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - vorheading; 669 var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg"); 670 var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg"); 671 if(!me.get_switch('toggle_centered')) 672 { 673 if(me.in_mode('toggle_display_mode', ['PLAN'])) 674 me.symbols.trkInd.hide(); 675 else 676 me.symbols.trkInd.show(); 677 if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) { 678 me.symbols.staArrowL.setVisible(staPtrVis); 679 me.symbols.staToL.setColor(0.195,0.96,0.097); 680 me.symbols.staFromL.setColor(0.195,0.96,0.097); 681 me.symbols.staArrowL.setRotation(nav0hdg*D2R); 682 } 683 elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) { 684 me.symbols.staArrowL.setVisible(staPtrVis); 685 me.symbols.staToL.setColor(0,0.6,0.85); 686 me.symbols.staFromL.setColor(0,0.6,0.85); 687 me.symbols.staArrowL.setRotation(adf0hdg*D2R); 688 } else { 689 me.symbols.staArrowL.hide(); 690 } 691 if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) { 692 me.symbols.staArrowR.setVisible(staPtrVis); 693 me.symbols.staToR.setColor(0.195,0.96,0.097); 694 me.symbols.staFromR.setColor(0.195,0.96,0.097); 695 me.symbols.staArrowR.setRotation(nav1hdg*D2R); 696 } elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) { 697 me.symbols.staArrowR.setVisible(staPtrVis); 698 me.symbols.staToR.setColor(0,0.6,0.85); 699 me.symbols.staFromR.setColor(0,0.6,0.85); 700 me.symbols.staArrowR.setRotation(adf1hdg*D2R); 701 } else { 702 me.symbols.staArrowR.hide(); 703 } 704 me.symbols.staArrowL2.hide(); 705 me.symbols.staArrowR2.hide(); 706 me.symbols.curHdgPtr2.hide(); 707 me.symbols.HdgBugCRT2.hide(); 708 me.symbols.TrkBugLCD2.hide(); 709 me.symbols.HdgBugLCD2.hide(); 710 me.symbols.selHdgLine2.hide(); 711 me.symbols.curHdgPtr.setVisible(staPtrVis); 712 me.symbols.HdgBugCRT.setVisible(staPtrVis and !dispLCD); 713 if(me.get_switch('toggle_track_heading') and !me.get_switch('toggle_hdg_bug_only')) 714 { 715 me.symbols.HdgBugLCD.hide(); 716 me.symbols.TrkBugLCD.setVisible(staPtrVis and dispLCD); 717 } 718 else 719 { 720 me.symbols.TrkBugLCD.hide(); 721 me.symbols.HdgBugLCD.setVisible(staPtrVis and dispLCD); 722 } 723 me.symbols.selHdgLine.setVisible(staPtrVis and hdg_bug_active); 724 } else { 725 me.symbols.trkInd.hide(); 726 if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) { 727 me.symbols.staArrowL2.setVisible(staPtrVis); 728 me.symbols.staFromL2.setColor(0.195,0.96,0.097); 729 me.symbols.staToL2.setColor(0.195,0.96,0.097); 730 me.symbols.staArrowL2.setRotation(nav0hdg*D2R); 731 } elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) { 732 me.symbols.staArrowL2.setVisible(staPtrVis); 733 me.symbols.staFromL2.setColor(0,0.6,0.85); 734 me.symbols.staToL2.setColor(0,0.6,0.85); 735 me.symbols.staArrowL2.setRotation(adf0hdg*D2R); 736 } else { 737 me.symbols.staArrowL2.hide(); 738 } 739 if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) { 740 me.symbols.staArrowR2.setVisible(staPtrVis); 741 me.symbols.staFromR2.setColor(0.195,0.96,0.097); 742 me.symbols.staToR2.setColor(0.195,0.96,0.097); 743 me.symbols.staArrowR2.setRotation(nav1hdg*D2R); 744 } elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) { 745 me.symbols.staArrowR2.setVisible(staPtrVis); 746 me.symbols.staFromR2.setColor(0,0.6,0.85); 747 me.symbols.staToR2.setColor(0,0.6,0.85); 748 me.symbols.staArrowR2.setRotation(adf1hdg*D2R); 749 } else { 750 me.symbols.staArrowR2.hide(); 751 } 752 me.symbols.staArrowL.hide(); 753 me.symbols.staArrowR.hide(); 754 me.symbols.curHdgPtr.hide(); 755 me.symbols.HdgBugCRT.hide(); 756 me.symbols.TrkBugLCD.hide(); 757 me.symbols.HdgBugLCD.hide(); 758 me.symbols.selHdgLine.hide(); 759 me.symbols.curHdgPtr2.setVisible(staPtrVis); 760 me.symbols.HdgBugCRT2.setVisible(staPtrVis and !dispLCD); 761 if(me.get_switch('toggle_track_heading') and !me.get_switch('toggle_hdg_bug_only')) 762 { 763 me.symbols.HdgBugLCD2.hide(); 764 me.symbols.TrkBugLCD2.setVisible(staPtrVis and dispLCD); 765 } 766 else 767 { 768 me.symbols.TrkBugLCD2.hide(); 769 me.symbols.HdgBugLCD2.setVisible(staPtrVis and dispLCD); 770 } 771 me.symbols.selHdgLine2.setVisible(staPtrVis and hdg_bug_active); 772 } 773 774 ## run all predicates in the NDStyle hash and evaluate their true/false behavior callbacks 775 ## this is in line with the original design, but normally we don't need to getprop/poll here, 776 ## using listeners or timers would be more canvas-friendly whenever possible 777 ## because running setprop() on any group/canvas element at framerate means that the canvas 778 ## will be updated at frame rate too - wasteful ... (check the performance monitor!) 779 780 foreach(var feature; me.nd_style.features ) { 781 # for stuff that always needs to be updated 782 if (contains(feature.impl, 'common')) feature.impl.common(me); 783 # conditional stuff 784 if(!contains(feature.impl, 'predicate')) continue; # no conditional stuff 785 if ( var result=feature.impl.predicate(me) ) 786 feature.impl.is_true(me, result); # pass the result to the predicate 787 else 788 feature.impl.is_false( me, result ); # pass the result to the predicate 789 } 790 791 ## update the status flags shown on the ND (wxr, wpt, arpt, sta) 792 # this could/should be using listeners instead ... 793 me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP'])); 794 me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP'])); 795 me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP'])); 796 me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP'])); 797 # Okay, _how_ do we hook this up with FGPlot? 798 logprint(canvas._MP_dbg_lvl, "Total ND update took "~((systime()-_time)*100)~"ms"); 799 setprop("/instrumentation/navdisplay["~ NavDisplay.id ~"]/update-ms", systime() - _time); 800 } # of update() method (50% of our file ...seriously?) 801}; 802