Line data Source code
1 : #include "SIM_config.h"
2 :
3 : #if AP_SIM_ADSB_SAGETECH_MXS_ENABLED
4 :
5 : #include "SIM_ADSB_Sagetech_MXS.h"
6 :
7 : #include <AP_Common/Location.h>
8 : #include <GCS_MAVLink/GCS.h>
9 : #include <AP_InternalError/AP_InternalError.h>
10 : #include "SITL.h"
11 : #include <ctype.h>
12 : #include "SIM_Aircraft.h"
13 : #include "SIM_ADSB.h"
14 :
15 : #include <errno.h>
16 :
17 : static const uint8_t FIRST_BYTE { 0xAA };
18 :
19 : using namespace SITL;
20 :
21 2191 : void ADSB_Sagetech_MXS::move_preamble_in_buffer(uint8_t search_start_pos)
22 : {
23 : uint8_t i;
24 2191 : for (i=search_start_pos; i<buflen; i++) {
25 4 : if ((uint8_t)msg.buffer[i] == FIRST_BYTE) {
26 4 : break;
27 : }
28 : }
29 2191 : if (i == 0) {
30 2169 : return;
31 : }
32 22 : memmove(msg.buffer, &msg.buffer[i], buflen-i);
33 22 : buflen = buflen - i;
34 : }
35 :
36 13427 : void ADSB_Sagetech_MXS::update_serial_input()
37 : {
38 13427 : const ssize_t n = read_from_autopilot(&msg.buffer[buflen], ARRAY_SIZE(msg.buffer) - buflen - 1);
39 13427 : if (n < 0) {
40 : // TODO: do better here
41 0 : if (errno != EAGAIN && errno != EWOULDBLOCK && errno != 0) {
42 0 : AP_HAL::panic("Failed to read from autopilot");
43 : }
44 : } else {
45 13427 : buflen += n;
46 : }
47 :
48 :
49 : // GCS_SEND_TEXT(MAV_SEVERITY_INFO, "read (%u) bytes", (unsigned)n);
50 :
51 13427 : switch (input_state) {
52 2169 : case InputState::WANT_START_BYTE:
53 2169 : move_preamble_in_buffer();
54 2169 : if (buflen == 0) {
55 2168 : return;
56 : }
57 1 : set_input_state(InputState::WANT_PREAMBLE);
58 : FALLTHROUGH;
59 11259 : case InputState::WANT_PREAMBLE:
60 11259 : if (buflen < sizeof(msg.preamble)) {
61 11237 : return;
62 : }
63 22 : set_input_state(InputState::WANT_PAYLOAD);
64 : FALLTHROUGH;
65 22 : case InputState::WANT_PAYLOAD: {
66 : // 1 preamble, 2 flags, 1 msg + payload
67 22 : const uint8_t want_len = 4 + msg.preamble.payload_length;
68 22 : if (buflen < want_len) {
69 0 : return;
70 : }
71 22 : set_input_state(InputState::WANT_CHECKSUM);
72 : FALLTHROUGH;
73 : }
74 22 : case InputState::WANT_CHECKSUM: {
75 : // 1 start byte, 1 msg type, 1 msgid, 1 payload len
76 22 : const uint8_t want_len = sizeof(msg.preamble) + msg.preamble.payload_length + 1;
77 22 : if (buflen < want_len) {
78 0 : return;
79 : }
80 22 : const uint8_t received_checksum = msg.buffer[want_len-1];
81 :
82 22 : uint8_t calculated_checksum = 0;
83 852 : for (uint8_t i=0; i<sizeof(msg.preamble)+msg.preamble.payload_length; i++) {
84 830 : calculated_checksum += msg.buffer[i];
85 : }
86 :
87 22 : if (calculated_checksum == received_checksum) {
88 : // GCS_SEND_TEXT(MAV_SEVERITY_INFO, "sim-MXS: Got one (%u)!", (unsigned)msg.preamble.msgtype);
89 22 : handle_message();
90 22 : move_preamble_in_buffer(want_len);
91 : } else {
92 0 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "sim-MXS: Bad checksum");
93 0 : move_preamble_in_buffer(1);
94 : }
95 : // consume these bytes
96 22 : set_input_state(InputState::WANT_PREAMBLE);
97 22 : return;
98 : }
99 : }
100 : }
101 :
102 22 : bool ADSB_Sagetech_MXS::_handle_message()
103 : {
104 22 : switch (msg.preamble.msgtype) {
105 1 : case MsgType::INSTALLATION:
106 1 : return handle_message(msg.packed_installation.msg);
107 1 : case MsgType::FLIGHTID:
108 1 : return handle_message(msg.packed_flightidmessage.msg);
109 10 : case MsgType::OPMSG:
110 10 : return handle_message(msg.packed_operating.msg);
111 9 : case MsgType::GPS:
112 9 : return handle_message(msg.packed_gps.msg);
113 0 : case MsgType::DATAREQ:
114 0 : return handle_message(msg.packed_data_req.msg);
115 1 : case MsgType::TARGETREQUEST:
116 1 : return handle_message(msg.packed_target_req.msg);
117 :
118 0 : case MsgType::ACK:
119 : case MsgType::STATEVECTORREPORT:
120 : case MsgType::MODESTATUSREPORT:
121 : // we should never receive one of these?
122 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
123 0 : break;
124 : }
125 :
126 : // we should handle all messages:
127 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
128 :
129 0 : return false;
130 : }
131 :
132 22 : void ADSB_Sagetech_MXS::handle_message()
133 : {
134 22 : if (!_handle_message()) {
135 0 : return;
136 : }
137 :
138 : // store ack info for sending an ack later:
139 22 : ack_info.ackType = msg.preamble.msgtype;
140 22 : ack_info.ackId = msg.preamble.msgid;
141 22 : ack_info.send = true;
142 : }
143 :
144 1 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::Installation& opmsg)
145 : {
146 : ASSERT_STORAGE_SIZE(Installation, 36);
147 :
148 1 : if (operating_mode != OperatingMode::OFF &&
149 0 : operating_mode != OperatingMode::MAINTENANCE) {
150 : // see page 10 - ignored if not in one of those. We could
151 : // return silently here if there are race conditions
152 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
153 : }
154 :
155 : // do something!
156 :
157 1 : return true;
158 : }
159 :
160 1 : void ADSB_Sagetech_MXS::assert_good_flight_id()
161 : {
162 : static_assert(sizeof(flight_id) == 8, "correct storage size");
163 :
164 1 : bool space_seen = false;
165 9 : for (uint8_t i=0; i<sizeof(flight_id); i++) {
166 : // this doesn't seem right; they really allow any ASCII
167 : // character? vertical tabs, for example?! The standard is
168 : // ~US$750. Null (0x0) is a valid ascii character!
169 8 : if (!isascii(flight_id[i])) {
170 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
171 : }
172 8 : if (flight_id[i] == ' ') {
173 0 : space_seen = true;
174 : }
175 8 : if (space_seen && flight_id[i] != ' ') {
176 0 : INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
177 : }
178 : }
179 1 : }
180 :
181 1 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::FlightIDMessage& _msg)
182 : {
183 : ASSERT_STORAGE_SIZE(FlightIDMessage, 12);
184 :
185 : // do something!
186 1 : flight_id_set_time_ms = AP_HAL::millis();
187 1 : memcpy(flight_id, _msg.flight_id, sizeof(flight_id));
188 1 : assert_good_flight_id();
189 :
190 1 : return true;
191 : }
192 :
193 10 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::Operating& _msg)
194 : {
195 : ASSERT_STORAGE_SIZE(Operating, 12);
196 :
197 : // do something!
198 :
199 10 : return true;
200 : }
201 :
202 : #define ORD(x) (x - '0')
203 :
204 : // buffer contains DDDMM.MMMMM (degrees, minutes including fraction)
205 9 : double ADSB_Sagetech_MXS::lon_string_to_double(const uint8_t *str)
206 : {
207 9 : return ORD(str[0]) * 100 + lat_string_to_double(&str[1]);
208 : }
209 :
210 : // buffer contains DDMM.MMMMM (degrees, minutes including fraction)
211 18 : double ADSB_Sagetech_MXS::lat_string_to_double(const uint8_t *str)
212 : {
213 18 : double degrees = ORD(str[0]) * 10 + ORD(str[1]);
214 18 : double minutes = ORD(str[2]) * 10.0 + ORD(str[3]) + ORD(str[5])*0.1 + ORD(str[6])*0.01 + ORD(str[7]) * 0.001 + ORD(str[8]) * 0.0001 + ORD(str[9]) * 0.00001;
215 18 : return degrees + minutes/60.0;
216 : }
217 :
218 : #undef ORD
219 :
220 9 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::GPS& _msg)
221 : {
222 : ASSERT_STORAGE_SIZE(GPS, 63);
223 :
224 : // store data to transmit via ADSB
225 9 : info_from_vehicle.gps.lat = lat_string_to_double(_msg.latitude);
226 9 : info_from_vehicle.gps.lng = lon_string_to_double(_msg.longitude);
227 :
228 9 : return true;
229 : }
230 :
231 0 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::DataRequest& _msg)
232 : {
233 : ASSERT_STORAGE_SIZE(DataRequest, 4);
234 :
235 : // handle request to send data to vehicle. Note that the
236 : // specification says (on page 32) that the ack is sent before the
237 : // data.
238 :
239 0 : return true;
240 : }
241 :
242 1 : bool ADSB_Sagetech_MXS::handle_message(const SITL::ADSB_Sagetech_MXS::TargetRequest& _msg)
243 : {
244 : ASSERT_STORAGE_SIZE(TargetRequest, 7);
245 :
246 : // handle request to send adsb data to vehicle as it is received
247 :
248 1 : return true;
249 : }
250 :
251 :
252 13427 : void ADSB_Sagetech_MXS::update_serial_output(const Aircraft *sitl_model)
253 : {
254 : // TODO: space checks here
255 :
256 13427 : if (ack_info.send) {
257 22 : uint8_t alt[3] {};
258 22 : uint8_t state = 0;
259 : Ack ack{
260 : ack_info.ackType,
261 22 : ack_info.ackId,
262 : state, // bitmask field
263 : alt
264 22 : };
265 22 : const PackedMessage<Ack> packed { ack, MsgType::ACK, msgid++ };
266 22 : write_to_autopilot((const char*)&packed, sizeof(packed));
267 22 : ack_info.send = false;
268 : }
269 :
270 13427 : update_serial_output_vehicles(sitl_model);
271 13427 : }
272 :
273 :
274 13427 : void ADSB_Sagetech_MXS::update_serial_output_vehicles(const SITL::Aircraft *sitl_model)
275 : {
276 13427 : if (sitl_model->adsb == nullptr) {
277 0 : return;
278 : }
279 80557 : for (uint8_t i=0; i<sitl_model->adsb->num_vehicles; i++) {
280 67130 : const ADSB_Vehicle &vehicle = sitl_model->adsb->vehicles[i];
281 67130 : if (!vehicle.initialised) {
282 3 : continue;
283 : }
284 67127 : send_vehicle_message(vehicle);
285 : }
286 : }
287 :
288 : // pack a floating-point latitude or longitude into three bytes
289 : // according to Sagetech definitions:
290 134254 : void ADSB_Sagetech_MXS::pack_scaled_geocoord(uint8_t buf[3], float coord)
291 : {
292 134254 : const int32_t scaled = coord * (1U<<23) / 180.0;
293 134254 : pack_int32_into_uint8_ts(scaled, buf);
294 134254 : }
295 :
296 : // pack a floating-point altitude in metres into three bytes
297 : // according to Sagetech definitions:
298 67127 : void ADSB_Sagetech_MXS::pack_scaled_alt(uint8_t buf[3], float alt_m)
299 : {
300 67127 : const int32_t scaled = METRES_TO_FEET*alt_m * (1/0.015625);
301 67127 : pack_int32_into_uint8_ts(scaled, buf);
302 67127 : }
303 :
304 : // discards the top 8 bits from source and shoves the rest into dest
305 335635 : void ADSB_Sagetech_MXS::pack_int32_into_uint8_ts(int32_t source, uint8_t dest[3])
306 : {
307 : // FIXME: cast &source instead?
308 335635 : dest[0] = source >> 16;
309 335635 : dest[1] = source >> 8;
310 335635 : dest[2] = source >> 0;
311 335635 : }
312 :
313 0 : uint8_t ADSB_Sagetech_MXS::scaled_groundspeed(float speed_m_s) const
314 : {
315 0 : if (is_zero(speed_m_s)) {
316 0 : return 0x01;
317 : }
318 0 : const float knots = M_PER_SEC_TO_KNOTS * speed_m_s;
319 0 : if (knots < 0.125) {
320 0 : return 0x02;
321 : }
322 : static const struct Threshold {
323 : float min;
324 : uint8_t code;
325 : float increment;
326 : } thresholds[] {
327 : { 0.125, 0x03, 0.146 },
328 : { 1, 0x09, 0.25 },
329 : { 2, 0x0D, 0.50 },
330 : { 15, 0x27, 1 },
331 : { 70, 0x5e, 2 },
332 : { 100, 0x6d, 5 },
333 : { 175, 0x7c, 0 },
334 : };
335 0 : auto *entry = &thresholds[0];
336 0 : for (uint8_t i=1; i<ARRAY_SIZE(thresholds); i++) {
337 0 : auto *next_entry = &thresholds[i];
338 0 : if (knots > entry->min && knots < next_entry->min) {
339 0 : const uint8_t code_delta = next_entry->code - entry->code;
340 0 : return entry->code + uint8_t((knots - entry->min) / code_delta);
341 : }
342 0 : entry = next_entry;
343 : }
344 0 : return 0x7c;
345 : }
346 0 : void ADSB_Sagetech_MXS::pack_scaled_groundspeed(uint8_t dest[1], float speed_m_s) const
347 : {
348 0 : dest[0] = scaled_groundspeed(speed_m_s);
349 0 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "speed in: %f", speed_m_s);
350 0 : }
351 :
352 134254 : void ADSB_Sagetech_MXS::pack_scaled_airspeed(uint8_t dest[2], float speed_m_s) const
353 : {
354 134254 : const int16_t scaled = M_PER_SEC_TO_KNOTS * speed_m_s * 8;
355 134254 : dest[0] = scaled >> 8;
356 134254 : dest[1] = scaled >> 0;
357 134254 : }
358 :
359 67127 : void ADSB_Sagetech_MXS::pack_scaled_vertical_rate(uint8_t dest[2], float speed_m_s) const
360 : {
361 67127 : const int16_t scaled = METRES_TO_FEET * speed_m_s;
362 67127 : dest[0] = scaled >> 8;
363 67127 : dest[1] = scaled >> 0;
364 67127 : }
365 :
366 67127 : void ADSB_Sagetech_MXS::send_vehicle_message(const SITL::ADSB_Vehicle &vehicle)
367 : {
368 67127 : send_vehicle_message_state_vector(vehicle);
369 67127 : send_vehicle_message_status_report(vehicle);
370 67127 : }
371 :
372 67127 : void ADSB_Sagetech_MXS::send_vehicle_message_state_vector(const SITL::ADSB_Vehicle &vehicle)
373 : {
374 : enum class SVR_ValidityFlag : uint8_t {
375 : LatitudeAndLongitude = (1U << 7),
376 : Altitude_Geometric = (1U << 6),
377 : NS_and_EW_Velocity = (1U << 5),
378 : GroundSpeedWhileOnSurface = (1U << 4),
379 : HeadingWhileOnSurface = (1U << 3),
380 : Altitude_Barometric = (1U << 2),
381 : VerticalRate_Geometric = (1U << 1),
382 : VerticalRate_Barometric = (1U << 0),
383 : };
384 : enum class SVR_EstimateValidityFlag : uint8_t {
385 : LatitudeAndLongitude = (1U << 7),
386 : NS_and_EW_Velocity = (1U << 6),
387 : };
388 :
389 : // this struct-based approach may prove to be too inflexible
390 : // if we want to change the shape of the report at runtime for
391 : // some reason.
392 : struct PACKED {
393 : StateVectorReport_ReportStructure report_structure;
394 : uint8_t validity_flags; // see SVR_ValidityFlag enum
395 : uint8_t estimated_validity_flags; // see SVR_EstimateValidityFlag enum
396 : uint8_t participant_address[3];
397 : uint8_t address_qualifier;
398 : uint16_t epos_toa;
399 : uint16_t pos_toa;
400 : uint16_t vel_toa;
401 : uint8_t latitude[3];
402 : uint8_t longitude[3];
403 : uint8_t alt_geometric[3];
404 : uint8_t ns_velocity[2];
405 : uint8_t ew_velocity[2];
406 : uint8_t up_velocity[2];
407 : // uint8_t groundspeed;
408 : // uint8_t ground_heading;
409 67127 : } my_report {};
410 :
411 67127 : my_report.report_structure.rs0.msn = 1;
412 :
413 : // note that the Sagetech SDK parser requires all of these fields
414 : // to be 1 to be an airborne vehicle::
415 67127 : my_report.report_structure.rs0.time_of_applicability_for_estimated_positon = 1;
416 67127 : my_report.report_structure.rs0.position_time_of_applicability = 1;
417 67127 : my_report.report_structure.rs0.velocity_time_of_applicability = 1;
418 :
419 67127 : my_report.report_structure.rs2.estimated_longitude = 0;
420 :
421 67127 : my_report.validity_flags = 0;
422 :
423 67127 : pack_int32_into_uint8_ts(vehicle.ICAO_address, my_report.participant_address);
424 67127 : my_report.address_qualifier = 0x02; // aircraft
425 :
426 67127 : Location loc = vehicle.get_location();
427 :
428 : // pack in latitude/longitude:
429 67127 : pack_scaled_geocoord(my_report.latitude, loc.lat * 1e-7);
430 67127 : pack_scaled_geocoord(my_report.longitude, loc.lng * 1e-7);
431 67127 : my_report.report_structure.rs0.lat_and_lng = 1;
432 67127 : my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::LatitudeAndLongitude;
433 :
434 : // pack in altitude:
435 67127 : pack_scaled_alt(my_report.alt_geometric, loc.alt*0.01);
436 67127 : my_report.report_structure.rs1.altitude_geometric = 1;
437 67127 : my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::Altitude_Geometric;
438 :
439 : // pack in north/south and east/west velocity
440 67127 : pack_scaled_airspeed(my_report.ns_velocity, vehicle.velocity_ef.x);
441 67127 : pack_scaled_airspeed(my_report.ew_velocity, vehicle.velocity_ef.y);
442 67127 : my_report.report_structure.rs1.north_south_east_west_vel = 1;
443 67127 : my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::NS_and_EW_Velocity;
444 :
445 : // pack in surface speed and heading:
446 : // pack_scaled_groundspeed(&my_report.groundspeed, vehicle.velocity_ef.length());
447 : // my_report.report_structure.rs1.ground_speed_on_ground = 1;
448 : // my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::GroundSpeedWhileOnSurface;
449 : // my_report.report_structure.rs1.heading_on_ground = 1;
450 : // my_report.ground_heading = wrap_180(vehicle.velocity_ef.xy().angle()) / 1.40625;
451 : // my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::HeadingWhileOnSurface;
452 :
453 : // pack in vertical rate
454 67127 : pack_scaled_vertical_rate(my_report.up_velocity, -vehicle.velocity_ef.z);
455 67127 : my_report.report_structure.rs1.vertical_rate_geometric_barometric = 1;
456 67127 : my_report.validity_flags |= (uint8_t)SVR_ValidityFlag::VerticalRate_Geometric;
457 :
458 : StateVectorReportBuffer<sizeof(my_report)> report;
459 67127 : memcpy(report.buffer, &my_report, sizeof(report.buffer));
460 :
461 : const PackedMessage<StateVectorReportBuffer<sizeof(my_report)>> packed {
462 : report,
463 : MsgType::STATEVECTORREPORT,
464 67127 : msgid++
465 67127 : };
466 67127 : write_to_autopilot((const char*)&packed, sizeof(packed));
467 67127 : }
468 :
469 67127 : void ADSB_Sagetech_MXS::send_vehicle_message_status_report(const SITL::ADSB_Vehicle &vehicle)
470 : {
471 : enum class RS0 : uint8_t {
472 : TimeOfApplicability = (1U << 3),
473 : ADSB_Version = (1U << 2),
474 : CallSign = (1U << 1),
475 : EmitterCategory = (1U << 0),
476 : };
477 : enum class RS1 : uint8_t {
478 : AVLengthAndWidthCode = (1U << 7),
479 : EmergencyPriorityStatus = (1U << 6),
480 : CapabilityCodes = (1U << 5),
481 : OperationalMode = (1U << 4),
482 : SVQualityNACp = (1U << 3),
483 : SVQualityNACv = (1U << 2),
484 : SVQualitySIL = (1U << 1),
485 : SQQualtity_GVA = (1U << 0),
486 : };
487 : enum class RS2 : uint8_t {
488 : SVQualityNICbaro = (1U << 7),
489 : TrueMagneticHeading = (1U << 6),
490 : VerticalRateType = (1U << 5),
491 : FlightModeSpecific = (1U << 4),
492 : Reserved3 = (1U << 3),
493 : Reserved2 = (1U << 2),
494 : Reserved1 = (1U << 1),
495 : Reserved0 = (1U << 0),
496 : };
497 :
498 : // this struct-based approach may prove to be too inflexible
499 : // if we want to change the shape of the report at runtime for
500 : // some reason.
501 : struct PACKED {
502 : uint32_t report_structure : 24;
503 : uint8_t validity_flags; // see SVR_ValidityFlag enum
504 : uint8_t participant_address[3];
505 : uint8_t address_qualifier;
506 : uint8_t callsign[8];
507 67127 : } my_report {};
508 :
509 67127 : my_report.report_structure = uint8_t(RS0::CallSign) | (0x2 << 4);
510 67127 : my_report.validity_flags = 0;
511 :
512 67127 : pack_int32_into_uint8_ts(vehicle.ICAO_address, my_report.participant_address);
513 67127 : my_report.address_qualifier = 0x02; // aircraft
514 :
515 67127 : memset(my_report.callsign, '\0', ARRAY_SIZE(my_report.callsign));
516 67127 : memcpy(my_report.callsign, vehicle.callsign, MIN(ARRAY_SIZE(vehicle.callsign), ARRAY_SIZE(my_report.callsign)));
517 :
518 : ModeStatusReportBuffer<sizeof(my_report)> report;
519 67127 : memcpy(report.buffer, &my_report, sizeof(report.buffer));
520 :
521 : const PackedMessage<ModeStatusReportBuffer<sizeof(my_report)>> packed {
522 : report,
523 : MsgType::MODESTATUSREPORT,
524 67127 : msgid++
525 67127 : };
526 67127 : write_to_autopilot((const char*)&packed, sizeof(packed));
527 67127 : }
528 :
529 13427 : void ADSB_Sagetech_MXS::update_rf_input()
530 : {
531 : // could selectivly copy from sitl->adsb here
532 13427 : }
533 :
534 13427 : void ADSB_Sagetech_MXS::update_rf_output()
535 : {
536 : // print a message here when we are transmitting
537 13427 : }
538 :
539 14460 : void ADSB_Sagetech_MXS::update(const SITL::Aircraft *sitl_model)
540 : {
541 : // see if we should do a report.
542 14460 : if ((AP::sitl()->adsb_types & (1U << (uint8_t)SIM::ADSBType::SageTechMXS)) == 0) {
543 1033 : return;
544 : }
545 :
546 13427 : update_serial_input();
547 13427 : update_serial_output(sitl_model);
548 :
549 13427 : update_rf_input();
550 13427 : update_rf_output();
551 : }
552 :
553 : #endif // AP_SIM_ADSB_SAGETECH_MXS_ENABLED
|