... | ... |
@@ -0,0 +1,83 @@ |
1 |
+var annunciationsClass = { |
|
2 |
+ new: func { |
|
3 |
+ var m = { parents : [ annunciationsClass ] }; |
|
4 |
+ m.registered = {}; # triggers identified by message |
|
5 |
+ m.active = []; # currently active warnings and alerts, sorted by emergency level |
|
6 |
+ m.devices = []; # PFD displays |
|
7 |
+ |
|
8 |
+ foreach (var d; keys(flightdeck)) |
|
9 |
+ if (flightdeck[d].role == 'PFD') |
|
10 |
+ append(m.devices, d); |
|
11 |
+ |
|
12 |
+ foreach (var warnings; alerts.getChildren('warnings')) |
|
13 |
+ foreach (var warning; warnings.getChildren('warning')) |
|
14 |
+ m.register(warning); |
|
15 |
+ |
|
16 |
+ data.timers.annunciations = maketimer(1.0, func call(m.activate, [], m)); |
|
17 |
+ data.timers.annunciations.start(); |
|
18 |
+ |
|
19 |
+ return m; |
|
20 |
+ }, |
|
21 |
+ |
|
22 |
+ register: func (node) { |
|
23 |
+ me.registered[node.getValue('message')] = { |
|
24 |
+ trigger: compile(node.getValue('script')), |
|
25 |
+ node: node, |
|
26 |
+ }; |
|
27 |
+ }, |
|
28 |
+ |
|
29 |
+ del: func (message) { |
|
30 |
+ if (contains(me.registered, message)) |
|
31 |
+ delete(me.registered, message); |
|
32 |
+ if (contains(me.active, message)) |
|
33 |
+ delete(me.active, message); |
|
34 |
+ }, |
|
35 |
+ |
|
36 |
+ activate: func { |
|
37 |
+ size(me.registered) or return; |
|
38 |
+ |
|
39 |
+ var score = {}; |
|
40 |
+ |
|
41 |
+ foreach (var a; keys(me.registered)) |
|
42 |
+ if (me.registered[a].trigger()) |
|
43 |
+ score[me.registered[a].node.getValue('message')] = me.registered[a].node.getValue('level'); |
|
44 |
+ |
|
45 |
+ if (size(score) > 1) { |
|
46 |
+ var sorted_scores = sort(keys(score), func (a, b) { |
|
47 |
+ if (score[a] <= score[b]) |
|
48 |
+ return 1; # greatest first |
|
49 |
+ else |
|
50 |
+ return -1; |
|
51 |
+ }); |
|
52 |
+ } |
|
53 |
+ else |
|
54 |
+ var sorted_scores = keys(score); |
|
55 |
+ |
|
56 |
+ me.active = sorted_scores; |
|
57 |
+ |
|
58 |
+ var levels_bg = [ 'lightgrey', 'white', 'red' ]; |
|
59 |
+ var levels_fg = [ 'black' , 'black', 'yellow' ]; |
|
60 |
+ |
|
61 |
+ if (size(me.active)) { |
|
62 |
+ var level = score[sorted_scores[0]]; |
|
63 |
+ if (level > 2) level = 2; |
|
64 |
+ |
|
65 |
+ foreach (var d; me.devices) { |
|
66 |
+ flightdeck[d].display.screenElements['SoftKey11-bg'] |
|
67 |
+ .setColorFill(flightdeck[d].display.colors[levels_bg[level]]); |
|
68 |
+ flightdeck[d].display.screenElements['SoftKey11-text'] |
|
69 |
+ .setColor(flightdeck[d].display.colors[levels_fg[level]]); |
|
70 |
+ flightdeck[d].display.updateSoftKeys(); |
|
71 |
+ } |
|
72 |
+ } |
|
73 |
+ else { |
|
74 |
+ foreach (var d; me.devices) { |
|
75 |
+ flightdeck[d].display.screenElements['SoftKey11-bg'] |
|
76 |
+ .setColorFill(flightdeck[d].display.colors.black); |
|
77 |
+ flightdeck[d].display.screenElements['SoftKey11-text'] |
|
78 |
+ .setColor(flightdeck[d].display.colors.lightgrey); |
|
79 |
+ flightdeck[d].display.updateSoftKeys(); |
|
80 |
+ } |
|
81 |
+ } |
|
82 |
+ }, |
|
83 |
+}; |
... | ... |
@@ -309,6 +309,9 @@ var powerOn = func { |
309 | 309 |
if (! contains(autopilot, 'parents')) |
310 | 310 |
autopilot = APClass.new(); |
311 | 311 |
|
312 |
+ if (! contains(annunciations, 'parents')) |
|
313 |
+ annunciations = annunciationsClass.new(); |
|
314 |
+ |
|
312 | 315 |
if (! contains(data.timers, 'listeners')) { |
313 | 316 |
data.timers.listeners = maketimer(1, setListeners); |
314 | 317 |
data.timers.listeners.singleShot = 1; |
... | ... |
@@ -57,6 +57,40 @@ var softkeysClass = { |
57 | 57 |
|
58 | 58 |
bindings : { |
59 | 59 |
PFD : { |
60 |
+ ALERTS: func { |
|
61 |
+ id = 'ALERTS'; |
|
62 |
+ if (!contains(me.device.windows.state, id)) { |
|
63 |
+ var obj_infos = [ |
|
64 |
+ { text: sprintf('%i ALERT%s', size(annunciations.active), size(annunciations.active) > 1 ? 'S' : ''), type: 'title' }, |
|
65 |
+ { type: 'separator' } |
|
66 |
+ ]; |
|
67 |
+ var firstEntry = 1; |
|
68 |
+ var levels = [ 'INFO', 'WARNING', 'ALERT' ]; |
|
69 |
+ forindex (var order; annunciations.active) { |
|
70 |
+ var level = annunciations.registered[annunciations.active[order]].node.getValue('level'); |
|
71 |
+ if (level > 2) level = 2; |
|
72 |
+ append(obj_infos, { |
|
73 |
+ text: sprintf('%02i - %-7s ', order + 1, levels[level]), |
|
74 |
+ type: firstEntry ? 'selected' : 'editable', |
|
75 |
+ scrollgroup: order, |
|
76 |
+ }); |
|
77 |
+ append(obj_infos, { |
|
78 |
+ text: annunciations.registered[annunciations.active[order]].node.getValue('message'), |
|
79 |
+ type: 'normal|end-of-line', |
|
80 |
+ scrollgroup: order, |
|
81 |
+ }); |
|
82 |
+ firstEntry = 0; |
|
83 |
+ } |
|
84 |
+ me.device.knobs.FmsInner = me.device.knobs.NavigateMenu; |
|
85 |
+ me.device.knobs.FmsOuter = me.device.knobs.NavigateMenu; |
|
86 |
+ me.device.windows.draw( id, obj_infos, {lines: 4, columns: 2, rows: 1} ); |
|
87 |
+ } |
|
88 |
+ else { |
|
89 |
+ me.device.knobs.FmsInner = func; |
|
90 |
+ me.device.knobs.FmsOuter = func; |
|
91 |
+ me.device.buttons.ClearTMRREF(); |
|
92 |
+ } |
|
93 |
+ }, |
|
60 | 94 |
INSET: { |
61 | 95 |
OFF: func { |
62 | 96 |
me.device.map.setVisible(0); |
... | ... |
@@ -58,13 +58,14 @@ Please report bug at <zkv1000@seb.lautre.net>. |
58 | 58 |
* Screens are fully animated, with softkeys and menus, on map (PFD inset and MFD) background is configurable with the preferred one, navaids and [route](https://seb.lautre.net/git/seb/zkv1000/issues/6) are displayed. Traffic and topography layers are available for display only on MFD screen (not in PFD inset as the window is too small). |
59 | 59 |
* There is also per-aircraft settings storage for preferred units (speed, distance, etc.). |
60 | 60 |
* Flightplan management is delegated to the more usable FG interface lauched from inside the zkv100, but it is possible to use DirectTo on selected navaid, and OBS mode with GPS. |
61 |
- * Checklists are managed, including separation between emergency and normal checklists (and aircraft agnostic, should be compatible with most systems). |
|
61 |
+ * Checklists are managed, including separation between emergency and normal checklists (and aircraft agnostic, should be compatible with most systems). Moreover a alerting system allow to show specific alerts for your aircraft, just register a new alert check and PFDs will warn the pilot about. |
|
62 | 62 |
* ... and many more ! :) |
63 | 63 |
* ![][90%] |
64 | 64 |
* route displayed on map: legs ![][done], current and next leg ![][done], OBS ![][done], TOC/TOD ![][ongoing] |
65 | 65 |
* XPDR: emergency code depending of the country (eg.: 1200 for US, 7700 for Europe), should be set in settings |
66 | 66 |
* ![][80%] |
67 | 67 |
* Flight plans catalog (display on map doesn't work each time...) |
68 |
+ * Alerts: voice description of the alert ![][ongoing] |
|
68 | 69 |
* ![][60%] |
69 | 70 |
* NOT TESTED: add the posssibility to only use Nasal part of the instrument. This in case of a cockpit which already includes needed objects and animations for screens, knobs and buttons in its config files |
70 | 71 |
* ![][50%] |
... | ... |
@@ -79,7 +80,6 @@ Please report bug at <zkv1000@seb.lautre.net>. |
79 | 80 |
* make possible to integrate other autopilot systems than the one integrated |
80 | 81 |
* make booting animation visible |
81 | 82 |
* CDI/GPS: scale depending of the flight phase |
82 |
- * Alerts |
|
83 | 83 |
* Weather map |
84 | 84 |
* replace the use of `clip` by a better system in map display (think also about INSET) |
85 | 85 |
* VS guidance |
... | ... |
@@ -206,6 +206,58 @@ You can specify a directory where to find the flightplans you save. Defaults to |
206 | 206 |
Only the flightplans set for a departure from your position will be shown, no matter of the name of the file (even extension), each will be parsed to find the ones corresponding to your departure location. |
207 | 207 |
The list will show only 6 flightplans, but it's scrollable so you can handle much more. |
208 | 208 |
|
209 |
+### Alerting system |
|
210 |
+You can set some alerts of your choice. Note that there are 3 levels: |
|
211 |
+ |
|
212 |
+* `0`: INFO |
|
213 |
+* `1`: WARNING |
|
214 |
+* `2`: ALERT |
|
215 |
+ |
|
216 |
+Alerts are identified by the displayed message, so you can't provide two alerts with the same alert message. The higher level is shown first in list displayed on PFD (press `ALERT` softkey), all levels should be integers at least if less than 2. The tests are executed each second. |
|
217 |
+ |
|
218 |
+To come: a voice alert if needed and configured (but I'm facing issue of multiple alerts at the same time, temporisation is ongoing). |
|
219 |
+ |
|
220 |
+To register your alerts you have two ways: |
|
221 |
+#### set the alerts in `/instrumentation/zkv1000/alerts/warnings` |
|
222 |
+This way they are registered at start |
|
223 |
+ |
|
224 |
+ <warnings> |
|
225 |
+ <warning> |
|
226 |
+ <script><![CDATA[ |
|
227 |
+ getprop('/foo/bar/baz/value') and getprop('/foo/bar/baz/value[1]') > 5; |
|
228 |
+ ]]></script> |
|
229 |
+ <message>TEST 1</message> |
|
230 |
+ <!-- <speaker/> TO USE THE MESSAGE AS VOICE --> |
|
231 |
+ <level>0</level> |
|
232 |
+ </warning> |
|
233 |
+ <warning> |
|
234 |
+ <script><![CDATA[ |
|
235 |
+ getprop('/foo/bar/baz/value[3]') == 'foobar' or getprop('/foo/bar/baz/value[2]') < 10; |
|
236 |
+ ]]></script> |
|
237 |
+ <message>TEST 2</message> |
|
238 |
+ <level>1</level> |
|
239 |
+ <!-- <speaker>this is only for testing</speaker> TO USE A SPECIFIC VOICE MESSAGE --> |
|
240 |
+ </warning> |
|
241 |
+ <warning> |
|
242 |
+ <script><![CDATA[ |
|
243 |
+ return getprop('/foo/bar/baz/value[4]') == 5 or getprop('/foo/bar/baz/value[5]'); |
|
244 |
+ ]]></script> |
|
245 |
+ <message>TEST 3</message> |
|
246 |
+ <level>2</level> |
|
247 |
+ </warning> |
|
248 |
+ </warnings> |
|
249 |
+ |
|
250 |
+#### or use the integrated API |
|
251 |
+Actually not yet tested, but you can register new alerts somewhere in your Nasal code with the following: |
|
252 |
+ |
|
253 |
+ zkv1000.annunciations.register(props.globals.new({ |
|
254 |
+ message: "a message", |
|
255 |
+ level: 20, |
|
256 |
+ script: "if (something) return 1; else return 0;" |
|
257 |
+ })); |
|
258 |
+ |
|
259 |
+It also exists a `zkv1000.annunciations.del(message)` to delete a warning from the register list, so it won't be tested anymore. |
|
260 |
+ |
|
209 | 261 |
## 3D models |
210 | 262 |
In the definition of your flightdeck (here are the values for the installation in the Lancair 235 in which I develop the device) |
211 | 263 |
put it everywhere you want to. Note that the path `Aircraft/Instruments-3d/zkv1000` is dependant on the path where the zkv1000 is installed, this can be somewhere like `Aircraft/My-Nice-Aircraft/arbitrary/dirs/zkv1000-or-not`as mentionned earlier in this section. |
... | ... |
@@ -16,17 +16,19 @@ files_to_load = [ |
16 | 16 |
'menu.nas', # manage windows |
17 | 17 |
'core.nas', # the scheduler and switch on button |
18 | 18 |
'afcs.nas', # Automatic Flight Control System |
19 |
+ 'annunciations.nas', # in flight tests |
|
19 | 20 |
]; |
20 | 21 |
# 'routes.nas', # manages flightplans and routes |
21 | 22 |
# 'display.nas', # display messages and popups |
22 | 23 |
# 'infos.nas', # get informations from environment |
23 |
-# 'alerts.nas', # in flight tests |
|
24 | 24 |
# 'mud.nas', # displays simple embedded GUI (Multi-Use Display) |
25 | 25 |
|
26 | 26 |
var flightdeck = {}; |
27 | 27 |
|
28 | 28 |
var autopilot = {}; |
29 | 29 |
|
30 |
+var annunciations = {}; |
|
31 |
+ |
|
30 | 32 |
var units = { |
31 | 33 |
speed : { |
32 | 34 |
from_kt : 1, |