Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / route_manager.nas
1 # route_manager.nas -  FlightPlan delegate(s) corresponding to the built-
2 # in route-manager dialog and GPS. Intended to provide a sensible default behaviour,
3 # but can be disabled by an aircraft-specific FMS / GPS system.
4
5 var RouteManagerDelegate = {
6     new: func(fp) {
7     # if this property is set, don't build a delegate at all
8     if (getprop('/autopilot/route-manager/disable-route-manager'))
9         return nil;
10
11         var m = { parents: [RouteManagerDelegate] };
12         m.flightplan = fp;
13         return m;
14     },
15
16     departureChanged: func
17     {
18         printlog('info', 'saw departure changed');
19         me.flightplan.clearWPType('sid');
20         if (me.flightplan.departure == nil)
21             return;
22
23         if (me.flightplan.departure_runway == nil) {
24         # no runway, only an airport, use that
25             var wp = createWPFrom(me.flightplan.departure);
26             wp.wp_role = 'sid';
27             me.flightplan.insertWP(wp, 0);
28             return;
29         }
30     # first, insert the runway itself
31         var wp = createWPFrom(me.flightplan.departure_runway);
32         wp.wp_role = 'sid';
33         me.flightplan.insertWP(wp, 0);
34         if (me.flightplan.sid == nil)
35             return;
36
37     # and we have a SID
38         var sid = me.flightplan.sid;
39         printlog('info', 'routing via SID ' ~ sid.id);
40         me.flightplan.insertWaypoints(sid.route(me.flightplan.departure_runway), 1);
41     },
42
43     arrivalChanged: func
44     {
45         printlog('info', 'saw arrival changed');
46         me.flightplan.clearWPType('star');
47         me.flightplan.clearWPType('approach');
48         if (me.flightplan.destination == nil)
49             return;
50
51         if (me.flightplan.destination_runway == nil) {
52         # no runway, only an airport, use that
53             var wp = createWPFrom(me.flightplan.destination);
54             wp.wp_role = 'approach';
55             me.flightplan.appendWP(wp);
56             return;
57         }
58
59         var initialApproachFix = nil;
60         if (me.flightplan.star != nil) {
61             printlog('info', 'routing via STAR ' ~ me.flightplan.star.id);
62             var wps = me.flightplan.star.route(me.flightplan.destination_runway);
63             me.flightplan.insertWaypoints(wps, -1);
64
65             initialApproachFix = wps[-1]; # final waypoint of STAR
66         }
67
68         if (me.flightplan.approach != nil) {
69             var wps = me.flightplan.approach.route(initialApproachFix);
70
71              if ((initialApproachFix != nil) and (wps == nil)) {
72              # current GUI allows selected approach then STAR; but STAR
73              # might not be possible for the approach (no transition).
74              # since fixing the GUI flow is hard, let's route assuming no
75              # IAF. This will likely cause an ugly direct leg, but that's
76              # what the user asked for.
77
78                  printlog('info', "couldn't route approach based on specified IAF "
79                   ~ initialApproachFix.wp_name);
80                  wps = me.flightplan.approach.route(nil);
81              }
82
83             if (wps == nil) {
84                 printlog('warn', 'routing via approach ' ~ me.flightplan.approach.id
85                     ~ ' failed entirely.');
86             } else {
87                 printlog('info', 'routing via approach ' ~ me.flightplan.approach.id);
88                 me.flightplan.insertWaypoints(wps, -1);
89             }
90         } else {
91             printlog('info', 'routing direct to runway ' ~ me.flightplan.destination_runway.id);
92             # no approach, just use the runway waypoint
93             var wp = createWPFrom(me.flightplan.destination_runway);
94             wp.wp_role = 'approach';
95             me.flightplan.appendWP(wp);
96         }
97     },
98
99     cleared: func
100     {
101         printlog('info', "saw active flightplan cleared, deactivating");
102         # see http://https://code.google.com/p/flightgear-bugs/issues/detail?id=885
103         fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
104     },
105
106     endOfFlightPlan: func
107     {
108         printlog('info', "end of flight-plan, deactivating");
109         fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
110     }
111 };
112
113
114 var FMSDelegate = {
115     new: func(fp) {
116     # if this property is set, don't build a delegate at all
117     if (getprop('/autopilot/route-manager/disable-fms'))
118         return nil;
119
120         var m = { parents: [FMSDelegate], flightplan:fp, landingCheck:nil };
121
122         # make FlightPlan behaviour match GPS config state
123         fp.followLegTrackToFix = getprop('/instrumentation/gps/config/follow-leg-track-to-fix');
124
125         # similarly, make FlightPlan follow the performance category settings
126         fp.aircraftCategory = getprop('/autopilot/settings/icao-aircraft-category');
127
128         return m;
129     },
130
131     _landingCheckTimeout: func
132     {
133         var wow = getprop('gear/gear[0]/wow');
134         var gs = getprop('velocities/groundspeed-kt');
135         if (wow and (gs < 25))  {
136           printlog('info', 'touchdown on destination runway, end of route.');
137           me.landingCheck.stop();
138           # record touch-down time?
139           me.flightplan.finish();
140         }
141     },
142
143     waypointsChanged: func
144     {
145     },
146
147     endOfFlightPlan: func
148     {
149       printlog('info', 'end of flight-plan');
150     },
151
152     currentWaypointChanged: func
153     {
154         if (me.landingCheck != nil) {
155             me.landingCheck.stop();
156             me.landingCheck = nil; # delete timer
157         }
158
159         #printlog('info', 'saw current WP changed, now ' ~ me.flightplan.current);
160         var active = me.flightplan.currentWP();
161         if (active == nil) return;
162
163         if (active.alt_cstr_type == "at") {
164             printlog('info', 'new WP has valid altitude restriction, setting on AP');
165             setprop('/autopilot/settings/target-altitude-ft', active.alt_cstr);
166         }
167
168         var activeRunway = active.runway();
169         # this check is needed to avoid problems with circular routes; when
170         # activating the FP we end up here, and without this check, immediately
171         # detect that we've 'landed' and finish the FP again.
172         var wow = getprop('gear/gear[0]/wow');
173
174         if (!wow and
175             (activeRunway != nil) and
176             (activeRunway.id == me.flightplan.destination_runway.id))
177         {
178             me.landingCheck = maketimer(2.0, me, FMSDelegate._landingCheckTimeout);
179             me.landingCheck.start();
180         }
181     }
182 };
183
184 registerFlightPlanDelegate(FMSDelegate.new);
185 registerFlightPlanDelegate(RouteManagerDelegate.new);
186