LCOV - code coverage report
Current view: top level - AP_RCTelemetry - AP_Spektrum_Telem.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 2 347 0.6 %
Date: 2026-09-09 11:02:08 Functions: 1 26 3.8 %

          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             :    Spektrum Telemetry library, based on AP_Frsky_Telem.cpp
      18             :    See https://www.spektrumrc.com/ProdInfo/Files/SPM_Telemetry_Developers_Specs.pdf
      19             : */
      20             : 
      21             : #include "AP_Spektrum_Telem.h"
      22             : #include <AP_HAL/utility/sparse-endian.h>
      23             : #include <AP_AHRS/AP_AHRS.h>
      24             : #include <AP_RPM/AP_RPM.h>
      25             : #include <AP_Airspeed/AP_Airspeed.h>
      26             : #include <AP_Compass/AP_Compass.h>
      27             : #include <AP_BattMonitor/AP_BattMonitor.h>
      28             : #include <AP_RangeFinder/AP_RangeFinder.h>
      29             : #include <AP_Common/AP_FWVersion.h>
      30             : #include <GCS_MAVLink/GCS.h>
      31             : #include <AP_Common/Location.h>
      32             : #include <AP_GPS/AP_GPS.h>
      33             : #include <AP_Baro/AP_Baro.h>
      34             : #include <AP_RTC/AP_RTC.h>
      35             : #include <AP_SerialManager/AP_SerialManager.h>
      36             : #include <AP_ESC_Telem/AP_ESC_Telem.h>
      37             : #include <math.h>
      38             : 
      39             : #if HAL_SPEKTRUM_TELEM_ENABLED
      40             : 
      41             : #define MICROSEC_PER_MINUTE 60000000
      42             : #define MAX_TEXTGEN_LEN     13
      43             : 
      44             : //#define SPKT_DEBUG
      45             : #ifdef SPKT_DEBUG
      46             : # define debug(fmt, args...)    hal.console->printf("SPKT:" fmt "\n", ##args)
      47             : #else
      48             : # define debug(fmt, args...)    do {} while(0)
      49             : #endif
      50             : 
      51             : extern const AP_HAL::HAL& hal;
      52             : 
      53             : AP_Spektrum_Telem *AP_Spektrum_Telem::singleton;
      54             : 
      55           0 : AP_Spektrum_Telem::AP_Spektrum_Telem() : AP_RCTelemetry(0)
      56             : {
      57           0 :     singleton = this;
      58           0 : }
      59             : 
      60           0 : AP_Spektrum_Telem::~AP_Spektrum_Telem(void)
      61             : {
      62           0 :     singleton = nullptr;
      63           0 : }
      64             : 
      65           0 : bool AP_Spektrum_Telem::init(void)
      66             : {
      67             :     // sanity check that we are using a UART for RC input
      68           0 :     if (!AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_RCIN, 0)) {
      69           0 :         return false;
      70             :     }
      71           0 :     return AP_RCTelemetry::init();
      72             : }
      73             : 
      74             : /*
      75             :   setup ready for passthrough telem
      76             :  */
      77           0 : void AP_Spektrum_Telem::setup_wfq_scheduler(void)
      78             : {
      79             :     // initialize packet weights for the WFQ scheduler
      80             :     // priority[i] = 1/_scheduler.packet_weight[i]
      81             :     // rate[i] = LinkRate * ( priority[i] / (sum(priority[1-n])) )
      82             : 
      83             :     // Spektrum telemetry rate is 46Hz, so these rates must fit
      84           0 :     add_scheduler_entry(50, 100);   // qos        10Hz
      85           0 :     add_scheduler_entry(50, 100);   // rpm        10Hz
      86           0 :     add_scheduler_entry(50, 100);   // text,      10Hz
      87           0 :     add_scheduler_entry(50, 120);   // Attitude and compass 8Hz
      88           0 :     add_scheduler_entry(550, 280);  // GPS        3Hz
      89           0 :     add_scheduler_entry(550, 280);  // ESC        3Hz
      90           0 :     add_scheduler_entry(400, 250);  // altitude   4Hz
      91           0 :     add_scheduler_entry(400, 250);  // airspeed   4Hz
      92           0 :     add_scheduler_entry(700, 500);  // GPS status 2Hz
      93           0 :     add_scheduler_entry(1300, 500); // batt volt  2Hz
      94           0 :     add_scheduler_entry(1300, 500); // batt curr  2Hz
      95           0 :     add_scheduler_entry(1300, 500); // batt mah   2Hz
      96           0 :     add_scheduler_entry(1300, 500); // temp       2Hz
      97           0 : }
      98             : 
      99           0 : void AP_Spektrum_Telem::adjust_packet_weight(bool queue_empty)
     100             : {
     101           0 :     if (!queue_empty) {
     102           0 :         _scheduler.packet_weight[TEXT] = 50;         // messages
     103             :     } else {
     104           0 :         _scheduler.packet_weight[TEXT] = 5000;        // messages
     105             :     }
     106           0 : }
     107             : 
     108             : // WFQ scheduler
     109           0 : bool AP_Spektrum_Telem::is_packet_ready(uint8_t idx, bool queue_empty)
     110             : {
     111           0 :     bool packet_ready = false;
     112           0 :     switch (idx) {
     113           0 :     case TEXT:
     114           0 :         packet_ready = !queue_empty;
     115           0 :         break;
     116           0 :     default:
     117           0 :         packet_ready = true;
     118           0 :         break;
     119             :     }
     120             : 
     121           0 :     return packet_ready;
     122             : }
     123             : 
     124             : // WFQ scheduler
     125           0 : void AP_Spektrum_Telem::process_packet(uint8_t idx)
     126             : {
     127             :     // send packet
     128           0 :     switch (idx) {
     129           0 :         case QOS: // QOS
     130           0 :             calc_qos();
     131           0 :             break;
     132           0 :         case RPM: // RPM
     133           0 :             calc_rpm();
     134           0 :             break;
     135           0 :         case TEXT: // status text
     136           0 :             if (repeat_msg_chunk() || get_next_msg_chunk()) {
     137           0 :                 send_msg_chunk(_msg_chunk);
     138             :             }
     139           0 :             break;
     140           0 :         case ATTITUDE: // Attitude and compass
     141           0 :             calc_attandmag();
     142           0 :             break;
     143           0 :         case GPS_LOC: // GPS location
     144           0 :             calc_gps_location();
     145           0 :             break;
     146           0 :         case ESC: // ESC
     147           0 :             calc_esc();
     148           0 :             break;
     149           0 :         case ALTITUDE: // altitude
     150           0 :             calc_altitude();
     151           0 :             break;
     152           0 :         case AIRSPEED: // airspeed
     153           0 :             calc_airspeed();
     154           0 :             break;
     155           0 :         case GPS_STATUS: // GPS status
     156           0 :             calc_gps_status();
     157           0 :             break;
     158           0 :         case VOLTAGE: // Battery volts
     159           0 :             calc_batt_volts(0);
     160           0 :             break;
     161           0 :         case AMPS: // Battery current
     162           0 :             calc_batt_amps(0);
     163           0 :             break;
     164           0 :         case MAH: // Battery current & mah
     165           0 :             calc_batt_mah();
     166           0 :             break;
     167           0 :         case TEMP: // temperature
     168           0 :             calc_temperature(0);
     169           0 :             break;
     170           0 :         default:
     171           0 :             break;
     172             :     }
     173           0 : }
     174             : 
     175             : // whether to repeat the last texgen output
     176           0 : bool AP_Spektrum_Telem::repeat_msg_chunk(void)
     177             : {
     178           0 :     if (_msg_chunk.repeats == 0) {
     179           0 :         return false;
     180             :     }
     181             : 
     182             :     // repeat each message chunk 3 times to ensure transmission
     183             :     // on slow links reduce the number of duplicate chunks
     184           0 :     uint8_t extra_chunks = 2;
     185             : 
     186           0 :     if (_scheduler.avg_packet_rate < 20) {
     187           0 :         extra_chunks = 0;
     188           0 :     } else if (_scheduler.avg_packet_rate < 30) {
     189           0 :         extra_chunks = 1;
     190             :     }
     191             : 
     192           0 :     if (_msg_chunk.repeats++ > extra_chunks) {
     193           0 :         _msg_chunk.repeats = 0;
     194           0 :         return false;
     195             :     }
     196           0 :     return true;
     197             : }
     198             : 
     199             : 
     200             : // grabs one "chunk" (13 bytes) of the queued message to be transmitted
     201           0 : bool AP_Spektrum_Telem::get_next_msg_chunk(void)
     202             : {
     203           0 :     _msg_chunk.repeats++;
     204             : 
     205           0 :     if (!_statustext.available) {
     206           0 :         WITH_SEMAPHORE(_statustext.sem);
     207           0 :         if (!_statustext.queue.pop(_statustext.next)) {
     208           0 :             return false;
     209             :         }
     210             : 
     211           0 :         _statustext.available = true;
     212             : 
     213             :         // We're going to display a new message so first clear the screen
     214           0 :         _msg_chunk.linenumber = 0xFF;
     215           0 :         _msg_chunk.char_index = 0;
     216           0 :         return true;
     217           0 :     }
     218             : 
     219           0 :     uint8_t character = 0;
     220           0 :     memset(_msg_chunk.chunk, 0, MAX_TEXTGEN_LEN);
     221             : 
     222           0 :     const uint8_t message_len = sizeof(_statustext.next.text);
     223             :     // the message fits in an entire line of text
     224             :     if (message_len < MAX_TEXTGEN_LEN) {
     225             :         memcpy(_msg_chunk.chunk, _statustext.next.text, message_len);
     226             :         _msg_chunk.linenumber = 0;
     227             :         _statustext.available = false;
     228             :         return true;
     229             :     }
     230             : 
     231             :     // a following part of multi-line text
     232           0 :     if (_msg_chunk.linenumber == 0xFF) {
     233           0 :         _msg_chunk.linenumber = 0;
     234           0 :     } else if (_msg_chunk.char_index > 0) {
     235           0 :         _msg_chunk.linenumber++;
     236             :     }
     237             : 
     238             :     // skip leading whitespace
     239           0 :     while (_statustext.next.text[_msg_chunk.char_index] == ' ' && _msg_chunk.char_index < message_len) {
     240           0 :         _msg_chunk.char_index++;
     241             :     }
     242             : 
     243           0 :     uint8_t space_idx = 0;
     244           0 :     const uint8_t begin_idx = _msg_chunk.char_index;
     245             :     // can't fit it all on one line so wrap at an appropriate place
     246           0 :     for (int i = 0; i < MAX_TEXTGEN_LEN && _msg_chunk.char_index < message_len; i++) {
     247           0 :         character = _statustext.next.text[_msg_chunk.char_index++];
     248             :         // split at the first ':'
     249           0 :         if (character == ':') {
     250           0 :             _msg_chunk.chunk[i] = 0;
     251           0 :             break;
     252             :         }
     253             :         // record the last space if we need to go back there
     254           0 :         if (character == ' ') {
     255           0 :             space_idx = _msg_chunk.char_index;
     256             :         }
     257             : 
     258           0 :         _msg_chunk.chunk[i] = character;
     259             : 
     260           0 :         if (!character) {
     261           0 :             break;
     262             :         }
     263             :     }
     264             :     // still not done, can we break at a word boundary?
     265           0 :     if (character != 0 && _msg_chunk.char_index < message_len && space_idx > 0) {
     266           0 :         _msg_chunk.char_index = space_idx;
     267           0 :         _msg_chunk.chunk[space_idx - begin_idx - 1] = 0;
     268             :     }
     269             : 
     270             :     // we've reached the end of the message (string terminated by '\0' or last character of the string has been processed)
     271           0 :     if (character == 0 || _msg_chunk.char_index == message_len) {
     272           0 :         _msg_chunk.char_index = 0; // reset index to get ready to process the next message
     273           0 :         _statustext.available = false;
     274             :     }
     275             : 
     276           0 :     return true;
     277             : }
     278             : 
     279             : // prepare qos data - mandatory frame that must be sent periodically
     280           0 : void AP_Spektrum_Telem::calc_qos()
     281             : {
     282           0 :     _telem.qos.identifier = TELE_DEVICE_QOS;
     283           0 :     _telem.qos.sID = 0;
     284           0 :     _telem.qos.A = 0xFFFF;
     285           0 :     _telem.qos.B = 0xFFFF;
     286           0 :     _telem.qos.L = 0xFFFF;
     287           0 :     _telem.qos.R = 0xFFFF;
     288           0 :     _telem.qos.F = 0xFFFF;
     289           0 :     _telem.qos.H = 0xFFFF;
     290           0 :     _telem.qos.rxVoltage = 0xFFFF;
     291           0 :     _telem_pending = true;
     292           0 : }
     293             : 
     294             : // prepare rpm data - B/E mandatory frame that must be sent periodically
     295           0 : void AP_Spektrum_Telem::calc_rpm()
     296             : {
     297             : #if AP_BATTERY_ENABLED
     298           0 :     const AP_BattMonitor &_battery = AP::battery();
     299             : #endif
     300             : 
     301           0 :     _telem.rpm.identifier = TELE_DEVICE_RPM;
     302           0 :     _telem.rpm.sID = 0;
     303             :     // battery voltage in centivolts, can have up to a 12S battery (4.25Vx12S = 51.0V)
     304             : #if AP_BATTERY_ENABLED
     305           0 :     _telem.rpm.volts = htobe16(((uint16_t)roundf(_battery.voltage(0) * 100.0f)));
     306             : #endif
     307           0 :     _telem.rpm.temperature = htobe16(int16_t(roundf(32.0f + AP::baro().get_temperature(0) * 9.0f / 5.0f)));
     308             : #if AP_RPM_ENABLED
     309           0 :     const AP_RPM *rpm = AP::rpm();
     310             :     float rpm_value;
     311           0 :     if (!rpm || !rpm->get_rpm(0, rpm_value) || rpm_value < 999.0f) {
     312           0 :         rpm_value = 999.0f;
     313             :     }
     314           0 :     _telem.rpm.microseconds = htobe16(uint16_t(roundf(MICROSEC_PER_MINUTE / rpm_value)));
     315           0 :     _telem.rpm.dBm_A = 0x7F;
     316           0 :     _telem.rpm.dBm_B = 0x7F;
     317             : #endif
     318           0 :     _telem_pending = true;
     319           0 : }
     320             : 
     321           0 : void AP_Spektrum_Telem::send_msg_chunk(const MessageChunk& chunk)
     322             : {
     323           0 :     memcpy(_telem.textgen.text, chunk.chunk, 13);
     324           0 :     _telem.textgen.identifier = TELE_DEVICE_TEXTGEN;
     325           0 :     _telem.textgen.lineNumber = chunk.linenumber;
     326           0 :     _telem.textgen.sID = 0;
     327           0 :     _telem_pending = true;
     328           0 : }
     329             : 
     330             : // prepare battery data - B/E but not supported by Spektrum
     331           0 : void AP_Spektrum_Telem::calc_batt_volts(uint8_t instance)
     332             : {
     333             : #if AP_BATTERY_ENABLED
     334           0 :     const AP_BattMonitor &_battery = AP::battery();
     335             : 
     336             :     // battery voltage in centivolts, can have up to a 12S battery (4.25Vx12S = 51.0V)
     337           0 :     _telem.hv.volts = htobe16(uint16_t(roundf(_battery.voltage(instance) * 100.0f)));
     338             : #endif
     339           0 :     _telem.hv.identifier = TELE_DEVICE_VOLTAGE;
     340           0 :     _telem.hv.sID = 0;
     341           0 :     _telem_pending = true;
     342           0 : }
     343             : 
     344             : // prepare battery data - B/E but not supported by Spektrum
     345           0 : void AP_Spektrum_Telem::calc_batt_amps(uint8_t instance)
     346             : {
     347             : #if AP_BATTERY_ENABLED
     348           0 :     const AP_BattMonitor &_battery = AP::battery();
     349             : 
     350             :     float current;
     351           0 :     if (!_battery.current_amps(current, instance)) {
     352           0 :         current = 0;
     353             :     }
     354             : 
     355             :     // Range: +/- 150A     Resolution: 300A / 2048 = 0.196791 A/count
     356           0 :     _telem.amps.current = htobe16(int16_t(roundf(current * 2048.0f / 300.0f)));
     357             : #endif
     358           0 :     _telem.amps.identifier = TELE_DEVICE_AMPS;
     359           0 :     _telem.amps.sID = 0;
     360           0 :     _telem_pending = true;
     361           0 : }
     362             : 
     363             : // prepare battery data - L/E
     364           0 : void AP_Spektrum_Telem::calc_batt_mah()
     365             : {
     366             : #if AP_BATTERY_ENABLED
     367           0 :     const AP_BattMonitor &_battery = AP::battery();
     368             : #endif
     369             : 
     370           0 :     _telem.fpMAH.identifier = TELE_DEVICE_FP_MAH;
     371           0 :     _telem.fpMAH.sID = 0;
     372             : 
     373             : #if AP_BATTERY_ENABLED
     374             :     float current;
     375           0 :     if (!_battery.current_amps(current, 0)) {
     376           0 :         current = 0;
     377             :     }
     378           0 :     _telem.fpMAH.current_A = int16_t(roundf(current * 10.0f));     // Instantaneous current, 0.1A (0-3276.6A)
     379             : 
     380             :     float used_mah;
     381           0 :     if (!_battery.consumed_mah(used_mah, 0)) {
     382           0 :         used_mah = 0;
     383             :     }
     384           0 :     _telem.fpMAH.chargeUsed_A = int16_t(roundf(used_mah));         // Integrated mAh used, 1mAh (0-32.766Ah)
     385             : 
     386             :     float temp;
     387           0 :     if (_battery.get_temperature(temp, 0)) {
     388           0 :          _telem.fpMAH.temp_A = uint16_t(roundf(temp * 10.0f));     // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
     389             :     } else {
     390           0 :         _telem.fpMAH.temp_A = 0x7FFF;
     391             :     }
     392             : 
     393           0 :     if (!_battery.current_amps(current, 1)) {
     394           0 :         current = 0;
     395             :     }
     396           0 :     _telem.fpMAH.current_B = int16_t(roundf(current * 10.0f));     // Instantaneous current, 0.1A (0-3276.6A)
     397             : 
     398           0 :     if (!_battery.consumed_mah(used_mah, 1)) {
     399           0 :         used_mah = 0;
     400             :     }
     401           0 :     _telem.fpMAH.chargeUsed_B = int16_t(roundf(used_mah));         // Integrated mAh used, 1mAh (0-32.766Ah)
     402             : 
     403           0 :     if (_battery.get_temperature(temp, 1)) {
     404           0 :          _telem.fpMAH.temp_B = uint16_t(roundf(temp * 10.0f));     // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
     405             :     } else {
     406           0 :         _telem.fpMAH.temp_B = 0x7FFF;
     407             :     }
     408             : #else
     409             :         _telem.fpMAH.temp_A = 0x7FFF;
     410             :         _telem.fpMAH.temp_B = 0x7FFF;
     411             : #endif
     412             : 
     413           0 :     _telem_pending = true;
     414           0 : }
     415             : 
     416             : // prepare temperature data - B/E but not supported by Spektrum
     417           0 : void AP_Spektrum_Telem::calc_temperature(uint8_t instance)
     418             : {
     419           0 :     _telem.temp.temperature = htobe16(int16_t(roundf(32.0f + AP::baro().get_temperature(instance) * 9.0f / 5.0f)));
     420           0 :     _telem.temp.identifier = TELE_DEVICE_TEMPERATURE;
     421           0 :     _telem.temp.sID = 0;
     422           0 :     _telem_pending = true;
     423           0 : }
     424             : 
     425             : // prepare altitude data - B/E
     426           0 : void AP_Spektrum_Telem::calc_altitude()
     427             : {
     428           0 :     _telem.alt.identifier = TELE_DEVICE_ALTITUDE;
     429           0 :     _telem.alt.sID = 0;
     430             : 
     431           0 :     AP_AHRS &ahrs = AP::ahrs();
     432           0 :     WITH_SEMAPHORE(ahrs.get_semaphore());
     433             : 
     434           0 :     float alt = 0;
     435           0 :     ahrs.get_relative_position_D_home(alt);
     436           0 :     alt = roundf(-alt * 10.0f);
     437           0 :     _telem.alt.altitude = htobe16(uint16_t(alt));            // .1m increments
     438           0 :     _max_alt = MAX(alt, _max_alt);
     439           0 :     _telem.alt.maxAltitude = htobe16(uint16_t(_max_alt));    // .1m increments
     440           0 :     _telem_pending = true;
     441           0 : }
     442             : 
     443             : // prepare airspeed data - B/E
     444           0 : void AP_Spektrum_Telem::calc_airspeed()
     445             : {
     446           0 :     _telem.speed.identifier = TELE_DEVICE_AIRSPEED;
     447           0 :     _telem.speed.sID = 0;
     448             : 
     449           0 :     AP_AHRS &ahrs = AP::ahrs();
     450           0 :     WITH_SEMAPHORE(ahrs.get_semaphore());
     451             : 
     452           0 :     float speed = 0.0f;
     453             : #if AP_AIRSPEED_ENABLED
     454           0 :     const AP_Airspeed *airspeed = AP::airspeed();
     455           0 :     if (airspeed && airspeed->healthy()) {
     456           0 :         speed = roundf(airspeed->get_airspeed() * 3.6);
     457             :     } else {
     458           0 :         speed = roundf(AP::ahrs().groundspeed() * 3.6);
     459             :     }
     460             : #else
     461             :     speed = roundf(AP::ahrs().groundspeed() * 3.6);
     462             : #endif
     463             : 
     464           0 :     _telem.speed.airspeed = htobe16(uint16_t(speed));           // 1 km/h increments
     465           0 :     _max_speed = MAX(speed, _max_speed);
     466           0 :     _telem.speed.maxAirspeed = htobe16(uint16_t(_max_speed));   // 1 km/h increments
     467           0 :     _telem_pending = true;
     468           0 : }
     469             : 
     470             : // prepare attitude and compass data - L/E
     471           0 : void AP_Spektrum_Telem::calc_attandmag(void)
     472             : {
     473           0 :     _telem.attMag.identifier = TELE_DEVICE_ATTMAG;
     474           0 :     _telem.attMag.sID = 0;
     475             : 
     476           0 :     AP_AHRS &_ahrs = AP::ahrs();
     477           0 :     WITH_SEMAPHORE(_ahrs.get_semaphore());
     478             : 
     479             :     // Attitude, 3 axes.  Roll is a rotation about the X Axis of the vehicle using the RHR.
     480             :     // Units are 0.1 deg - Pitch is a rotation about the Y Axis of the vehicle using the RHR.
     481             :     // Yaw is a rotation about the Z Axis of the vehicle using the RHR.
     482           0 :     _telem.attMag.attRoll = _ahrs.roll_sensor / 10;
     483           0 :     _telem.attMag.attPitch = _ahrs.pitch_sensor / 10;
     484           0 :     _telem.attMag.attYaw = _ahrs.yaw_sensor / 10;
     485           0 :     _telem.attMag.heading = (_ahrs.yaw_sensor / 10) % 3600;  // Heading, 0.1deg
     486             : 
     487           0 :     const Vector3f& field = AP::compass().get_field();
     488             : 
     489           0 :     _telem.attMag.magX = int16_t(roundf(field.x * 10.0f));             // Units are 0.1mG
     490           0 :     _telem.attMag.magY = int16_t(roundf(field.y * 10.0f));
     491           0 :     _telem.attMag.magZ = int16_t(roundf(field.z * 10.0f));
     492           0 :     _telem_pending = true;
     493           0 : }
     494             : 
     495             : // prepare gps location - L/E
     496           0 : void AP_Spektrum_Telem::calc_gps_location()
     497             : {
     498           0 :     const Location &loc = AP::gps().location(0); // use the first gps instance (same as in send_mavlink_gps_raw)
     499           0 :     const uint32_t u1e8 = 100000000, u1e7 = 10000000, u1e6 = 1000000, u1e5 = 100000, u1e4 = 10000;
     500             : 
     501           0 :     _telem.gpsloc.identifier = TELE_DEVICE_GPS_LOC;                 // Source device = 0x16
     502           0 :     _telem.gpsloc.sID = 0;                                          // Secondary ID
     503           0 :     uint32_t alt = (abs(loc.alt) / 10) % u1e6;
     504           0 :     _telem.gpsloc.altitudeLow = ((alt % u1e4 / 1000) << 12) | ((alt % 1000 / 100) << 8)
     505           0 :         | ((alt % 100 / 10) << 4) | (alt % 100); // BCD, meters, format 3.1 (Low order of altitude)
     506             : 
     507           0 :     const float lat = fabsf(loc.lat / 1.0e7f);                       // BCD, format 4.4, Degrees * 100 + minutes, less than 100 degrees
     508           0 :     const float lng = fabsf(loc.lng / 1.0e7f);                       // BCD, format 4.4 , Degrees * 100 + minutes, flag indicates > 99 degrees
     509             : 
     510           0 :     const uint32_t ulat = roundf((int32_t(lat) * 100.0f + (lat - int32_t(lat)) * 60.0f) * 10000.0f);
     511           0 :     const uint32_t ulng = roundf((int32_t(lng) * 100.0f + (lng - int32_t(lng)) * 60.0f) * 10000.0f);
     512             : 
     513           0 :     _telem.gpsloc.latitude = ((ulat % u1e8 / u1e7) << 28) | ((ulat % u1e7 / u1e6) << 24) | ((ulat % u1e6 / u1e5) << 20) | ((ulat % u1e5 / u1e4) << 16)
     514           0 :         | ((ulat % u1e4 / 1000) << 12) | ((ulat % 1000 / 100) << 8) | ((ulat % 100 / 10) << 4) | (ulat % 10);
     515           0 :     _telem.gpsloc.longitude = ((ulng % u1e8 / u1e7) << 28) | ((ulng % u1e7 / u1e6) << 24) | ((ulng % u1e6 / u1e5) << 20) | ((ulng % u1e5 / u1e4) << 16)
     516           0 :         | ((ulng % u1e4 / 1000) << 12) | ((ulng % 1000 / 100) << 8) | ((ulng % 100 / 10) << 4) | (ulng % 10);
     517             : 
     518           0 :     uint16_t course = uint16_t(roundf(AP::gps().ground_course() * 10.0f));
     519           0 :     _telem.gpsloc.course = ((course % u1e5 / u1e4) << 12) | ((course % u1e4 / 1000) << 8)  | ((course % 1000 / 100) << 4) | (course % 100 / 10);  // BCD, 3.1
     520           0 :     uint16_t hdop = AP::gps().get_hdop(0);
     521           0 :     _telem.gpsloc.HDOP = ((hdop % 1000 / 100) << 4) | (hdop % 100 / 10); // BCD, format 1.1
     522           0 :     _telem.gpsloc.GPSflags = 0;
     523             : 
     524           0 :     if (AP::gps().status(0) >= AP_GPS_FixType::FIX_3D) {
     525           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_3D_FIX;
     526             :     }
     527           0 :     if (loc.alt < 0) {
     528           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_NEGATIVE_ALT;
     529             :     }
     530           0 :     if ((loc.lng / 1e7) > 99) {
     531           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_LONGITUDE_GREATER_99;
     532             :     }
     533           0 :     if (loc.lat >= 0) {
     534           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_IS_NORTH;
     535             :     }
     536           0 :     if (loc.lng >= 0) {
     537           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_IS_EAST;
     538             :     }
     539           0 :     if (AP::gps().status(0) > AP_GPS_FixType::NONE) {
     540           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_GPS_FIX_VALID;
     541             :     }
     542           0 :     if (AP::gps().status(0) >= AP_GPS_FixType::NONE) {
     543           0 :         _telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_GPS_DATA_RECEIVED;
     544             :     }
     545           0 :     _telem_pending = true;
     546           0 : }
     547             : 
     548             : // prepare gps status - L/E
     549           0 : void AP_Spektrum_Telem::calc_gps_status()
     550             : {
     551           0 :     const Location &loc = AP::gps().location(0);
     552             : 
     553           0 :     _telem.gpsstat.identifier = TELE_DEVICE_GPS_STATS;              // Source device = 0x17
     554           0 :     _telem.gpsstat.sID = 0;                                         // Secondary ID
     555             : 
     556           0 :     uint16_t knots = roundf(AP::gps().ground_speed() * 1.94384f * 10.0f);
     557           0 :     _telem.gpsstat.speed = ((knots % 10000 / 1000) << 12) | ((knots % 1000 / 100) << 8) | ((knots % 100 / 10) << 4) | (knots % 10); // BCD, knots, format 3.1
     558             :     uint16_t ms;
     559             :     uint8_t h, m, s;
     560             : #if AP_RTC_ENABLED
     561           0 :     AP::rtc().get_system_clock_utc(h, m, s, ms);                    // BCD, format HH:MM:SS.S, format 6.1
     562             :     // FIXME: the above call can fail!
     563             : #else
     564             :     h = 0;
     565             :     m = 0;
     566             :     s = 0;
     567             :     ms = 0;
     568             : #endif
     569           0 :     _telem.gpsstat.UTC = ((((h / 10) << 4) | (h % 10)) << 20) | ((((m / 10) << 4) | (m % 10)) << 12) | ((((s / 10) << 4) | (s % 10)) << 4) | (ms / 100) ;
     570           0 :     uint8_t nsats =  AP::gps().num_sats();
     571           0 :     _telem.gpsstat.numSats = ((nsats / 10) << 4) | (nsats % 10);    // BCD, 0-99
     572           0 :     uint32_t alt = (abs(loc.alt) / 100000);
     573           0 :     _telem.gpsstat.altitudeHigh = ((alt / 10) << 4) | (alt % 10); // BCD, meters, format 2.0 (High order of altitude)
     574           0 :     _telem_pending = true;
     575           0 : }
     576             : 
     577             : // prepare ESC information - B/E
     578           0 : void AP_Spektrum_Telem::calc_esc()
     579             : {
     580             : #if HAL_WITH_ESC_TELEM
     581           0 :     uint8_t esc = AP::esc_telem().get_max_rpm_esc();
     582           0 :     const volatile AP_ESC_Telem_Backend::TelemetryData& td = AP::esc_telem().get_telem_data(esc); // ideally should rotate between ESCs
     583           0 :     float rpm = 0.0f;
     584           0 :     uint16_t rpmdata = 0xFFFFU;
     585           0 :     if (AP::esc_telem().get_rpm(esc, rpm)) {
     586           0 :         rpmdata = uint16_t(roundf(rpm));
     587             :     }
     588             : 
     589           0 :         _telem.esc.identifier = TELE_DEVICE_ESC;            // Source device = 0x20
     590           0 :         _telem.esc.sID = 0;                                                                     // Secondary ID
     591           0 :         _telem.esc.RPM = htobe16(rpmdata);      // Electrical RPM, 10RPM (0-655340 RPM)  0xFFFF --> "No data"
     592           0 :         _telem.esc.voltsInput = htobe16(td.voltage * 100);          // Volts, 0.01v (0-655.34V)       0xFFFF --> "No data"
     593           0 :         _telem.esc.tempFET = htobe16(td.temperature_cdeg * 10); // Temperature, 0.1C (0-6553.4C)  0xFFFF --> "No data"
     594           0 :         _telem.esc.currentMotor = htobe16(td.current * 100);            // Current, 10mA (0-655.34A)      0xFFFF --> "No data"
     595           0 :         _telem.esc.tempBEC = 0xFFFF;                                            // Temperature, 0.1C (0-6553.4C)  0xFFFF --> "No data"
     596           0 :         _telem.esc.currentBEC = 0xFF;                                           // BEC Current, 100mA (0-25.4A)   0xFF ----> "No data"
     597           0 :         _telem.esc.voltsBEC = 0xFF;                                                     // BEC Volts, 0.05V (0-12.70V)    0xFF ----> "No data"
     598           0 :         _telem.esc.throttle = 0xFF;                                                     // 0.5% (0-100%)                  0xFF ----> "No data"
     599           0 :         _telem.esc.powerOut = 0xFF;                                                     // Power Output, 0.5% (0-127%)    0xFF ----> "No data"
     600           0 :     _telem_pending = true;
     601             : #endif
     602           0 : }
     603             : 
     604             : /*
     605             :   fetch Spektrum data for an external transport, such as SRXL2
     606             :  */
     607           0 : bool AP_Spektrum_Telem::_get_telem_data(uint8_t* data)
     608             : {
     609           0 :     memset(&_telem, 0, 16);
     610           0 :     run_wfq_scheduler();
     611           0 :     if (!_telem_pending) {
     612           0 :         return false;
     613             :     }
     614           0 :     memcpy(data, &_telem, 16);
     615           0 :     _telem_pending = false;
     616           0 :     return true;
     617             : }
     618             : 
     619             : /*
     620             :   fetch data for an external transport, such as SRXL2
     621             :  */
     622           0 : bool AP_Spektrum_Telem::get_telem_data(uint8_t* data)
     623             : {
     624           0 :     if (!singleton && !hal.util->get_soft_armed()) {
     625             :         // if telem data is requested when we are disarmed and don't
     626             :         // yet have a AP_Spektrum_Telem object then try to allocate one
     627           0 :         NEW_NOTHROW AP_Spektrum_Telem();
     628             :         // initialize the passthrough scheduler
     629           0 :         if (singleton) {
     630           0 :             singleton->init();
     631             :         }
     632             :     }
     633           0 :     if (!singleton) {
     634           0 :         return false;
     635             :     }
     636           0 :     return singleton->_get_telem_data(data);
     637             : }
     638             : 
     639             : namespace AP {
     640      262927 :     AP_Spektrum_Telem *spektrum_telem() {
     641      262927 :         return AP_Spektrum_Telem::get_singleton();
     642             :     }
     643             : };
     644             : 
     645             : #endif

Generated by: LCOV version 1.14