Showing 6 changed files with 87 additions and 114 deletions
+2 -2
Models/tcas.svg
... ...
@@ -76,7 +76,7 @@
76 76
          y="1003.079">+30</tspan></text>
77 77
     <text
78 78
        sodipodi:linespacing="125%"
79
-       id="Alt_below"
79
+       id="Callsign"
80 80
        y="1052.1428"
81 81
        x="38.328125"
82 82
        style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:end;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;font-family:Liberation Mono;-inkscape-font-specification:Liberation Mono"
... ...
@@ -84,7 +84,7 @@
84 84
          y="1052.1428"
85 85
          x="38.328125"
86 86
          id="tspan2993"
87
-         sodipodi:role="line">-30</tspan></text>
87
+         sodipodi:role="line">callsign</tspan></text>
88 88
     <path
89 89
        style="color:#000000;fill:#e8e8d8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
90 90
        d="m 41.3125,16.09375 -5.9375,10.25 4,0 0,21.5 3.84375,0 0,-21.5 4,0 -5.90625,-10.25 z"
+47 -43
Nasal/core.nas
... ...
@@ -151,50 +151,54 @@ var deviceClass = {
151 151
             data.timers.map.singleShot = 1;
152 152
             data.timers.map.start();
153 153
         }
154
-        if (getprop('/instrumentation/tcas/serviceable') != nil) {
155
-            m.data.tcas = 0;
156
-            if (!contains(data.timers, 'tcas')) {
157
-                data.timers.tcas = maketimer ( 1,
158
-                    func {
159
-                        var tcas_dirty = [];
160
-                        var _range = 6;
161
-                        var _minLevel = -1;
162
-                        foreach (var ac; props.globals.getNode("/ai/models").getChildren("multiplayer")) {
163
-                            if (ac.getValue("valid")) {
164
-                                var range = ac.getNode("radar/range-nm").getValue();
165
-                                if (range != nil) {
166
-                                    if (debug.isnan(range) == 0) {
167
-                                        if ( (range > 0) and (range <= _range) ) {
168
-                                            var nTcasThreat = ac.getNode("tcas/threat-level");
169
-                                            if (nTcasThreat != nil) {
170
-                                                var level = nTcasThreat.getValue();
171
-                                                if (level > _minLevel) {
172
-                                                    var lat     = ac.getNode("position/latitude-deg").getValue();
173
-                                                    var lon     = ac.getNode("position/longitude-deg").getValue();
174
-                                                    var aAlt    = ac.getNode("position/altitude-ft").getValue();
175
-                                                    var vs      = ac.getNode("velocities/vertical-speed-fps").getValue();
176
-                                                    var alt     = math.floor(((aAlt - data.alt) / 100) + 0.5);
177
-                                                    if (debug.isnan(lat) == 0 and
178
-                                                        debug.isnan(lon) == 0 and
179
-                                                        debug.isnan(vs)  == 0 and
180
-                                                        debug.isnan(alt) == 0) {
181
-                                                            append(tcas_dirty, {
182
-                                                                lat: lat,
183
-                                                                lon: lon,
184
-                                                                vs: vs,
185
-                                                                alt: alt,
186
-                                                                level: level,
187
-                                                            });
188
-                                                    } } } } } } } }
189
-                        data.tcas = tcas_dirty;
154
+        if (!contains(data.timers, 'tcas')) {
155
+            data.timers.tcas = maketimer ( 5, func {
156
+                var traffic_displayed = 0;
157
+                foreach (var name; keys(flightdeck))
158
+                    traffic_displayed += flightdeck[name].map.layers.tcas.group.getVisible();
159
+                var tcas_dirty = [];
160
+                var level_dirty = 0;
161
+                foreach (var AItype; [ 'aircraft', 'multiplayer' ])
162
+                    foreach (var ac; props.globals.getNode("/ai/models").getChildren(AItype)) {
163
+                        if (ac.getValue("valid")) {
164
+                            var lat = ac.getNode("position/latitude-deg").getValue();
165
+                            var lon = ac.getNode("position/longitude-deg").getValue();
166
+                            var alt = ac.getNode("position/altitude-ft").getValue();
167
+                            var vs  = ac.getNode("velocities/vertical-speed-fps").getValue();
168
+                            if (isnum(lat) and isnum(lon) and isnum(vs) and isnum(alt)) {
169
+                                alt = math.floor(((alt - data.alt) / 100) + 0.5);
170
+                                var (course, dist) = courseAndDistance(lat, lon,
171
+                                                     geo.Coord.new().set_latlon(data.lat, data.lon));
172
+                                if (dist < 50) {
173
+                                    var dir = ac.getNode('orientation/true-heading-deg').getValue() - course;
174
+                                    if (dist < 5 and abs(alt) < 10)
175
+                                        level = 3;
176
+                                    elsif (dist < 10 and alt * vs < 0 and abs(dir) < 10)
177
+                                        level = 3;
178
+                                    elsif (dist < 15 and abs(alt) < 5)
179
+                                        level = 2;
180
+                                    elsif (dist < 15 and abs(alt) < 50)
181
+                                        level = 1;
182
+                                    else
183
+                                        level = 0;
184
+                                    level_dirty = level > level_dirty ? level : level_dirty;
185
+                                    if (traffic_displayed)
186
+                                        append(tcas_dirty, {
187
+                                            lat: lat,
188
+                                            lon: lon,
189
+                                            vs: vs,
190
+                                            alt: alt,
191
+                                            level: level,
192
+                                            callsign: ac.getValue('callsign')
193
+                                        });
194
+                                }
195
+                            }
196
+                        }
190 197
                     }
191
-                );
192
-                data.timers.tcas.start();
193
-            }
194
-        }
195
-        else {
196
-            delete(m.softkeys.bindings.PFD.INSET, 'TRAFFIC');
197
-            delete(m.softkeys.bindings.MFD.MAP,   'TRAFFIC');
198
+                data.tcas = tcas_dirty;
199
+                data.tcas_level = level_dirty;
200
+            });
201
+            data.timers.tcas.start();
198 202
         }
199 203
         m.display.showInitProgress();
200 204
 
+1 -1
Nasal/display.nas
... ...
@@ -702,7 +702,7 @@ var displayClass = {
702 702
         me.screenElements['SlipSkid']
703 703
             .setTranslation(getprop("/instrumentation/slip-skid-ball/indicated-slip-skid") * 10, 0);
704 704
         me.screenElements['Traffic']
705
-            .setVisible(size(data.tcas));
705
+            .setVisible(data.tcas_level > 1);
706 706
     },
707 707
 #}}}
708 708
 
+35 -52
Nasal/maps/tcas.nas
... ...
@@ -8,16 +8,14 @@ var TcasItemClass = {
8 8
 
9 9
         canvas.parsesvg(m._group, data.zkv1000_reldir ~ "Models/tcas.svg");
10 10
         m._can = {
11
-            Alt : [
12
-                m._group.getElementById("Alt_above").setVisible(0),
13
-                m._group.getElementById("Alt_below").setVisible(0)
14
-            ],
11
+            Alt : m._group.getElementById("Alt_above").setVisible(0),
15 12
             Arrow : [
16 13
                 m._group.getElementById("Arrow_climb").setVisible(0),
17 14
                 m._group.getElementById("Arrow_descent").setVisible(0)
18
-            ]
15
+            ],
16
+            Callsign: m._group.getElementById("Callsign").setVisible(0),
17
+            ThreadLevel: [],
19 18
         };
20
-        m._can.ThreadLevel = [];
21 19
 
22 20
         for (var i=0; i<4; i+=1)
23 21
             append(m._can.ThreadLevel,
... ...
@@ -28,41 +26,38 @@ var TcasItemClass = {
28 26
         return m;
29 27
     },
30 28
 
31
-    setData : func(lat, lon, alt, vs, level) {
29
+    setData : func(lat, lon, alt, vs, level, callsign) {
32 30
         me._group.setVisible(1);
33 31
         me._group.setGeoPosition(lat, lon);
34 32
         
35
-        if (alt == 0) {
36
-            me._can.Alt[0].setVisible(0);
37
-            me._can.Alt[1].setVisible(0);
38
-        }
39
-        else {
40
-            me._can.Alt[alt < 0]
33
+        if (alt and level)
34
+            me._can.Alt
41 35
                 .setText(sprintf("%+i", alt))
42 36
                 .set('fill', me._colors[level])
43 37
                 .setVisible(1);
44
-
45
-            me._can.Alt[alt > 0].setVisible(0);
46
-        }
38
+        else
39
+            me._can.Alt.setVisible(0);
47 40
                 
48
-        if (vs >= -3 and vs <= 3) {
49
-            me._can.Arrow[0].setVisible(0);
50
-            me._can.Arrow[1].setVisible(0);
51
-        }
52
-        else {
53
-            me._can.Arrow[vs < -3]
41
+        if (abs(vs) > 50 and level > 1) {
42
+            me._can.Arrow[vs < 0]
54 43
                 .set('fill', me._colors[level])
55 44
                 .set('stroke', me._colors[level])
56 45
                 .setVisible(1);
57 46
 
58
-            me._can.Arrow[vs > 3].setVisible(0);
47
+            me._can.Arrow[vs > 0].setVisible(0);
59 48
         }
60
-        
61
-        me._can.ThreadLevel[0].setVisible(level == 0);
62
-        me._can.ThreadLevel[1].setVisible(level == 1);
63
-        me._can.ThreadLevel[2].setVisible(level == 2);
64
-        me._can.ThreadLevel[3].setVisible(level == 3);
65
-        me._group.set("z-index", level + 1);
49
+        else {
50
+            me._can.Arrow[0].setVisible(0);
51
+            me._can.Arrow[1].setVisible(0);
52
+        }
53
+
54
+        for (var i = 0; i < 4; i += 1)
55
+            me._can.ThreadLevel[i].setVisible(level == i);
56
+
57
+        me._can.Callsign
58
+            .setText(callsign)
59
+            .set('fill', me._colors[level])
60
+            .setVisible(1);
66 61
     },
67 62
 };
68 63
 # }}} 
... ...
@@ -72,27 +67,15 @@ var MapTcas = {
72 67
     new : func(device, group) {
73 68
         var m = {parents:[MapTcas]}; 
74 69
         m.device = device;
75
-        if (getprop('/instrumentation/tcas/serviceable') == nil) {
76
-            m.update = func;
77
-            m.setVisible = func;
78
-        }
79
-        else {
80
-            m.visibility = 0;
81
-            m.group = group.createChild('map', 'tcas')
82
-                .setTranslation(
83
-                    m.device.role == 'MFD' ? (m.device.data.mapview[0] + m.device.data.mapclip.left)/2 : 120,
84
-                    m.device.role == 'MFD' ? 400 : 600)
85
-                .setVisible(m.visibility);
86
-            m._item      = [];
87
-            m._itemIndex = 0;
88
-            m._itemCount = 0;
89
-# /instrumentation/tcas/inputs/mode
90
-            m.MODE      = ["Normal","Above","Unlimited","Below"];
91
-        }
92
-        if (m.device.role == 'PFD')
93
-            m.device.softkeys.colored.INSETTRAFFIC = 1;
94
-        if (m.device.role == 'MFD')
95
-            m.device.softkeys.colored.MAPTRAFFIC = 1;
70
+        m.visibility = 0;
71
+        m.group = group.createChild('map', 'tcas')
72
+            .setTranslation(
73
+                m.device.role == 'MFD' ? (m.device.data.mapview[0] + m.device.data.mapclip.left)/2 : 120,
74
+                m.device.role == 'MFD' ? 400 : 600)
75
+            .setVisible(m.visibility);
76
+        m._item      = [];
77
+        m._itemIndex = 0;
78
+        m._itemCount = 0;
96 79
         return m;
97 80
     },
98 81
 
... ...
@@ -101,7 +84,7 @@ var MapTcas = {
101 84
     },
102 85
 
103 86
     update : func() {
104
-        if (me.device.data.tcas == 0)
87
+        if (me.group.getVisible() == 0)
105 88
             return;
106 89
         me.group._node.getNode('ref-lat', 1).setDoubleValue(data.lat);
107 90
         me.group._node.getNode('ref-lon', 1).setDoubleValue(data.lon);
... ...
@@ -113,7 +96,7 @@ var MapTcas = {
113 96
                 append(me._item, TcasItemClass.new(me.group, me._itemIndex));
114 97
                 me._itemCount += 1;
115 98
             }
116
-            me._item[me._itemIndex].setData(ac.lat, ac.lon, ac.alt, ac.vs, ac.level);
99
+            me._item[me._itemIndex].setData(ac.lat, ac.lon, ac.alt, ac.vs, ac.level, ac.callsign);
117 100
             me._itemIndex += 1;
118 101
         }
119 102
                 
+1 -16
README.md
... ...
@@ -77,7 +77,7 @@ Please report bug at <zkv1000@seb.lautre.net>.
77 77
   * single-prop EIS: texts displayed, animations for fuel
78 78
   * ND and map display: synchronized tiles and navaids, range change, map heads up
79 79
   * rotation and zooming of online maps in-flight
80
-  * simple Traffic Alert Collision Avoidance System (TCAS)
80
+  * Traffic alert and display on map
81 81
   * screen brightness and body lighting settings
82 82
   * checklists management (emergency and normal)
83 83
   * possibility to fit the size of the devices to enter the panel
... ...
@@ -272,21 +272,6 @@ Actually there are only two "types of display": MFD or PFD, which is known by th
272 272
 Other devices as keyboard or non-display can also exists, as long as they don't have a `status` property...
273 273
 Not sur I'm clear on this point though :)
274 274
 
275
-## TCAS
276
-To enable TCAS you need to make it serviceable in your aircraft before the ZKV1000 being powered on, so the best is to set it in the aircraft configuration files.
277
-Actually it checks if the property `/instrumentation/tcas/serviceable` is set to boolean true value.
278
-
279
-        <tcas>
280
-          <name>tcas</name>
281
-          <number>0</number>
282
-          <serviceable type='bool'>true</serviceable>
283
-          <inputs>
284
-            <mode type="int">5</mode>
285
-          </inputs>
286
-        </tcas>
287
-
288
-Moreover it only shows alerts with an annunciation "TRAFFIC" on PFD, and shows icons on maps (MFD and INSET), no decision is taken, no relation with transponder and no evasion scenari and no evasion scenario.
289
-
290 275
 ## Autopilot
291 276
 There are two systems supported: the GFC700 shipped with the official Stuart Buchanan's FG1000 and the Octal450's STEC 55X (and at time of writing only HDG and buggy NAV/GPS modes are available).
292 277
 
+1
zkv1000.nas
... ...
@@ -39,6 +39,7 @@ var data = { # set of data common to all devices
39 39
     aoa : 0,
40 40
     fpSize : 0,
41 41
     tcas: [],
42
+    tcas_level: 0,
42 43
     settings: {
43 44
         units: {
44 45
             pressure: 'inhg',