Line data Source code
1 : /*
2 : This program is free software: you can redistribute it and/or modify
3 : it under the terms of the GNU General Public License as published by
4 : the Free Software Foundation, either version 3 of the License, or
5 : (at your option) any later version.
6 :
7 : This program is distributed in the hope that it will be useful,
8 : but WITHOUT ANY WARRANTY; without even the implied warranty of
9 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 : GNU General Public License for more details.
11 :
12 : You should have received a copy of the GNU General Public License
13 : along with this program. If not, see <http://www.gnu.org/licenses/>.
14 : */
15 :
16 : /*
17 : AP_ADSB.cpp
18 :
19 : ADS-B RF based collision avoidance module
20 : https://en.wikipedia.org/wiki/Automatic_dependent_surveillance_%E2%80%93_broadcast
21 : */
22 :
23 : #include "AP_ADSB_config.h"
24 :
25 : #if HAL_ADSB_ENABLED
26 :
27 : #include "AP_ADSB.h"
28 :
29 : #include "AP_ADSB_MAVLink.h"
30 : #include "AP_ADSB_uAvionix_UCP.h"
31 : #include "AP_ADSB_Sagetech.h"
32 : #include "AP_ADSB_Sagetech_MXS.h"
33 :
34 : #include <AP_AHRS/AP_AHRS.h>
35 : #include <AP_Baro/AP_Baro.h>
36 : #include <AP_GPS/AP_GPS.h>
37 : #include <AP_Logger/AP_Logger.h>
38 : #include <AP_Vehicle/AP_Vehicle_Type.h>
39 : #include <GCS_MAVLink/GCS.h>
40 : #include <AP_RTC/AP_RTC.h>
41 :
42 : #define VEHICLE_TIMEOUT_MS 5000 // if no updates in this time, drop it from the list
43 : #define ADSB_SQUAWK_OCTAL_DEFAULT 1200
44 :
45 : #ifndef ADSB_VEHICLE_LIST_SIZE_DEFAULT
46 : #define ADSB_VEHICLE_LIST_SIZE_DEFAULT 25
47 : #endif
48 :
49 : #ifndef ADSB_LIST_RADIUS_DEFAULT
50 : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
51 : #define ADSB_LIST_RADIUS_DEFAULT 10000 // in meters
52 : #else
53 : #define ADSB_LIST_RADIUS_DEFAULT 2000 // in meters
54 : #endif
55 : #endif
56 :
57 : #ifndef AP_ADSB_TYPE_DEFAULT
58 : #define AP_ADSB_TYPE_DEFAULT 0
59 : #endif
60 :
61 : extern const AP_HAL::HAL& hal;
62 :
63 : AP_ADSB *AP_ADSB::_singleton;
64 :
65 : // table of user settable parameters
66 : const AP_Param::GroupInfo AP_ADSB::var_info[] = {
67 : // @Param: TYPE
68 : // @DisplayName: ADSB Type
69 : // @Description: Type of ADS-B hardware for ADSB-in and ADSB-out configuration and operation. Incoming MAVLink ADSB_VEHICLE messages are processed for any non-zero type. Select 1 (MAVLink) either for a MAVLink-connected transceiver such as the uAvionix ping, or to process ADSB_VEHICLE messages forwarded from a companion computer with no ADS-B hardware attached. ADSB-out is only sent once a transceiver reports its health, so with no hardware attached this type is receive-only.
70 : // @Values: 0:Disabled,1:MAVLink,2:Sagetech,3:uAvionix-UCP,4:Sagetech MX Series
71 : // @User: Standard
72 : // @RebootRequired: True
73 : AP_GROUPINFO_FLAGS("TYPE", 0, AP_ADSB, _type[0], AP_ADSB_TYPE_DEFAULT, AP_PARAM_FLAG_ENABLE),
74 :
75 : // index 1 is reserved - was BEHAVIOR
76 :
77 : // @Param: LIST_MAX
78 : // @DisplayName: ADSB vehicle list size
79 : // @Description: ADSB list size of nearest vehicles. Longer lists take longer to refresh with lower SRx_ADSB values.
80 : // @Range: 1 100
81 : // @User: Advanced
82 : // @RebootRequired: True
83 : AP_GROUPINFO("LIST_MAX", 2, AP_ADSB, in_state.list_size_param, ADSB_VEHICLE_LIST_SIZE_DEFAULT),
84 :
85 :
86 : // @Param: LIST_RADIUS
87 : // @DisplayName: ADSB vehicle list radius filter
88 : // @Description: ADSB vehicle list radius filter. Vehicles detected outside this radius will be completely ignored. They will not show up in the SRx_ADSB stream to the GCS and will not be considered in any avoidance calculations. A value of 0 will disable this filter.
89 : // @Range: 0 100000
90 : // @User: Advanced
91 : // @Units: m
92 : AP_GROUPINFO("LIST_RADIUS", 3, AP_ADSB, in_state.list_radius, ADSB_LIST_RADIUS_DEFAULT),
93 :
94 : // @Param: ICAO_ID
95 : // @DisplayName: ICAO_ID vehicle identification number
96 : // @Description: ICAO_ID unique vehicle identification number of this aircraft. This is an integer limited to 24bits. If set to 0 then one will be randomly generated. If set to -1 then static information is not sent, transceiver is assumed pre-programmed.
97 : // @Range: -1 16777215
98 : // @User: Advanced
99 : AP_GROUPINFO("ICAO_ID", 4, AP_ADSB, out_state.cfg.ICAO_id_param, 0),
100 :
101 : // @Param: EMIT_TYPE
102 : // @DisplayName: Emitter type
103 : // @Description: ADSB classification for the type of vehicle emitting the transponder signal. Default value is 14 (UAV).
104 : // @Values: 0:NoInfo,1:Light,2:Small,3:Large,4:HighVortexlarge,5:Heavy,6:HighlyManuv,7:Rotocraft,8:RESERVED,9:Glider,10:LightAir,11:Parachute,12:UltraLight,13:RESERVED,14:UAV,15:Space,16:RESERVED,17:EmergencySurface,18:ServiceSurface,19:PointObstacle
105 : // @User: Advanced
106 : AP_GROUPINFO("EMIT_TYPE", 5, AP_ADSB, out_state.cfg.emitterType, ADSB_EMITTER_TYPE_UAV),
107 :
108 : // @Param: LEN_WIDTH
109 : // @DisplayName: Aircraft length and width
110 : // @Description: Aircraft length and width dimension options in Length and Width in meters. In most cases, use a value of 1 for smallest size.
111 : // @Values: 0:NO_DATA,1:L15W23,2:L25W28P5,3:L25W34,4:L35W33,5:L35W38,6:L45W39P5,7:L45W45,8:L55W45,9:L55W52,10:L65W59P5,11:L65W67,12:L75W72P5,13:L75W80,14:L85W80,15:L85W90
112 : // @User: Advanced
113 : AP_GROUPINFO("LEN_WIDTH", 6, AP_ADSB, out_state.cfg.lengthWidth, UAVIONIX_ADSB_OUT_CFG_AIRCRAFT_SIZE_L15M_W23M),
114 :
115 : // @Param: OFFSET_LAT
116 : // @DisplayName: GPS antenna lateral offset
117 : // @Description: GPS antenna lateral offset. This describes the physical location offset from center of the GPS antenna on the aircraft.
118 : // @Values: 0:NoData,1:Left2m,2:Left4m,3:Left6m,4:Center,5:Right2m,6:Right4m,7:Right6m
119 : // @User: Advanced
120 : AP_GROUPINFO("OFFSET_LAT", 7, AP_ADSB, out_state.cfg.gpsOffsetLat, UAVIONIX_ADSB_OUT_CFG_GPS_OFFSET_LAT_RIGHT_0M),
121 :
122 : // @Param: OFFSET_LON
123 : // @DisplayName: GPS antenna longitudinal offset
124 : // @Description: GPS antenna longitudinal offset. This is usually set to 1, Applied By Sensor
125 : // @Values: 0:NO_DATA,1:AppliedBySensor
126 : // @User: Advanced
127 : AP_GROUPINFO("OFFSET_LON", 8, AP_ADSB, out_state.cfg.gpsOffsetLon, UAVIONIX_ADSB_OUT_CFG_GPS_OFFSET_LON_APPLIED_BY_SENSOR),
128 :
129 : // @Param: RF_SELECT
130 : // @DisplayName: Transceiver RF selection
131 : // @Description: Transceiver RF selection for Rx enable and/or Tx enable. This only effects devices that can Tx and/or Rx. Rx-only devices should override this to always be Rx-only.
132 : // @Bitmask: 0:Rx,1:Tx
133 : // @User: Advanced
134 : AP_GROUPINFO("RF_SELECT", 9, AP_ADSB, out_state.cfg.rfSelect, UAVIONIX_ADSB_OUT_RF_SELECT_RX_ENABLED),
135 :
136 : // @Param: SQUAWK
137 : // @DisplayName: Squawk code
138 : // @Description: VFR squawk (Mode 3/A) code is a pre-programmed default code when the pilot is flying VFR and not in contact with ATC. In the USA, the VFR squawk code is octal 1200 (hex 0x280, decimal 640) and in most parts of Europe the VFR squawk code is octal 7000. If an invalid octal number is set then it will be reset to 1200.
139 : // @Range: 0 7777
140 : // @Units: octal
141 : // @User: Advanced
142 : AP_GROUPINFO("SQUAWK", 10, AP_ADSB, out_state.cfg.squawk_octal_param, ADSB_SQUAWK_OCTAL_DEFAULT),
143 :
144 : // @Param: RF_CAPABLE
145 : // @DisplayName: RF capabilities
146 : // @Description: Describes your hardware RF In/Out capabilities.
147 : // @Bitmask: 0:UAT_in,1:1090ES_in,2:UAT_out,3:1090ES_out
148 : // @User: Advanced
149 : AP_GROUPINFO("RF_CAPABLE", 11, AP_ADSB, out_state.cfg.rf_capable, 0),
150 :
151 : // @Param: LIST_ALT
152 : // @DisplayName: ADSB vehicle list altitude filter
153 : // @Description: ADSB vehicle list altitude filter. Vehicles detected more than this altitude above our own altitude will be completely ignored. They will not show up in the SRx_ADSB stream to the GCS and will not be considered in any avoidance calculations. A value of 0 will disable this filter.
154 : // @Range: 0 32767
155 : // @User: Advanced
156 : // @Units: m
157 : AP_GROUPINFO("LIST_ALT", 12, AP_ADSB, in_state.list_altitude, 0),
158 :
159 : // @Param: ICAO_SPECL
160 : // @DisplayName: ICAO_ID of special vehicle
161 : // @Description: ICAO_ID of special vehicle that ignores ADSB_LIST_RADIUS and ADSB_LIST_ALT. The vehicle is always tracked. Use 0 to disable.
162 : // @User: Advanced
163 : AP_GROUPINFO("ICAO_SPECL", 13, AP_ADSB, _special_ICAO_target, 0),
164 :
165 : // @Param: LOG
166 : // @DisplayName: ADS-B logging
167 : // @Description: 0: no logging, 1: log only special ID, 2:log all
168 : // @Values: 0:no logging,1:log only special ID,2:log all
169 : // @User: Advanced
170 : AP_GROUPINFO("LOG", 14, AP_ADSB, _log, 1),
171 :
172 : // @Param: OPTIONS
173 : // @DisplayName: ADS-B Options
174 : // @Description: Options for emergency failsafe codes and device capabilities
175 : // @Bitmask: 0:Ping200X Send GPS,1:Squawk 7400 on RC failsafe,2:Squawk 7400 on GCS failsafe,3:Sagetech MXS use External Config,4:Transmit in traditional Mode 3A/C only and inhibit Mode-S and ES (ADSB) transmissions
176 : // @User: Advanced
177 : AP_GROUPINFO("OPTIONS", 15, AP_ADSB, _options, 0),
178 :
179 : AP_GROUPEND
180 : };
181 :
182 : // constructor
183 3386 : AP_ADSB::AP_ADSB()
184 : {
185 1693 : AP_Param::setup_object_defaults(this, var_info);
186 : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
187 1693 : if (_singleton != nullptr) {
188 0 : AP_HAL::panic("AP_ADSB must be singleton");
189 : }
190 : #endif
191 1693 : _singleton = this;
192 :
193 : #ifdef ADSB_STATIC_CALLSIGN
194 : strncpy(out_state.cfg.callsign, ADSB_STATIC_CALLSIGN, sizeof(out_state.cfg.callsign));
195 : #endif
196 1693 : }
197 :
198 : /*
199 : * Initialize variables and allocate memory for array
200 : */
201 10 : void AP_ADSB::init(void)
202 : {
203 10 : if (in_state.vehicle_list == nullptr) {
204 : // sanity check param
205 10 : in_state.list_size_param.set(constrain_int16(in_state.list_size_param, 1, INT16_MAX));
206 :
207 10 : in_state.vehicle_list = NEW_NOTHROW adsb_vehicle_t[in_state.list_size_param];
208 :
209 10 : if (in_state.vehicle_list == nullptr) {
210 : // dynamic RAM allocation of in_state.vehicle_list[] failed
211 0 : _init_failed = true; // this keeps us from constantly trying to init forever in main update
212 0 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ADSB: Unable to initialize ADSB vehicle list");
213 0 : return;
214 : }
215 10 : in_state.list_size_allocated = in_state.list_size_param;
216 : }
217 :
218 10 : if (detected_num_instances == 0) {
219 20 : for (uint8_t i=0; i<ADSB_MAX_INSTANCES; i++) {
220 10 : detect_instance(i);
221 10 : if (_backend[i] == nullptr) {
222 0 : continue;
223 : }
224 10 : if (!_backend[i]->init()) {
225 0 : delete _backend[i];
226 0 : _backend[i] = nullptr;
227 0 : continue;
228 : }
229 : // success
230 10 : detected_num_instances = i+1;
231 : }
232 : }
233 :
234 10 : if (detected_num_instances == 0) {
235 0 : _init_failed = true;
236 0 : GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ADSB: Unable to initialize ADSB driver");
237 : }
238 : }
239 :
240 2254270 : bool AP_ADSB::check_startup()
241 : {
242 2254270 : if (_init_failed) {
243 0 : return false;
244 : }
245 :
246 2254270 : bool all_backends_disabled = true;
247 4494511 : for (uint8_t instance=0; instance<ADSB_MAX_INSTANCES; instance++) {
248 2254270 : if (_type[instance] > 0) {
249 14029 : all_backends_disabled = false;
250 14029 : break;
251 : }
252 : }
253 :
254 2254270 : if (all_backends_disabled) {
255 : // nothing to do
256 2240241 : return false;
257 : }
258 14029 : if (in_state.vehicle_list == nullptr) {
259 10 : init();
260 : }
261 14029 : return in_state.vehicle_list != nullptr;
262 : }
263 :
264 :
265 : // detect if an instance of an ADSB sensor is connected.
266 10 : void AP_ADSB::detect_instance(uint8_t instance)
267 : {
268 10 : switch (get_type(instance)) {
269 0 : case Type::None:
270 0 : return;
271 :
272 8 : case Type::MAVLink:
273 : #if HAL_ADSB_MAVLINK_ENABLED
274 8 : if (AP_ADSB_MAVLink::detect()) {
275 8 : _backend[instance] = NEW_NOTHROW AP_ADSB_MAVLink(*this, instance);
276 : }
277 : #endif
278 8 : break;
279 :
280 0 : case Type::uAvionix_UCP:
281 : #if HAL_ADSB_UCP_ENABLED
282 0 : if (AP_ADSB_uAvionix_UCP::detect()) {
283 0 : _backend[instance] = NEW_NOTHROW AP_ADSB_uAvionix_UCP(*this, instance);
284 : }
285 : #endif
286 0 : break;
287 :
288 0 : case Type::Sagetech:
289 : #if HAL_ADSB_SAGETECH_ENABLED
290 0 : if (AP_ADSB_Sagetech::detect()) {
291 0 : _backend[instance] = NEW_NOTHROW AP_ADSB_Sagetech(*this, instance);
292 : }
293 : #endif
294 0 : break;
295 :
296 2 : case Type::Sagetech_MXS:
297 : #if HAL_ADSB_SAGETECH_MXS_ENABLED
298 2 : if (AP_ADSB_Sagetech_MXS::detect()) {
299 2 : _backend[instance] = NEW_NOTHROW AP_ADSB_Sagetech_MXS(*this, instance);
300 : }
301 : #endif
302 2 : break;
303 : }
304 :
305 : }
306 :
307 : // get instance type from instance
308 10 : AP_ADSB::Type AP_ADSB::get_type(uint8_t instance) const
309 : {
310 10 : if (instance < ADSB_MAX_INSTANCES) {
311 10 : return (Type)(_type[instance].get());
312 : }
313 0 : return Type::None;
314 : }
315 :
316 60 : bool AP_ADSB::is_valid_callsign(uint16_t octal)
317 : {
318 : // treat "octal" as decimal and test if any decimal digit is > 7
319 60 : if (octal > 7777) {
320 12 : return false;
321 : }
322 :
323 150 : while (octal != 0) {
324 110 : if (octal % 10 > 7) {
325 8 : return false;
326 : }
327 102 : octal /= 10;
328 : }
329 :
330 40 : return true;
331 : }
332 :
333 : #if AP_GPS_ENABLED && AP_AHRS_ENABLED && AP_BARO_ENABLED
334 : /*
335 : * periodic update to handle vehicle timeouts and trigger collision detection
336 : */
337 1306596 : void AP_ADSB::update(void)
338 : {
339 1306596 : Loc loc{};
340 1306596 : if (!AP::ahrs().get_location(loc)) {
341 90888 : loc.zero();
342 : }
343 :
344 1306596 : const AP_GPS &gps = AP::gps();
345 :
346 1306596 : loc.fix_type = gps.status();
347 1306596 : loc.epoch_us = gps.time_epoch_usec();
348 : #if AP_RTC_ENABLED
349 1306596 : loc.have_epoch_from_rtc_us = AP::rtc().get_utc_usec(loc.epoch_from_rtc_us);
350 : #endif
351 :
352 1306596 : loc.satellites = gps.num_sats();
353 :
354 1306596 : loc.horizontal_pos_accuracy_is_valid = gps.horizontal_accuracy(loc.horizontal_pos_accuracy);
355 1306596 : loc.vertical_pos_accuracy_is_valid = gps.vertical_accuracy(loc.vertical_pos_accuracy);
356 1306596 : loc.horizontal_vel_accuracy_is_valid = gps.speed_accuracy(loc.horizontal_vel_accuracy);
357 :
358 :
359 1306596 : loc.vel_ned = gps.velocity();
360 :
361 1306596 : loc.vertRateD_is_valid = AP::ahrs().get_vert_pos_rate_D(loc.vertRateD);
362 :
363 1306596 : const auto &baro = AP::baro();
364 1306596 : loc.baro_is_healthy = baro.healthy();
365 :
366 : // Altitude difference between sea level pressure and current
367 : // pressure (in metres)
368 1306596 : if (loc.baro_is_healthy) {
369 1306085 : loc.baro_alt_press_diff_sea_level = baro.get_altitude_difference(SSL_AIR_PRESSURE, baro.get_pressure());
370 : }
371 :
372 1306596 : update(loc);
373 1306596 : }
374 : #endif // AP_GPS_ENABLED && AP_AHRS_ENABLED
375 :
376 1306596 : void AP_ADSB::update(const AP_ADSB::Loc &loc)
377 : {
378 1306596 : if (!check_startup()) {
379 1303679 : return;
380 : }
381 :
382 2917 : _my_loc = loc;
383 :
384 2917 : const uint32_t now = AP_HAL::millis();
385 :
386 : // check current list for vehicles that time out
387 2917 : uint16_t index = 0;
388 3640 : while (index < in_state.vehicle_count) {
389 : // check list and drop stale vehicles. When disabled, the list will get flushed
390 723 : if (now - in_state.vehicle_list[index].last_update_ms > VEHICLE_TIMEOUT_MS) {
391 : // don't increment index, we want to check this same index again because the contents changed
392 : // also, if we're disabled then clear the list
393 5 : delete_vehicle(index);
394 : } else {
395 718 : index++;
396 : }
397 : }
398 :
399 2917 : if (out_state.cfg.squawk_octal_param != out_state.cfg.squawk_octal) {
400 : // param changed, check that it's a valid octal
401 10 : if (!is_valid_callsign(out_state.cfg.squawk_octal_param)) {
402 : // invalid, reset it to default
403 0 : out_state.cfg.squawk_octal_param.set(ADSB_SQUAWK_OCTAL_DEFAULT);
404 : }
405 10 : out_state.cfg.squawk_octal = (uint16_t)out_state.cfg.squawk_octal_param;
406 : }
407 :
408 : // ensure it's positive 24bit but allow -1
409 2917 : if (out_state.cfg.ICAO_id_param <= -1 || out_state.cfg.ICAO_id_param > 0x00FFFFFF) {
410 : // icao param of -1 means static information is not sent, transceiver is assumed pre-programmed.
411 : // reset timer constantly so it never reaches 10s so it never sends
412 0 : out_state.last_config_ms = now;
413 :
414 2917 : } else if ((out_state.cfg.rfSelect & UAVIONIX_ADSB_OUT_RF_SELECT_TX_ENABLED) &&
415 0 : (out_state.cfg.ICAO_id == 0 || out_state.cfg.ICAO_id_param_prev != out_state.cfg.ICAO_id_param)) {
416 :
417 : // if param changed then regenerate. This allows the param to be changed back to zero to trigger a re-generate
418 0 : if (out_state.cfg.ICAO_id_param == 0) {
419 0 : out_state.cfg.ICAO_id = genICAO(_my_loc);
420 : } else {
421 0 : out_state.cfg.ICAO_id = out_state.cfg.ICAO_id_param;
422 : }
423 0 : out_state.cfg.ICAO_id_param_prev = out_state.cfg.ICAO_id_param;
424 :
425 : #ifndef ADSB_STATIC_CALLSIGN
426 0 : if (!out_state.cfg.was_set_externally) {
427 0 : set_callsign("ARDU", true);
428 : }
429 : #endif
430 0 : out_state.last_config_ms = 0; // send now
431 : }
432 :
433 5834 : for (uint8_t i=0; i<detected_num_instances; i++) {
434 2917 : if (_backend[i] != nullptr && _type[i].get() != (int8_t)Type::None) {
435 2917 : _backend[i]->update();
436 : }
437 : }
438 :
439 : }
440 :
441 : /*
442 : * determine index and distance of furthest vehicle. This is
443 : * used to bump it off when a new closer aircraft is detected
444 : */
445 0 : void AP_ADSB::determine_furthest_aircraft(void)
446 : {
447 0 : float max_distance = 0;
448 0 : uint16_t max_distance_index = 0;
449 :
450 0 : for (uint16_t index = 0; index < in_state.vehicle_count; index++) {
451 0 : if (is_special_vehicle(in_state.vehicle_list[index].info.ICAO_address)) {
452 0 : continue;
453 : }
454 0 : const float distance = _my_loc.get_distance(get_location(in_state.vehicle_list[index]));
455 0 : if (max_distance < distance || index == 0) {
456 0 : max_distance = distance;
457 0 : max_distance_index = index;
458 : }
459 : } // for index
460 :
461 0 : in_state.furthest_vehicle_index = max_distance_index;
462 0 : in_state.furthest_vehicle_distance = max_distance;
463 0 : }
464 :
465 : /*
466 : * Convert/Extract a Location from a vehicle
467 : */
468 8398 : Location AP_ADSB::get_location(const adsb_vehicle_t &vehicle) const
469 : {
470 : const Location loc = Location(
471 8398 : vehicle.info.lat,
472 8398 : vehicle.info.lon,
473 8398 : vehicle.info.altitude * 0.1f,
474 8398 : Location::AltFrame::ABSOLUTE);
475 :
476 8398 : return loc;
477 : }
478 :
479 : /*
480 : * delete a vehicle by copying last vehicle to
481 : * current index then decrementing count
482 : */
483 6 : void AP_ADSB::delete_vehicle(const uint16_t index)
484 : {
485 6 : if (index >= in_state.vehicle_count) {
486 : // index out of range
487 0 : return;
488 : }
489 :
490 : // if the vehicle is the furthest, invalidate it. It has been bumped
491 6 : if (index == in_state.furthest_vehicle_index && in_state.furthest_vehicle_distance > 0) {
492 0 : in_state.furthest_vehicle_distance = 0;
493 0 : in_state.furthest_vehicle_index = 0;
494 : }
495 6 : if (index != (in_state.vehicle_count-1)) {
496 1 : in_state.vehicle_list[index] = in_state.vehicle_list[in_state.vehicle_count-1];
497 : }
498 : // TODO: is memset needed? When we decrement the index we essentially forget about it
499 6 : memset(&in_state.vehicle_list[in_state.vehicle_count-1], 0, sizeof(adsb_vehicle_t));
500 6 : in_state.vehicle_count--;
501 : }
502 :
503 : /*
504 : * Search _vehicle_list for the given vehicle. A match
505 : * depends on ICAO_address. Returns true if match found
506 : * and index is populated. otherwise, return false.
507 : */
508 16775 : bool AP_ADSB::find_index(const adsb_vehicle_t &vehicle, uint16_t *index) const
509 : {
510 51951 : for (uint16_t i = 0; i < in_state.vehicle_count; i++) {
511 51932 : if (in_state.vehicle_list[i].info.ICAO_address == vehicle.info.ICAO_address) {
512 16756 : *index = i;
513 16756 : return true;
514 : }
515 : }
516 19 : return false;
517 : }
518 :
519 : /*
520 : * Update the vehicle list. If the vehicle is already in the
521 : * list then it will update it, otherwise it will be added.
522 : */
523 8398 : void AP_ADSB::handle_adsb_vehicle(const adsb_vehicle_t &vehicle)
524 : {
525 8398 : if (!check_startup()) {
526 7 : return;
527 : }
528 :
529 8392 : uint16_t index = in_state.list_size_allocated + 1; // initialize with invalid index
530 8392 : const Location vehicle_loc = AP_ADSB::get_location(vehicle);
531 8392 : const bool my_loc_is_zero = _my_loc.is_zero();
532 8392 : const float my_loc_distance_to_vehicle = _my_loc.get_distance(vehicle_loc);
533 8392 : const bool is_special = is_special_vehicle(vehicle.info.ICAO_address);
534 8392 : const bool out_of_range = in_state.list_radius > 0 && !my_loc_is_zero && my_loc_distance_to_vehicle > in_state.list_radius && !is_special;
535 8392 : const bool out_of_range_alt = in_state.list_altitude > 0 && !my_loc_is_zero && abs(vehicle_loc.alt - _my_loc.alt) > in_state.list_altitude*100 && !is_special;
536 8392 : const bool is_tracked_in_list = find_index(vehicle, &index);
537 8392 : const uint32_t now = AP_HAL::millis();
538 :
539 8392 : const uint16_t required_flags_position = ADSB_FLAGS_VALID_COORDS | ADSB_FLAGS_VALID_ALTITUDE;
540 8392 : const bool detected_ourself = (out_state.cfg.ICAO_id != 0) && ((uint32_t)out_state.cfg.ICAO_id == vehicle.info.ICAO_address);
541 :
542 16784 : if (vehicle_loc.is_zero() ||
543 8391 : out_of_range ||
544 8391 : out_of_range_alt ||
545 8391 : detected_ourself ||
546 8391 : (vehicle.info.ICAO_address > 0x00FFFFFF) || // ICAO address is 24bits, so ignore higher values.
547 25175 : !(vehicle.info.flags & required_flags_position) ||
548 8391 : now - vehicle.last_update_ms > VEHICLE_TIMEOUT_MS) {
549 :
550 : // vehicle is out of range or invalid lat/lng. If we're tracking it, delete from list. Otherwise ignore it.
551 1 : if (is_tracked_in_list) {
552 1 : delete_vehicle(index);
553 : }
554 1 : return;
555 :
556 8391 : } else if (is_tracked_in_list) {
557 :
558 : // found, update it
559 8378 : set_vehicle(index, vehicle);
560 :
561 13 : } else if (in_state.vehicle_count < in_state.list_size_allocated) {
562 :
563 : // not found and there's room, add it to the end of the list
564 13 : set_vehicle(in_state.vehicle_count, vehicle);
565 13 : in_state.vehicle_count++;
566 :
567 : } else {
568 : // buffer is full. if new vehicle is closer than furthest, replace furthest with new
569 :
570 0 : if (my_loc_is_zero) {
571 : // nothing else to do
572 0 : in_state.furthest_vehicle_distance = 0;
573 0 : in_state.furthest_vehicle_index = 0;
574 :
575 : } else {
576 0 : if (in_state.furthest_vehicle_distance <= 0) {
577 : // ensure this is populated
578 0 : determine_furthest_aircraft();
579 : }
580 :
581 0 : if (my_loc_distance_to_vehicle < in_state.furthest_vehicle_distance) { // is closer than the furthest
582 : // replace with the furthest vehicle
583 0 : set_vehicle(in_state.furthest_vehicle_index, vehicle);
584 :
585 : // in_state.furthest_vehicle_index is now invalid because the vehicle was overwritten, need
586 : // to run determine_furthest_aircraft() to determine a new one next time
587 0 : in_state.furthest_vehicle_distance = 0;
588 0 : in_state.furthest_vehicle_index = 0;
589 : }
590 : }
591 : } // if buffer full
592 :
593 8391 : const uint16_t required_flags_avoidance =
594 : ADSB_FLAGS_VALID_COORDS |
595 : ADSB_FLAGS_VALID_ALTITUDE |
596 : ADSB_FLAGS_VALID_HEADING |
597 : ADSB_FLAGS_VALID_VELOCITY;
598 :
599 8391 : if (vehicle.info.flags & required_flags_avoidance) {
600 8391 : push_sample(vehicle); // note that set_vehicle modifies vehicle
601 : }
602 : }
603 :
604 : /*
605 : * Copy a vehicle's data into the list
606 : */
607 8391 : void AP_ADSB::set_vehicle(const uint16_t index, const adsb_vehicle_t &vehicle)
608 : {
609 8391 : if (index >= in_state.list_size_allocated) {
610 : // out of range
611 0 : return;
612 : }
613 8391 : in_state.vehicle_list[index] = vehicle;
614 :
615 : #if HAL_LOGGING_ENABLED
616 8391 : write_log(vehicle);
617 : #endif
618 : }
619 :
620 939274 : void AP_ADSB::send_adsb_vehicle(const mavlink_channel_t chan)
621 : {
622 939274 : if (!check_startup() || in_state.vehicle_count == 0) {
623 938951 : return;
624 : }
625 :
626 323 : uint32_t now = AP_HAL::millis();
627 :
628 323 : if (in_state.send_index[chan] >= in_state.vehicle_count) {
629 : // we've finished a list
630 232 : if (now - in_state.send_start_ms[chan] < 1000) {
631 : // too soon to start a new one
632 193 : return;
633 : } else {
634 : // start new list
635 39 : in_state.send_start_ms[chan] = now;
636 39 : in_state.send_index[chan] = 0;
637 : }
638 : }
639 :
640 130 : if (in_state.send_index[chan] < in_state.vehicle_count) {
641 130 : mavlink_adsb_vehicle_t vehicle = in_state.vehicle_list[in_state.send_index[chan]].info;
642 130 : in_state.send_index[chan]++;
643 :
644 130 : mavlink_msg_adsb_vehicle_send(chan,
645 : vehicle.ICAO_address,
646 : vehicle.lat,
647 : vehicle.lon,
648 130 : vehicle.altitude_type,
649 : vehicle.altitude,
650 130 : vehicle.heading,
651 130 : vehicle.hor_velocity,
652 130 : vehicle.ver_velocity,
653 : vehicle.callsign,
654 130 : vehicle.emitter_type,
655 130 : vehicle.tslc,
656 130 : vehicle.flags,
657 130 : vehicle.squawk);
658 : }
659 : }
660 :
661 : /*
662 : * handle incoming packet UAVIONIX_ADSB_OUT_CFG.
663 : * This allows a GCS to send cfg info through the autopilot to the ADSB hardware.
664 : * This is done indirectly by reading and storing the packet and then another mechanism sends it out periodically
665 : */
666 0 : void AP_ADSB::handle_out_cfg(const mavlink_uavionix_adsb_out_cfg_t &packet)
667 : {
668 0 : out_state.cfg.was_set_externally = true;
669 :
670 0 : out_state.cfg.ICAO_id = packet.ICAO;
671 0 : out_state.cfg.ICAO_id_param.set(out_state.cfg.ICAO_id_param_prev = packet.ICAO & 0x00FFFFFFFF);
672 :
673 : // May contain a non-null value at the end so accept it as-is with memcpy instead of strcpy
674 0 : memcpy(out_state.cfg.callsign, packet.callsign, sizeof(out_state.cfg.callsign));
675 :
676 0 : out_state.cfg.emitterType.set(packet.emitterType);
677 0 : out_state.cfg.lengthWidth.set(packet.aircraftSize);
678 0 : out_state.cfg.gpsOffsetLat.set(packet.gpsOffsetLat);
679 0 : out_state.cfg.gpsOffsetLon.set(packet.gpsOffsetLon);
680 0 : out_state.cfg.rfSelect.set(packet.rfSelect);
681 0 : out_state.cfg.stall_speed_cm = packet.stallSpeed;
682 :
683 : // guard against string with non-null end char
684 0 : char tmp_callsign[MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG_FIELD_CALLSIGN_LEN+1] {};
685 0 : memcpy(tmp_callsign, out_state.cfg.callsign, MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG_FIELD_CALLSIGN_LEN);
686 0 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ADSB: Using ICAO_id %d and Callsign %s", (int)out_state.cfg.ICAO_id, tmp_callsign);
687 :
688 : // send now
689 0 : out_state.last_config_ms = 0;
690 0 : }
691 :
692 : /*
693 : * handle incoming packet UAVIONIX_ADSB_OUT_CONTROL
694 : * allows a GCS to set the contents of the control message sent by ardupilot to the transponder
695 : */
696 0 : void AP_ADSB::handle_out_control(const mavlink_uavionix_adsb_out_control_t &packet)
697 : {
698 0 : out_state.ctrl.baroCrossChecked = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_EXTERNAL_BARO_CROSSCHECKED;
699 0 : out_state.ctrl.airGroundState = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_ON_GROUND;
700 0 : out_state.ctrl.modeAEnabled = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_MODE_A_ENABLED;
701 0 : out_state.ctrl.modeCEnabled = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_MODE_C_ENABLED;
702 0 : out_state.ctrl.modeSEnabled = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_MODE_S_ENABLED;
703 0 : out_state.ctrl.es1090TxEnabled = packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_1090ES_TX_ENABLED;
704 0 : out_state.ctrl.externalBaroAltitude_mm = packet.baroAltMSL;
705 0 : out_state.ctrl.squawkCode = packet.squawk;
706 0 : out_state.ctrl.emergencyState = packet.emergencyStatus;
707 0 : memcpy(out_state.ctrl.callsign, packet.flight_id, sizeof(out_state.ctrl.callsign));
708 0 : out_state.ctrl.x_bit = packet.x_bit;
709 :
710 0 : if (packet.state & UAVIONIX_ADSB_OUT_CONTROL_STATE::UAVIONIX_ADSB_OUT_CONTROL_STATE_IDENT_BUTTON_ACTIVE) {
711 0 : IGNORE_RETURN(ident_start());
712 : }
713 0 : }
714 :
715 : /*
716 : * this is a message from the transceiver reporting it's health. Using this packet
717 : * we determine which channel is on so we don't have to send out_state to all channels
718 : */
719 0 : void AP_ADSB::handle_transceiver_report(const mavlink_channel_t chan, const mavlink_uavionix_adsb_transceiver_health_report_t &packet)
720 : {
721 0 : if (out_state.chan != chan) {
722 0 : GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "ADSB: Found transceiver on channel %d", chan);
723 : }
724 :
725 0 : out_state.chan_last_ms = AP_HAL::millis();
726 0 : out_state.chan = chan;
727 0 : out_state.status = (UAVIONIX_ADSB_RF_HEALTH)packet.rfHealth;
728 0 : }
729 :
730 : /*
731 : * send a periodic report of the ADSB out status
732 : */
733 0 : void AP_ADSB::send_adsb_out_status(const mavlink_channel_t chan) const
734 : {
735 0 : for (uint8_t i=0; i < ADSB_MAX_INSTANCES; i++) {
736 0 : if (_type[i] == (int8_t)(AP_ADSB::Type::uAvionix_UCP) || _type[i] == (int8_t)(AP_ADSB::Type::Sagetech_MXS)) {
737 0 : mavlink_msg_uavionix_adsb_out_status_send_struct(chan, &out_state.tx_status);
738 0 : return;
739 : }
740 : }
741 : }
742 :
743 : /*
744 : @brief Generates pseudorandom ICAO from gps time, lat, and lon.
745 : Reference: DO282B, 2.2.4.5.1.3.2
746 : */
747 0 : uint32_t AP_ADSB::genICAO(const Location &loc) const
748 : {
749 : // gps_time is used as a pseudo-random number instead of seconds since UTC midnight
750 : // TODO: use UTC time instead of GPS time
751 0 : const AP_ADSB::Loc &gps { _my_loc };
752 0 : const uint64_t gps_time = gps.time_epoch_usec();
753 :
754 0 : uint32_t timeSum = 0;
755 0 : const uint32_t M3 = 4096 * (loc.lat & 0x00000FFF) + (loc.lng & 0x00000FFF);
756 :
757 0 : for (uint8_t i=0; i<24; i++) {
758 0 : timeSum += (((gps_time & 0x00FFFFFF)>> i) & 0x00000001);
759 : }
760 0 : return( (timeSum ^ M3) & 0x00FFFFFF);
761 : }
762 :
763 : // assign a string to out_state.cfg.callsign but ensure it's null terminated
764 0 : void AP_ADSB::set_callsign(const char* str, const bool append_icao)
765 : {
766 0 : bool zero_char_pad = false;
767 :
768 : // clean slate
769 0 : memset(out_state.cfg.callsign, 0, sizeof(out_state.cfg.callsign));
770 :
771 : // copy str to cfg.callsign but we can't use strncpy because we need
772 : // to restrict values to only 'A' - 'Z' and '0' - '9' and pad
773 0 : for (uint8_t i=0; i<sizeof(out_state.cfg.callsign)-1; i++) {
774 0 : if (!str[i] || zero_char_pad) {
775 : // finish early. Either pad the rest with zero char or null terminate and call it a day
776 0 : if ((append_icao && i<4) || zero_char_pad) {
777 0 : out_state.cfg.callsign[i] = '0';
778 0 : zero_char_pad = true;
779 : } else {
780 : // already null terminated via memset so just stop
781 : break;
782 : }
783 :
784 0 : } else if (('A' <= str[i] && str[i] <= 'Z') ||
785 0 : ('0' <= str[i] && str[i] <= '9')) {
786 : // valid as-is
787 : // spaces are also allowed but are handled in the last else
788 0 : out_state.cfg.callsign[i] = str[i];
789 :
790 0 : } else if ('a' <= str[i] && str[i] <= 'z') {
791 : // toupper()
792 0 : out_state.cfg.callsign[i] = str[i] - ('a' - 'A');
793 :
794 0 : } else if (i == 0) {
795 : // invalid, pad to char zero because first index can't be space
796 0 : out_state.cfg.callsign[i] = '0';
797 :
798 : } else {
799 : // invalid, pad with space
800 0 : out_state.cfg.callsign[i] = ' ';
801 : }
802 : } // for i
803 :
804 0 : if (append_icao) {
805 0 : hal.util->snprintf(&out_state.cfg.callsign[4], 5, "%04X", unsigned(out_state.cfg.ICAO_id % 0x10000));
806 : }
807 0 : }
808 :
809 :
810 8391 : void AP_ADSB::push_sample(const adsb_vehicle_t &vehicle)
811 : {
812 8391 : _samples.push(vehicle);
813 8391 : }
814 :
815 2637 : bool AP_ADSB::next_sample(adsb_vehicle_t &vehicle)
816 : {
817 2637 : return _samples.pop(vehicle);
818 : }
819 :
820 15 : void AP_ADSB::handle_message(const mavlink_channel_t chan, const mavlink_message_t &msg)
821 : {
822 15 : switch (msg.msgid) {
823 15 : case MAVLINK_MSG_ID_ADSB_VEHICLE: {
824 15 : adsb_vehicle_t vehicle {};
825 15 : mavlink_msg_adsb_vehicle_decode(&msg, &vehicle.info);
826 15 : vehicle.last_update_ms = AP_HAL::millis() - uint32_t(vehicle.info.tslc * 1000U);
827 15 : handle_adsb_vehicle(vehicle);
828 15 : break;
829 : }
830 :
831 0 : case MAVLINK_MSG_ID_UAVIONIX_ADSB_TRANSCEIVER_HEALTH_REPORT: {
832 0 : mavlink_uavionix_adsb_transceiver_health_report_t packet {};
833 0 : mavlink_msg_uavionix_adsb_transceiver_health_report_decode(&msg, &packet);
834 0 : handle_transceiver_report(chan, packet);
835 0 : break;
836 : }
837 :
838 0 : case MAVLINK_MSG_ID_UAVIONIX_ADSB_OUT_CFG: {
839 0 : mavlink_uavionix_adsb_out_cfg_t packet {};
840 0 : mavlink_msg_uavionix_adsb_out_cfg_decode(&msg, &packet);
841 0 : handle_out_cfg(packet);
842 0 : break;
843 : }
844 :
845 0 : case MAVLINK_MSG_ID_UAVIONIX_ADSB_OUT_DYNAMIC:
846 : // unhandled, this is an outbound packet only
847 0 : break;
848 :
849 0 : case MAVLINK_MSG_ID_UAVIONIX_ADSB_OUT_CONTROL: {
850 0 : mavlink_uavionix_adsb_out_control_t packet {};
851 0 : mavlink_msg_uavionix_adsb_out_control_decode(&msg, &packet);
852 0 : handle_out_control(packet);
853 0 : break;
854 : }
855 : }
856 :
857 15 : }
858 :
859 : // If that ICAO is found in the database then return true with a fully populated vehicle
860 8383 : bool AP_ADSB::get_vehicle_by_ICAO(const uint32_t icao, adsb_vehicle_t &vehicle) const
861 : {
862 : adsb_vehicle_t temp_vehicle;
863 8383 : temp_vehicle.info.ICAO_address = icao;
864 :
865 : uint16_t i;
866 8383 : if (find_index(temp_vehicle, &i)) {
867 : // vehicle is tracked in list.
868 : // we must memcpy it because the database may reorganize itself and we don't
869 : // want the reference to magically start pointing at a different vehicle
870 8377 : memcpy(&vehicle, &in_state.vehicle_list[i], sizeof(adsb_vehicle_t));
871 8377 : return true;
872 : }
873 6 : return false;
874 : }
875 :
876 : #if HAL_LOGGING_ENABLED
877 : /*
878 : * Write vehicle to log
879 : */
880 8391 : void AP_ADSB::write_log(const adsb_vehicle_t &vehicle) const
881 : {
882 8391 : switch ((Logging)_log) {
883 8391 : case Logging::SPECIAL_ONLY:
884 8391 : if (!is_special_vehicle(vehicle.info.ICAO_address)) {
885 8391 : return;
886 : }
887 0 : break;
888 :
889 0 : case Logging::ALL:
890 0 : break;
891 :
892 0 : case Logging::NONE:
893 : default:
894 0 : return;
895 : }
896 :
897 0 : struct log_ADSB pkt = {
898 : LOG_PACKET_HEADER_INIT(LOG_ADSB_MSG),
899 0 : time_us : AP_HAL::micros64(),
900 0 : ICAO_address : vehicle.info.ICAO_address,
901 0 : lat : vehicle.info.lat,
902 0 : lng : vehicle.info.lon,
903 0 : alt : vehicle.info.altitude,
904 0 : heading : vehicle.info.heading,
905 0 : hor_velocity : vehicle.info.hor_velocity,
906 0 : ver_velocity : vehicle.info.ver_velocity,
907 0 : squawk : vehicle.info.squawk,
908 0 : };
909 0 : AP::logger().WriteBlock(&pkt, sizeof(pkt));
910 : }
911 : #endif // HAL_LOGGING_ENABLED
912 :
913 : /**
914 : * @brief Convert base 8 or 16 to decimal. Used to convert an octal/hexadecimal value stored on a GCS as a string field in different format, but then transmitted over mavlink as a float which is always a decimal.
915 : * baseIn: base of input number
916 : * inputNumber: value currently in base "baseIn" to be converted to base "baseOut"
917 : *
918 : * Example: convert ADSB squawk octal "1200" stored in memory as 0x0280 to 0x04B0
919 : * uint16_t squawk_decimal = convertMathBase(8, squawk_octal);
920 : */
921 11 : uint32_t AP_ADSB::convert_base_to_decimal(const uint8_t baseIn, uint32_t inputNumber)
922 : {
923 : // Our only sensible input bases are 16 and 8
924 11 : if (baseIn != 8 && baseIn != 16) {
925 0 : return inputNumber;
926 : }
927 11 : uint32_t outputNumber = 0;
928 44 : for (uint8_t i=0; i < 10; i++) {
929 44 : outputNumber += (inputNumber % 10) * powf(baseIn, i);
930 44 : inputNumber /= 10;
931 44 : if (inputNumber == 0) break;
932 : }
933 11 : return outputNumber;
934 : }
935 :
936 : // Trigger a Mode 3/A transponder IDENT. This should only be done when requested to do so by an Air Traffic Controller.
937 : // See wikipedia for IDENT explanation https://en.wikipedia.org/wiki/Transponder_(aeronautics)
938 0 : bool AP_ADSB::ident_start()
939 : {
940 0 : if (!healthy()) {
941 0 : return false;
942 : }
943 0 : out_state.ctrl.identActive = true;
944 0 : GCS_SEND_TEXT(MAV_SEVERITY_INFO,"ADSB: IDENT!");
945 0 : return true;
946 : }
947 :
948 : // methods for embedded class Location
949 9 : bool AP_ADSB::Loc::speed_accuracy(float &sacc) const
950 : {
951 9 : if (!horizontal_vel_accuracy_is_valid) {
952 7 : return false;
953 : }
954 2 : sacc = horizontal_vel_accuracy;
955 2 : return true;
956 : }
957 :
958 9 : bool AP_ADSB::Loc::horizontal_accuracy(float &hacc) const
959 : {
960 9 : if (!horizontal_pos_accuracy_is_valid) {
961 7 : return false;
962 : }
963 2 : hacc = horizontal_pos_accuracy;
964 2 : return true;
965 : }
966 :
967 9 : bool AP_ADSB::Loc::vertical_accuracy(float &vacc) const
968 : {
969 9 : if (!vertical_pos_accuracy_is_valid) {
970 7 : return false;
971 : }
972 2 : vacc = vertical_pos_accuracy;
973 2 : return true;
974 : }
975 :
976 3 : uint8_t AP_ADSB::convert_maxknots_to_enum(const float maxAircraftSpeed_knots)
977 : {
978 3 : if (maxAircraftSpeed_knots <= 0) {
979 : // not set or unknown. no bits set
980 1 : return 0;
981 2 : } else if (maxAircraftSpeed_knots <= 75) {
982 2 : return 1;
983 0 : } else if (maxAircraftSpeed_knots <= 150) {
984 0 : return 2;
985 0 : } else if (maxAircraftSpeed_knots <= 300) {
986 0 : return 3;
987 0 : } else if (maxAircraftSpeed_knots <= 600) {
988 0 : return 4;
989 0 : } else if (maxAircraftSpeed_knots <= 1200) {
990 0 : return 5;
991 : } else {
992 0 : return 6;
993 : }
994 : }
995 :
996 66592 : AP_ADSB *AP::ADSB()
997 : {
998 66592 : return AP_ADSB::get_singleton();
999 : }
1000 : #endif // HAL_ADSB_ENABLED
|