CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_ADSB/AP_ADSB_Sagetech_MXS.cpp
Views: 1798
1
/*
2
* Copyright (C) 2022 Sagetech Avionics Inc. All rights reserved.
3
*
4
* This file is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License as published by the
6
* Free Software Foundation, either version 3 of the License, or
7
* (at your option) any later version.
8
*
9
* This file is distributed in the hope that it will be useful, but
10
* WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
* See the GNU General Public License for more details.
13
*
14
* You should have received a copy of the GNU General Public License along
15
* with this program. If not, see <http://www.gnu.org/licenses/>.
16
*
17
* SDK specification
18
* https://github.com/Sagetech-Avionics/sagetech-mxs-sdk/blob/main/doc/pdf/ICD02373_MXS_Host_ICD.pdf
19
*
20
* Authors: Chuck Faber, Tom Pittenger
21
*/
22
23
24
#include "AP_ADSB_Sagetech_MXS.h"
25
26
#if HAL_ADSB_SAGETECH_MXS_ENABLED
27
#include <GCS_MAVLink/GCS.h>
28
#include <AP_SerialManager/AP_SerialManager.h>
29
#include <stdio.h>
30
#include <time.h>
31
#include <AP_Vehicle/AP_Vehicle_Type.h>
32
33
#define SAGETECH_USE_MXS_SDK !APM_BUILD_TYPE(APM_BUILD_UNKNOWN)
34
35
#define MXS_INIT_TIMEOUT 20000
36
37
#define SAGETECH_SCALE_CM_TO_FEET (0.0328084f)
38
#define SAGETECH_SCALE_FEET_TO_MM (304.8f)
39
#define SAGETECH_SCALE_KNOTS_TO_CM_PER_SEC (51.4444f)
40
#define SAGETECH_SCALE_FT_PER_MIN_TO_CM_PER_SEC (0.508f)
41
#define SAGETECH_SCALE_M_PER_SEC_TO_FT_PER_MIN (196.85f)
42
#define CLIMB_RATE_LIMIT 16448
43
44
#define SAGETECH_INSTALL_MSG_RATE 5000
45
#define SAGETECH_OPERATING_MSG_RATE 1000
46
#define SAGETECH_FLIGHT_ID_MSG_RATE 8200
47
#define SAGETECH_GPS_MSG_RATE_FLYING 200
48
#define SAGETECH_GPS_MSG_RATE_GROUNDED 1000
49
#define SAGETECH_TARGETREQ_MSG_RATE 1000
50
#define SAGETECH_HFOM_UNKNOWN (19000.0f)
51
#define SAGETECH_VFOM_UNKNOWN (151.0f)
52
#define SAGETECH_HPL_UNKNOWN (38000.0f)
53
54
bool AP_ADSB_Sagetech_MXS::detect()
55
{
56
return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_ADSB, 0);
57
}
58
59
60
bool AP_ADSB_Sagetech_MXS::init()
61
{
62
_port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_ADSB, 0);
63
if (_port == nullptr) {
64
return false;
65
}
66
return true;
67
}
68
69
void AP_ADSB_Sagetech_MXS::update()
70
{
71
if (_port == nullptr) {
72
return;
73
}
74
75
// -----------------------------
76
// read any available data on serial port
77
// -----------------------------
78
uint32_t nbytes = MIN(_port->available(), 10 * PAYLOAD_MXS_MAX_SIZE);
79
while (nbytes-- > 0) {
80
uint8_t data;
81
if (!_port->read(data)) {
82
break;
83
}
84
parse_byte(data);
85
}
86
87
const uint32_t now_ms = AP_HAL::millis();
88
89
// -----------------------------
90
// handle timers for generating data
91
// -----------------------------
92
if (!mxs_state.init) {
93
if (!last.packet_initialize_ms || (now_ms - last.packet_initialize_ms >= SAGETECH_INSTALL_MSG_RATE)) {
94
last.packet_initialize_ms = now_ms;
95
96
if (_frontend._options & uint32_t(AP_ADSB::AdsbOption::SagteTech_MXS_External_Config)) {
97
// request the device's configuration
98
send_data_req(dataInstall);
99
100
} else {
101
// auto configure based on autopilot's saved parameters
102
auto_config_operating();
103
auto_config_installation();
104
auto_config_flightid();
105
send_targetreq_msg();
106
_frontend.out_state.cfg.rf_capable.set_and_notify(rf_capable_flags_default); // Set the RF Capability to 1090ES TX and RX
107
mxs_state.init = true;
108
}
109
110
} else if (last.packet_initialize_ms > MXS_INIT_TIMEOUT && !mxs_state.init_failed) {
111
GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "ADSB Sagetech MXS: Initialization Timeout. Failed to initialize.");
112
mxs_state.init_failed = true;
113
}
114
115
// before init is done, don't run any other update() tasks
116
return;
117
}
118
119
if ((now_ms - last.packet_initialize_ms >= SAGETECH_INSTALL_MSG_RATE) &&
120
(mxs_state.inst.icao != (uint32_t)_frontend.out_state.cfg.ICAO_id_param.get() ||
121
mxs_state.inst.emitter != convert_emitter_type_to_sg(_frontend.out_state.cfg.emitterType.get()) ||
122
mxs_state.inst.size != _frontend.out_state.cfg.lengthWidth.get() ||
123
mxs_state.inst.maxSpeed != (sg_airspeed_t)AP_ADSB::convert_maxknots_to_enum(_frontend.out_state.cfg.maxAircraftSpeed_knots)
124
)) {
125
last.packet_initialize_ms = now_ms;
126
send_install_msg();
127
128
} else if (!last.packet_PreFlight_ms || (now_ms - last.packet_PreFlight_ms >= SAGETECH_FLIGHT_ID_MSG_RATE)) {
129
last.packet_PreFlight_ms = now_ms;
130
send_flight_id_msg();
131
132
} else if (!last.packet_Operating_ms || // Send once at boot
133
now_ms - last.packet_Operating_ms >= SAGETECH_OPERATING_MSG_RATE || // Send Operating Message every second
134
last.operating_squawk != _frontend.out_state.ctrl.squawkCode || // Or anytime Operating data changes
135
last.operating_squawk != _frontend.out_state.cfg.squawk_octal ||
136
abs(last.operating_alt - _frontend._my_loc.alt) > 1555 || // 1493cm == 49ft. The output resolution is 100ft per bit
137
last.operating_rf_select != _frontend.out_state.cfg.rfSelect || // The following booleans control the MXS OpMode
138
last.modeAEnabled != _frontend.out_state.ctrl.modeAEnabled ||
139
last.modeCEnabled != _frontend.out_state.ctrl.modeCEnabled ||
140
last.modeSEnabled != _frontend.out_state.ctrl.modeSEnabled
141
) {
142
143
if (last.operating_squawk != _frontend.out_state.ctrl.squawkCode) {
144
last.operating_squawk = _frontend.out_state.ctrl.squawkCode;
145
_frontend.out_state.cfg.squawk_octal_param.set_and_notify(last.operating_squawk);
146
} else if (last.operating_squawk != _frontend.out_state.cfg.squawk_octal) {
147
last.operating_squawk = _frontend.out_state.cfg.squawk_octal;
148
_frontend.out_state.ctrl.squawkCode = last.operating_squawk;
149
}
150
last.operating_rf_select = _frontend.out_state.cfg.rfSelect;
151
last.modeAEnabled = _frontend.out_state.ctrl.modeAEnabled;
152
last.modeCEnabled = _frontend.out_state.ctrl.modeCEnabled;
153
last.modeSEnabled = (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Mode3_Only)) ? 0 : _frontend.out_state.ctrl.modeSEnabled;
154
155
last.operating_alt = _frontend._my_loc.alt;
156
last.packet_Operating_ms = now_ms;
157
send_operating_msg();
158
159
} else if (now_ms - last.packet_GPS_ms >= (_frontend.out_state.is_flying ? SAGETECH_GPS_MSG_RATE_FLYING : SAGETECH_GPS_MSG_RATE_GROUNDED)) {
160
last.packet_GPS_ms = now_ms;
161
send_gps_msg();
162
163
} else if ((now_ms - last.packet_targetReq >= SAGETECH_TARGETREQ_MSG_RATE) &&
164
((mxs_state.treq.icao != (uint32_t)_frontend._special_ICAO_target) || (mxs_state.treq.maxTargets != (uint16_t)_frontend.in_state.list_size_param))) {
165
send_targetreq_msg();
166
}
167
}
168
169
void AP_ADSB_Sagetech_MXS::handle_packet(const Packet &msg)
170
{
171
#if SAGETECH_USE_MXS_SDK
172
switch (msg.type) {
173
case MsgType::ACK:
174
if(sgDecodeAck((uint8_t*) &msg, &mxs_state.ack)) {
175
handle_ack(mxs_state.ack);
176
}
177
break;
178
179
case MsgType::Installation_Response:
180
if (!mxs_state.init && sgDecodeInstall((uint8_t*)&msg, &mxs_state.inst)) {
181
// If not doing auto-config, get the current configuration of the MXS
182
// Fill out configuration parameters with preconfigured values
183
if (mxs_state.ack.opMode == modeOff) { // If the last ACK showed an OFF state, turn off all rfSelect bits.
184
_frontend.out_state.cfg.rfSelect.set_and_notify(0);
185
} else if (mxs_state.ack.opMode == modeStby) {
186
_frontend.out_state.ctrl.modeAEnabled = false;
187
_frontend.out_state.ctrl.modeCEnabled = false;
188
_frontend.out_state.ctrl.modeSEnabled = false;
189
_frontend.out_state.ctrl.es1090TxEnabled = false;
190
} else if (mxs_state.ack.opMode == modeOn) {
191
_frontend.out_state.ctrl.modeAEnabled = true;
192
_frontend.out_state.ctrl.modeCEnabled = false;
193
_frontend.out_state.ctrl.modeSEnabled = true;
194
_frontend.out_state.ctrl.es1090TxEnabled = true;
195
} else if (mxs_state.ack.opMode == modeAlt) {
196
_frontend.out_state.ctrl.modeAEnabled = true;
197
_frontend.out_state.ctrl.modeCEnabled = true;
198
_frontend.out_state.ctrl.modeSEnabled = true;
199
_frontend.out_state.ctrl.es1090TxEnabled = true;
200
}
201
_frontend.out_state.cfg.ICAO_id_param.set_and_notify(mxs_state.inst.icao);
202
_frontend.out_state.cfg.lengthWidth.set_and_notify(mxs_state.inst.size);
203
_frontend.out_state.cfg.emitterType.set_and_notify(convert_sg_emitter_type_to_adsb(mxs_state.inst.emitter));
204
205
_frontend.out_state.cfg.rf_capable.set_and_notify(rf_capable_flags_default); // Set the RF Capability to UAT and 1090ES TX and RX
206
207
mxs_state.init = true;
208
}
209
break;
210
211
case MsgType::FlightID_Response: {
212
sg_flightid_t flightId {};
213
if (sgDecodeFlightId((uint8_t*) &msg, &flightId)) {
214
_frontend.set_callsign(flightId.flightId, false);
215
}
216
break;
217
}
218
219
// ADSB Messages
220
case MsgType::ADSB_StateVector_Report: {
221
sg_svr_t svr {};
222
if (sgDecodeSVR((uint8_t*) &msg, &svr)) {
223
handle_svr(svr);
224
}
225
break;
226
}
227
228
case MsgType::ADSB_ModeStatus_Report: {
229
sg_msr_t msr {};
230
if (sgDecodeMSR((uint8_t*) &msg, &msr)) {
231
handle_msr(msr);
232
}
233
break;
234
}
235
236
case MsgType::Data_Request:
237
case MsgType::Target_Request:
238
case MsgType::Mode:
239
case MsgType::Installation:
240
case MsgType::FlightID:
241
case MsgType::Operating:
242
case MsgType::GPS_Data:
243
case MsgType::Status_Response:
244
case MsgType::Version_Response:
245
case MsgType::Serial_Number_Response:
246
case MsgType::Mode_Settings:
247
case MsgType::Target_Summary_Report:
248
case MsgType::RESERVED_0x84:
249
case MsgType::RESERVED_0x85:
250
case MsgType::RESERVED_0x8D:
251
case MsgType::ADSB_Target_State_Report:
252
case MsgType::ADSB_Air_Ref_Vel_Report:
253
// unhandled or intended for out-bound only
254
break;
255
}
256
#endif // SAGETECH_USE_MXS_SDK
257
}
258
259
bool AP_ADSB_Sagetech_MXS::parse_byte(const uint8_t data)
260
{
261
switch (message_in.state) {
262
default:
263
case ParseState::WaitingFor_Start:
264
if (data == START_BYTE) {
265
message_in.checksum = data; // initialize checksum here
266
message_in.state = ParseState::WaitingFor_MsgType;
267
}
268
break;
269
case ParseState::WaitingFor_MsgType:
270
message_in.checksum += data;
271
message_in.packet.type = static_cast<MsgType>(data);
272
message_in.state = ParseState::WaitingFor_MsgId;
273
break;
274
case ParseState::WaitingFor_MsgId:
275
message_in.checksum += data;
276
message_in.packet.id = data;
277
message_in.state = ParseState::WaitingFor_PayloadLen;
278
break;
279
case ParseState::WaitingFor_PayloadLen:
280
message_in.checksum += data;
281
message_in.packet.payload_length = data;
282
message_in.index = 0;
283
message_in.state = (data == 0) ? ParseState::WaitingFor_Checksum : ParseState::WaitingFor_PayloadContents;
284
break;
285
case ParseState::WaitingFor_PayloadContents:
286
message_in.checksum += data;
287
message_in.packet.payload[message_in.index++] = data;
288
if (message_in.index >= message_in.packet.payload_length) {
289
message_in.state = ParseState::WaitingFor_Checksum;
290
}
291
break;
292
case ParseState::WaitingFor_Checksum:
293
message_in.state = ParseState::WaitingFor_Start;
294
if (message_in.checksum == data) {
295
// append the checksum to the payload and zero out the payload index
296
message_in.packet.payload[message_in.index] = data;
297
message_in.index = 0;
298
handle_packet(message_in.packet);
299
}
300
break;
301
}
302
return false;
303
}
304
305
void AP_ADSB_Sagetech_MXS::msg_write(const uint8_t *data, const uint16_t len) const
306
{
307
if (_port != nullptr) {
308
_port->write(data, len);
309
}
310
}
311
312
void AP_ADSB_Sagetech_MXS::auto_config_operating()
313
{
314
// Configure the Default Operation Message Data
315
mxs_state.op.squawk = AP_ADSB::convert_base_to_decimal(8, _frontend.out_state.cfg.squawk_octal);
316
mxs_state.op.opMode = sg_op_mode_t::modeOff; // MXS needs to start in OFF mode to accept installation message
317
mxs_state.op.savePowerUp = true; // Save power-up state in non-volatile
318
mxs_state.op.enableSqt = true; // Enable extended squitters
319
mxs_state.op.enableXBit = false; // Enable the x-bit
320
mxs_state.op.milEmergency = false; // Broadcast a military emergency
321
mxs_state.op.emergcType = (sg_emergc_t)_frontend.out_state.ctrl.emergencyState; // Enumerated civilian emergency type
322
323
mxs_state.op.altUseIntrnl = true; // True = Report altitude from internal pressure sensor (will ignore other bits in the field)
324
mxs_state.op.altHostAvlbl = false;
325
mxs_state.op.altRes25 = !mxs_state.inst.altRes100; // Host Altitude Resolution from install
326
327
mxs_state.op.identOn = false;
328
329
const auto &my_loc = _frontend._my_loc;
330
331
populate_op_altitude(my_loc);
332
populate_op_climbrate(my_loc);
333
populate_op_airspeed_and_heading(my_loc);
334
335
last.msg.type = SG_MSG_TYPE_HOST_OPMSG;
336
337
#if SAGETECH_USE_MXS_SDK
338
uint8_t txComBuffer[SG_MSG_LEN_OPMSG] {};
339
sgEncodeOperating(txComBuffer, &mxs_state.op, ++last.msg.id);
340
msg_write(txComBuffer, SG_MSG_LEN_OPMSG);
341
#endif
342
}
343
344
void AP_ADSB_Sagetech_MXS::auto_config_installation()
345
{
346
// Configure the Default Installation Message Data
347
mxs_state.inst.icao = (uint32_t) _frontend.out_state.cfg.ICAO_id_param;
348
snprintf(mxs_state.inst.reg, 8, "%-7s", "TEST01Z");
349
350
mxs_state.inst.com0 = sg_baud_t::baud230400;
351
mxs_state.inst.com1 = sg_baud_t::baud57600;
352
353
mxs_state.inst.eth.ipAddress = 0;
354
mxs_state.inst.eth.subnetMask = 0;
355
mxs_state.inst.eth.portNumber = 0;
356
357
mxs_state.inst.sil = sg_sil_t::silUnknown;
358
mxs_state.inst.sda = sg_sda_t::sdaUnknown;
359
mxs_state.inst.emitter = convert_emitter_type_to_sg(_frontend.out_state.cfg.emitterType.get());
360
mxs_state.inst.size = (sg_size_t)_frontend.out_state.cfg.lengthWidth.get();
361
mxs_state.inst.maxSpeed = (sg_airspeed_t)AP_ADSB::convert_maxknots_to_enum(_frontend.out_state.cfg.maxAircraftSpeed_knots);
362
mxs_state.inst.altOffset = 0; // Alt encoder offset is legacy field that should always be 0.
363
mxs_state.inst.antenna = sg_antenna_t::antBottom;
364
365
mxs_state.inst.altRes100 = true;
366
mxs_state.inst.hdgTrueNorth = false;
367
mxs_state.inst.airspeedTrue = false;
368
mxs_state.inst.heater = true; // Heater should always be connected.
369
mxs_state.inst.wowConnected = true;
370
371
last.msg.type = SG_MSG_TYPE_HOST_INSTALL;
372
373
#if SAGETECH_USE_MXS_SDK
374
uint8_t txComBuffer[SG_MSG_LEN_INSTALL] {};
375
sgEncodeInstall(txComBuffer, &mxs_state.inst, ++last.msg.id);
376
msg_write(txComBuffer, SG_MSG_LEN_INSTALL);
377
#endif
378
}
379
380
void AP_ADSB_Sagetech_MXS::auto_config_flightid()
381
{
382
if (!strlen(_frontend.out_state.cfg.callsign)) {
383
snprintf(mxs_state.fid.flightId, sizeof(mxs_state.fid.flightId), "%-8s", "TESTMXS0");
384
} else {
385
snprintf(mxs_state.fid.flightId, sizeof(mxs_state.fid.flightId), "%-8s", _frontend.out_state.cfg.callsign);
386
}
387
last.msg.type = SG_MSG_TYPE_HOST_FLIGHT;
388
389
#if SAGETECH_USE_MXS_SDK
390
uint8_t txComBuffer[SG_MSG_LEN_FLIGHT] {};
391
sgEncodeFlightId(txComBuffer, &mxs_state.fid, ++last.msg.id);
392
msg_write(txComBuffer, SG_MSG_LEN_FLIGHT);
393
#endif
394
}
395
396
void AP_ADSB_Sagetech_MXS::handle_ack(const sg_ack_t ack)
397
{
398
if ((ack.ackId != last.msg.id) || (ack.ackType != last.msg.type)) {
399
// GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ADSB Sagetech MXS: ACK: Message %d of type %02x not acknowledged.", last.msg.id, last.msg.type);
400
}
401
// System health
402
if (ack.failXpdr && !last.failXpdr) {
403
GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ADSB Sagetech MXS: Transponder Failure");
404
_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;
405
}
406
if (ack.failSystem && !last.failSystem) {
407
GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ADSB Sagetech MXS: System Failure");
408
_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;
409
}
410
last.failXpdr = ack.failXpdr;
411
last.failSystem = ack.failSystem;
412
}
413
414
void AP_ADSB_Sagetech_MXS::handle_svr(const sg_svr_t svr)
415
{
416
if (svr.addrType != svrAdrIcaoUnknown && svr.addrType != svrAdrIcao && svr.addrType != svrAdrIcaoSurface) {
417
// invalid icao
418
return;
419
}
420
421
AP_ADSB::adsb_vehicle_t vehicle;
422
if (!_frontend.get_vehicle_by_ICAO(svr.addr, vehicle)) {
423
// new vehicle
424
memset(&vehicle, 0, sizeof(vehicle));
425
vehicle.info.ICAO_address = svr.addr;
426
}
427
428
vehicle.info.flags &= ~ADSB_FLAGS_VALID_SQUAWK;
429
430
if (svr.validity.position) {
431
vehicle.info.lat = (int32_t) (svr.lat * 1e7);
432
vehicle.info.lon = (int32_t) (svr.lon * 1e7);
433
vehicle.info.flags |= ADSB_FLAGS_VALID_COORDS;
434
}
435
if (svr.validity.geoAlt) {
436
vehicle.info.altitude_type = ADSB_ALTITUDE_TYPE_GEOMETRIC;
437
vehicle.info.altitude = svr.airborne.geoAlt * SAGETECH_SCALE_FEET_TO_MM; // Convert from feet to mm
438
vehicle.info.flags |= ADSB_FLAGS_VALID_ALTITUDE;
439
}
440
if (svr.validity.baroAlt) {
441
vehicle.info.altitude_type = ADSB_ALTITUDE_TYPE_PRESSURE_QNH;
442
vehicle.info.altitude = svr.airborne.baroAlt * SAGETECH_SCALE_FEET_TO_MM; // Convert from feet to mm
443
vehicle.info.flags |= ADSB_FLAGS_VALID_ALTITUDE;
444
}
445
if (svr.validity.surfSpeed) {
446
vehicle.info.hor_velocity = svr.surface.speed * SAGETECH_SCALE_KNOTS_TO_CM_PER_SEC; // Convert from knots to cm/s
447
vehicle.info.flags |= ADSB_FLAGS_VALID_VELOCITY;
448
}
449
if (svr.validity.surfHeading) {
450
vehicle.info.heading = svr.surface.heading * 100;
451
vehicle.info.flags |= ADSB_FLAGS_VALID_HEADING;
452
}
453
if (svr.validity.airSpeed) {
454
vehicle.info.hor_velocity = svr.airborne.speed * SAGETECH_SCALE_KNOTS_TO_CM_PER_SEC; // Convert from knots to cm/s
455
vehicle.info.heading = svr.airborne.heading * 100;
456
vehicle.info.flags |= ADSB_FLAGS_VALID_VELOCITY;
457
vehicle.info.flags |= ADSB_FLAGS_VALID_HEADING;
458
}
459
if (svr.validity.geoVRate || svr.validity.baroVRate) {
460
vehicle.info.ver_velocity = svr.airborne.vrate * SAGETECH_SCALE_FT_PER_MIN_TO_CM_PER_SEC; // Convert from ft/min to cm/s
461
vehicle.info.flags |= ADSB_FLAGS_VERTICAL_VELOCITY_VALID;
462
}
463
464
vehicle.last_update_ms = AP_HAL::millis();
465
_frontend.handle_adsb_vehicle(vehicle);
466
}
467
468
void AP_ADSB_Sagetech_MXS::handle_msr(const sg_msr_t msr)
469
{
470
AP_ADSB::adsb_vehicle_t vehicle;
471
if (!_frontend.get_vehicle_by_ICAO(msr.addr, vehicle)) {
472
// new vehicle is not allowed here because we don't know the lat/lng
473
// yet and we don't allow lat/lng of (0,0) so it will get rejected anyway
474
return;
475
}
476
477
if (strlen(msr.callsign)) {
478
snprintf(vehicle.info.callsign, sizeof(vehicle.info.callsign), "%-8s", msr.callsign);
479
vehicle.info.flags |= ADSB_FLAGS_VALID_CALLSIGN;
480
} else {
481
vehicle.info.flags &= ~ADSB_FLAGS_VALID_CALLSIGN;
482
}
483
484
vehicle.last_update_ms = AP_HAL::millis();
485
_frontend.handle_adsb_vehicle(vehicle);
486
}
487
488
void AP_ADSB_Sagetech_MXS::send_data_req(const sg_datatype_t dataReqType)
489
{
490
sg_datareq_t dataReq {};
491
dataReq.reqType = dataReqType;
492
last.msg.type = SG_MSG_TYPE_HOST_DATAREQ;
493
494
#if SAGETECH_USE_MXS_SDK
495
uint8_t txComBuffer[SG_MSG_LEN_DATAREQ] {};
496
sgEncodeDataReq(txComBuffer, &dataReq, ++last.msg.id);
497
msg_write(txComBuffer, SG_MSG_LEN_DATAREQ);
498
#else
499
(void)dataReq;
500
#endif
501
}
502
503
void AP_ADSB_Sagetech_MXS::send_install_msg()
504
{
505
// MXS must be in OFF mode to change ICAO or Registration
506
if (mxs_state.op.opMode != modeOff) {
507
// GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "ADSB Sagetech MXS: unable to send installation data while not in OFF mode.");
508
return;
509
}
510
511
mxs_state.inst.icao = (uint32_t)_frontend.out_state.cfg.ICAO_id_param.get();
512
mxs_state.inst.emitter = convert_emitter_type_to_sg(_frontend.out_state.cfg.emitterType.get());
513
mxs_state.inst.size = (sg_size_t)_frontend.out_state.cfg.lengthWidth.get();
514
mxs_state.inst.maxSpeed = (sg_airspeed_t)AP_ADSB::convert_maxknots_to_enum(_frontend.out_state.cfg.maxAircraftSpeed_knots);
515
mxs_state.inst.antenna = sg_antenna_t::antBottom;
516
517
last.msg.type = SG_MSG_TYPE_HOST_INSTALL;
518
519
#if SAGETECH_USE_MXS_SDK
520
uint8_t txComBuffer[SG_MSG_LEN_INSTALL] {};
521
sgEncodeInstall(txComBuffer, &mxs_state.inst, ++last.msg.id);
522
msg_write(txComBuffer, SG_MSG_LEN_INSTALL);
523
#endif
524
}
525
526
void AP_ADSB_Sagetech_MXS::send_flight_id_msg()
527
{
528
if (!strlen((char*) _frontend.out_state.ctrl.callsign)) {
529
return;
530
}
531
snprintf(mxs_state.fid.flightId, sizeof(mxs_state.fid.flightId), "%-8s", (char*) _frontend.out_state.ctrl.callsign);
532
533
last.msg.type = SG_MSG_TYPE_HOST_FLIGHT;
534
535
#if SAGETECH_USE_MXS_SDK
536
uint8_t txComBuffer[SG_MSG_LEN_FLIGHT] {};
537
sgEncodeFlightId(txComBuffer, &mxs_state.fid, ++last.msg.id);
538
msg_write(txComBuffer, SG_MSG_LEN_FLIGHT);
539
#endif
540
}
541
542
void AP_ADSB_Sagetech_MXS::send_operating_msg()
543
{
544
if (!_frontend.out_state.ctrl.modeAEnabled && !_frontend.out_state.ctrl.modeCEnabled &&
545
!_frontend.out_state.ctrl.modeSEnabled && !_frontend.out_state.ctrl.es1090TxEnabled) {
546
mxs_state.op.opMode = modeStby;
547
}
548
if (_frontend.out_state.ctrl.modeAEnabled && !_frontend.out_state.ctrl.modeCEnabled &&
549
_frontend.out_state.ctrl.modeSEnabled && _frontend.out_state.ctrl.es1090TxEnabled) {
550
mxs_state.op.opMode = modeOn;
551
}
552
if (_frontend.out_state.ctrl.modeAEnabled && _frontend.out_state.ctrl.modeCEnabled &&
553
_frontend.out_state.ctrl.modeSEnabled && _frontend.out_state.ctrl.es1090TxEnabled) {
554
mxs_state.op.opMode = modeAlt;
555
}
556
if ((_frontend.out_state.cfg.rfSelect & 1) == 0) {
557
mxs_state.op.opMode = modeOff;
558
}
559
560
mxs_state.op.squawk = AP_ADSB::convert_base_to_decimal(8, last.operating_squawk);
561
mxs_state.op.emergcType = (sg_emergc_t) _frontend.out_state.ctrl.emergencyState;
562
563
const auto &my_loc = _frontend._my_loc;
564
565
populate_op_altitude(my_loc);
566
populate_op_climbrate(my_loc);
567
populate_op_airspeed_and_heading(my_loc);
568
569
mxs_state.op.identOn = _frontend.out_state.ctrl.identActive;
570
_frontend.out_state.ctrl.identActive = false; // only send identButtonActive once per request
571
572
last.msg.type = SG_MSG_TYPE_HOST_OPMSG;
573
574
#if SAGETECH_USE_MXS_SDK
575
uint8_t txComBuffer[SG_MSG_LEN_OPMSG] {};
576
sgEncodeOperating(txComBuffer, &mxs_state.op, ++last.msg.id);
577
msg_write(txComBuffer, SG_MSG_LEN_OPMSG);
578
#endif
579
}
580
581
void AP_ADSB_Sagetech_MXS::send_gps_msg()
582
{
583
sg_gps_t gps {};
584
const AP_ADSB::Loc &ap_gps { _frontend._my_loc };
585
float hAcc, vAcc, velAcc;
586
587
gps.hpl = SAGETECH_HPL_UNKNOWN; // HPL over 37,040m means unknown
588
gps.hfom = ap_gps.horizontal_accuracy(hAcc) ? hAcc : SAGETECH_HFOM_UNKNOWN; // HFOM over 18,520 specifies unknown
589
gps.vfom = ap_gps.vertical_accuracy(vAcc) ? vAcc : SAGETECH_VFOM_UNKNOWN; // VFOM over 150 specifies unknown
590
gps.nacv = sg_nacv_t::nacvUnknown;
591
if (ap_gps.speed_accuracy(velAcc)) {
592
if (velAcc >= 10.0 || velAcc < 0) {
593
gps.nacv = sg_nacv_t::nacvUnknown;
594
}
595
else if (velAcc >= 3.0) {
596
gps.nacv = sg_nacv_t::nacv10dot0;
597
}
598
else if (velAcc >= 1.0) {
599
gps.nacv = sg_nacv_t::nacv3dot0;
600
}
601
else if (velAcc >= 0.3) {
602
gps.nacv = sg_nacv_t::nacv1dot0;
603
}
604
else { //if (velAcc >= 0.0)
605
gps.nacv = sg_nacv_t::nacv0dot3;
606
}
607
}
608
609
// Get Vehicle Longitude and Latitude and Convert to string
610
const int32_t longitude = _frontend._my_loc.lng;
611
const int32_t latitude = _frontend._my_loc.lat;
612
const double lon_deg = longitude * (double)1.0e-7 * (longitude < 0 ? -1 : 1);
613
const double lon_minutes = (lon_deg - int(lon_deg)) * 60;
614
snprintf((char*)&gps.longitude, 12, "%03u%02u.%05u", (unsigned)lon_deg, (unsigned)lon_minutes, unsigned((lon_minutes - (int)lon_minutes) * 1.0E5));
615
616
const double lat_deg = latitude * (double)1.0e-7 * (latitude < 0 ? -1 : 1);
617
const double lat_minutes = (lat_deg - int(lat_deg)) * 60;
618
snprintf((char*)&gps.latitude, 11, "%02u%02u.%05u", (unsigned)lat_deg, (unsigned)lat_minutes, unsigned((lat_minutes - (int)lat_minutes) * 1.0E5));
619
620
const Vector2f speed = _frontend._my_loc.groundspeed_vector();
621
const float speed_knots = speed.length() * M_PER_SEC_TO_KNOTS;
622
snprintf((char*)&gps.grdSpeed, 7, "%03u.%02u", (unsigned)speed_knots, unsigned((speed_knots - (int)speed_knots) * 1.0E2));
623
624
if (!is_zero(speed_knots)) {
625
cog = wrap_360(degrees(speed.angle()));
626
}
627
snprintf((char*)&gps.grdTrack, 9, "%03u.%04u", unsigned(cog), unsigned((cog - (int)cog) * 1.0E4));
628
629
630
gps.latNorth = (latitude >= 0 ? true: false);
631
gps.lngEast = (longitude >= 0 ? true: false);
632
633
gps.gpsValid = ap_gps.status() >= AP_GPS_FixType::FIX_2D;
634
635
uint64_t time_usec = ap_gps.epoch_from_rtc_us;
636
if (ap_gps.have_epoch_from_rtc_us) {
637
const time_t time_sec = time_usec * 1E-6;
638
struct tm tmd {};
639
struct tm* tm = gmtime_r(&time_sec, &tmd);
640
641
snprintf((char*)&gps.timeOfFix, 11, "%02u%02u%06.3f", tm->tm_hour, tm->tm_min, tm->tm_sec + (time_usec % 1000000) * 1.0e-6);
642
} else {
643
strncpy(gps.timeOfFix, " . ", 11);
644
}
645
646
int32_t height;
647
if (_frontend._my_loc.initialised() && _frontend._my_loc.get_alt_cm(Location::AltFrame::ABSOLUTE, height)) {
648
gps.height = height * 0.01;
649
} else {
650
gps.height = 0.0;
651
}
652
653
last.msg.type = SG_MSG_TYPE_HOST_GPS;
654
655
#if SAGETECH_USE_MXS_SDK
656
uint8_t txComBuffer[SG_MSG_LEN_GPS] {};
657
sgEncodeGPS(txComBuffer, &gps, ++last.msg.id);
658
msg_write(txComBuffer, SG_MSG_LEN_GPS);
659
#else
660
(void)gps;
661
#endif
662
}
663
664
void AP_ADSB_Sagetech_MXS::send_targetreq_msg()
665
{
666
mxs_state.treq.reqType = sg_reporttype_t::reportAuto;
667
mxs_state.treq.transmitPort = sg_transmitport_t::transmitCom1;
668
mxs_state.treq.maxTargets = _frontend.in_state.list_size_param;
669
mxs_state.treq.icao = _frontend._special_ICAO_target.get();
670
mxs_state.treq.stateVector = true;
671
mxs_state.treq.modeStatus = true;
672
mxs_state.treq.targetState = false;
673
mxs_state.treq.airRefVel = false;
674
mxs_state.treq.tisb = false;
675
mxs_state.treq.military = false;
676
mxs_state.treq.commA = false;
677
mxs_state.treq.ownship = true;
678
679
last.msg.type = SG_MSG_TYPE_HOST_TARGETREQ;
680
681
#if SAGETECH_USE_MXS_SDK
682
uint8_t txComBuffer[SG_MSG_LEN_TARGETREQ] {};
683
sgEncodeTargetReq(txComBuffer, &mxs_state.treq, ++last.msg.id);
684
msg_write(txComBuffer, SG_MSG_LEN_TARGETREQ);
685
#endif
686
}
687
688
sg_emitter_t AP_ADSB_Sagetech_MXS::convert_emitter_type_to_sg(const uint8_t emitterType) const
689
{
690
uint8_t result = SG_EMIT_OFFSET_D;
691
692
if (emitterType < 8) {
693
result = emitterType;
694
} else if (emitterType < 13 && emitterType >= 8) {
695
result = (emitterType - 8) + SG_EMIT_OFFSET_B;
696
} else if (emitterType < 16 && emitterType >= 14) {
697
result = (emitterType - 14) + (SG_EMIT_OFFSET_B + 6); // Subtracting 14 because SG emitter types don't include the reserved state at value 13.
698
} else if (emitterType < 21 && emitterType >= 16) {
699
result = (emitterType - 16) + SG_EMIT_OFFSET_C;
700
}
701
return (sg_emitter_t)result;
702
}
703
704
uint8_t AP_ADSB_Sagetech_MXS::convert_sg_emitter_type_to_adsb(const sg_emitter_t sgEmitterType) const
705
{
706
if (sgEmitterType < SG_EMIT_OFFSET_B) {
707
return sgEmitterType;
708
} else if ((sgEmitterType < (SG_EMIT_OFFSET_B + 6)) && (sgEmitterType >= SG_EMIT_OFFSET_B)) {
709
return (sgEmitterType - SG_EMIT_OFFSET_B) + 8;
710
} else if ((sgEmitterType < SG_EMIT_OFFSET_C) && (sgEmitterType >= SG_EMIT_OFFSET_B + 6)) {
711
return (sgEmitterType - (SG_EMIT_OFFSET_B + 6)) + 14; // Starts at UAV = 14
712
} else if ((sgEmitterType < SG_EMIT_OFFSET_D) && (sgEmitterType >= SG_EMIT_OFFSET_C)) {
713
return (sgEmitterType - SG_EMIT_OFFSET_C) + 16;
714
} else {
715
return 0;
716
}
717
}
718
719
sg_airspeed_t AP_ADSB_Sagetech_MXS::convert_airspeed_knots_to_sg(const float maxAirSpeed) const
720
{
721
const int32_t airspeed = (int) maxAirSpeed;
722
723
if (airspeed < 0) {
724
return sg_airspeed_t::speedUnknown;
725
} else if (airspeed < 75) {
726
return sg_airspeed_t::speed75kt;
727
} else if (airspeed < 150) {
728
return sg_airspeed_t::speed150kt;
729
} else if (airspeed < 300) {
730
return sg_airspeed_t::speed300kt;
731
} else if (airspeed < 600) {
732
return sg_airspeed_t::speed600kt;
733
} else if (airspeed < 1200) {
734
return sg_airspeed_t::speed1200kt;
735
} else { //if (airspeed >= 1200)
736
return sg_airspeed_t::speedGreater;
737
}
738
}
739
740
void AP_ADSB_Sagetech_MXS::populate_op_altitude(const AP_ADSB::Loc &loc)
741
{
742
int32_t height;
743
if (loc.initialised() && loc.get_alt_cm(Location::AltFrame::ABSOLUTE, height)) {
744
mxs_state.op.altitude = height * SAGETECH_SCALE_CM_TO_FEET; // Height above sealevel in feet
745
} else {
746
mxs_state.op.altitude = 0;
747
}
748
}
749
750
void AP_ADSB_Sagetech_MXS::populate_op_climbrate(const AP_ADSB::Loc &my_loc)
751
{
752
float vertRateD;
753
if (my_loc.get_vert_pos_rate_D(vertRateD)) {
754
// convert from down to up, and scale appropriately:
755
mxs_state.op.climbRate = -1 * vertRateD * SAGETECH_SCALE_M_PER_SEC_TO_FT_PER_MIN;
756
mxs_state.op.climbValid = true;
757
} else {
758
mxs_state.op.climbValid = false;
759
mxs_state.op.climbRate = -CLIMB_RATE_LIMIT;
760
}
761
}
762
763
void AP_ADSB_Sagetech_MXS::populate_op_airspeed_and_heading(const AP_ADSB::Loc &my_loc)
764
{
765
const Vector2f speed = my_loc.groundspeed_vector();
766
if (!speed.is_nan() && !speed.is_zero()) {
767
mxs_state.op.headingValid = true;
768
mxs_state.op.airspdValid = true;
769
} else {
770
mxs_state.op.headingValid = false;
771
mxs_state.op.airspdValid = false;
772
}
773
const uint16_t speed_knots = speed.length() * M_PER_SEC_TO_KNOTS;
774
double heading = wrap_360(degrees(speed.angle()));
775
mxs_state.op.airspd = speed_knots;
776
mxs_state.op.heading = heading;
777
}
778
779
#endif // HAL_ADSB_SAGETECH_MXS_ENABLED
780
781
782