LCOV - code coverage report
Current view: top level - AP_Frsky_Telem - AP_Frsky_SPort.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 204 255 80.0 %
Date: 2026-09-09 11:02:08 Functions: 8 10 80.0 %

          Line data    Source code
       1             : #include "AP_Frsky_SPort.h"
       2             : 
       3             : #if AP_FRSKY_SPORT_TELEM_ENABLED
       4             : 
       5             : #include <AP_HAL/utility/sparse-endian.h>
       6             : #include <AP_BattMonitor/AP_BattMonitor.h>
       7             : #include <AP_GPS/AP_GPS.h>
       8             : #include <GCS_MAVLink/GCS.h>
       9             : #include <AP_RPM/AP_RPM.h>
      10             : 
      11             : #include "AP_Frsky_SPortParser.h"
      12             : 
      13             : #include <string.h>
      14             : 
      15             : extern const AP_HAL::HAL& hal;
      16             : 
      17             : AP_Frsky_SPort *AP_Frsky_SPort::singleton;
      18             : 
      19             : /*
      20             :  * send telemetry data
      21             :  * for FrSky SPort protocol (X-receivers)
      22             :  */
      23        6666 : void AP_Frsky_SPort::send(void)
      24             : {
      25        6666 :     const uint16_t numc = MIN(_port->available(), 1024U);
      26             : 
      27             :     // this is the constant for hub data frame
      28        6666 :     if (_port->txspace() < 19) {
      29        3834 :         return;
      30             :     }
      31             : 
      32        2832 :     if (numc == 0) {
      33             :         // no serial data to process do bg tasks
      34        2783 :         if (_SPort.vario_refresh) {
      35           5 :             calc_nav_alt(); // nav altitude is not recalculated until all of it has been sent
      36           5 :             _SPort.vario_refresh = false;
      37             :         }
      38        2783 :         if (_SPort.gps_refresh) {
      39           2 :             calc_gps_position(); // gps data is not recalculated until all of it has been sent
      40           2 :             _SPort.gps_refresh = false;
      41             :         }
      42        2783 :         return;
      43             :     }
      44             : 
      45         147 :     for (int16_t i = 0; i < numc; i++) {
      46             :         uint8_t readbyte;
      47          98 :         if (!_port->read(readbyte)) {
      48           0 :             break;
      49             :         }
      50          98 :         if (_SPort.sport_status == false) {
      51          49 :             if  (readbyte == FRAME_HEAD) {
      52          49 :                 _SPort.sport_status = true;
      53             :             }
      54             :         } else {
      55          49 :             const AP_BattMonitor &_battery = AP::battery();
      56          49 :             switch (readbyte) {
      57          10 :             case SENSOR_ID_VARIO:   // Sensor ID  0
      58          10 :                 switch (_SPort.vario_call) {
      59           5 :                 case 0:
      60           5 :                     send_sport_frame(SPORT_DATA_FRAME, ALT_ID, _SPort_data.alt_nav_meters*100 + _SPort_data.alt_nav_cm); // send altitude in cm
      61           5 :                     break;
      62           5 :                 case 1:
      63           5 :                     send_sport_frame(SPORT_DATA_FRAME, VARIO_ID, _SPort_data.vario_vspd); // send vspeed cm/s
      64           5 :                     _SPort.vario_refresh = true;
      65           5 :                     break;
      66             :                 }
      67          10 :                 if (++_SPort.vario_call > 1) {
      68           5 :                     _SPort.vario_call = 0;
      69             :                 }
      70          10 :                 break;
      71          10 :             case SENSOR_ID_FAS: // Sensor ID  2
      72          10 :                 switch (_SPort.fas_call) {
      73           4 :                 case 0:
      74             :                     {
      75           4 :                         uint8_t percentage = 0;
      76           4 :                         IGNORE_RETURN(_battery.capacity_remaining_pct(percentage));
      77           4 :                         send_sport_frame(SPORT_DATA_FRAME, FUEL_ID, (uint16_t)roundf(percentage)); // send battery remaining
      78           4 :                         break;
      79             :                     }
      80           3 :                 case 1:
      81           3 :                     send_sport_frame(SPORT_DATA_FRAME, VFAS_ID, (uint16_t)roundf(_battery.voltage() * 100.0f)); // send battery voltage in cV
      82           3 :                     break;
      83           3 :                 case 2: {
      84             :                     float current;
      85           3 :                     if (!_battery.current_amps(current)) {
      86           0 :                         current = 0;
      87             :                     }
      88           3 :                     send_sport_frame(SPORT_DATA_FRAME, CURR_ID, (uint16_t)roundf(current * 10.0f)); // send current consumption in dA
      89           3 :                     break;
      90             :                 }
      91             :                 break;
      92             :                 }
      93          10 :                 if (++_SPort.fas_call > 2) {
      94           3 :                     _SPort.fas_call = 0;
      95             :                 }
      96          10 :                 break;
      97          10 :             case SENSOR_ID_GPS: // Sensor ID  3
      98          10 :                 switch (_SPort.gps_call) {
      99           2 :                 case 0:
     100           2 :                     send_sport_frame(SPORT_DATA_FRAME, GPS_LONG_LATI_FIRST_ID, calc_gps_latlng(_passthrough.send_latitude)); // gps latitude or longitude
     101           2 :                     break;
     102           2 :                 case 1:
     103           2 :                     send_sport_frame(SPORT_DATA_FRAME, GPS_LONG_LATI_FIRST_ID, calc_gps_latlng(_passthrough.send_latitude)); // gps latitude or longitude
     104           2 :                     break;
     105           2 :                 case 2:
     106           2 :                     send_sport_frame(SPORT_DATA_FRAME, GPS_SPEED_ID, _SPort_data.speed_in_meter*1000 + _SPort_data.speed_in_centimeter*10); // send gps speed in mm/sec
     107           2 :                     break;
     108           2 :                 case 3:
     109           2 :                     send_sport_frame(SPORT_DATA_FRAME, GPS_ALT_ID, _SPort_data.alt_gps_meters*100+_SPort_data.alt_gps_cm); // send gps altitude in cm
     110           2 :                     break;
     111           2 :                 case 4:
     112           2 :                     send_sport_frame(SPORT_DATA_FRAME, GPS_COURS_ID, _SPort_data.yaw*100); // send heading in cd based on AHRS and not GPS
     113           2 :                     _SPort.gps_refresh = true;
     114           2 :                     break;
     115             :                 }
     116          10 :                 if (++_SPort.gps_call > 4) {
     117           2 :                     _SPort.gps_call = 0;
     118             :                 }
     119          10 :                 break;
     120          10 :             case SENSOR_ID_RPM: // Sensor ID 4
     121             : #if AP_RPM_ENABLED
     122             :                 {
     123          10 :                     const AP_RPM* rpm = AP::rpm();
     124          10 :                     if (rpm == nullptr) {
     125           0 :                         break;
     126             :                     }
     127             :                     int32_t value;
     128          10 :                     if (calc_rpm(_SPort.rpm_call, value)) {
     129             :                         // use high numbered frsky sensor ids to leave low numbered free for externally attached physical frsky sensors
     130          10 :                         uint16_t id = RPM1_ID;
     131          10 :                         if (_SPort.rpm_call != 0) {
     132             :                             // only two sensors are currently supported
     133           0 :                             id = RPM2_ID;
     134             :                         }
     135          10 :                         send_sport_frame(SPORT_DATA_FRAME, id, value);
     136             :                     }
     137          10 :                     if (++_SPort.rpm_call > MIN(rpm->num_sensors()-1, 1)) {
     138          10 :                         _SPort.rpm_call = 0;
     139             :                     }
     140             :                 }
     141             : #endif  // AP_RPM_ENABLED
     142          10 :                 break;
     143           9 :             case SENSOR_ID_SP2UR: // Sensor ID  6
     144           9 :                 switch (_SPort.various_call) {
     145           5 :                 case 0 :
     146           5 :                     send_sport_frame(SPORT_DATA_FRAME, TEMP2_ID, (uint16_t)(AP::gps().num_sats() * 10 + (uint8_t)AP::gps().status())); // send GPS status and number of satellites as num_sats*10 + status (to fit into a uint8_t)
     147           5 :                     break;
     148           4 :                 case 1:
     149           4 :                     send_sport_frame(SPORT_DATA_FRAME, TEMP1_ID, gcs().custom_mode()); // send flight mode
     150           4 :                     break;
     151             :                 }
     152           9 :                 if (++_SPort.various_call > 1) {
     153           4 :                     _SPort.various_call = 0;
     154             :                 }
     155           9 :                 break;
     156           0 :             default:
     157             :                 {
     158             :                     // respond to custom user data polling
     159           0 :                     WITH_SEMAPHORE(_sport_push_buffer.sem);
     160           0 :                     if (_sport_push_buffer.pending && readbyte == _sport_push_buffer.packet.sensor) {
     161           0 :                         send_sport_frame(_sport_push_buffer.packet.frame, _sport_push_buffer.packet.appid, _sport_push_buffer.packet.data);
     162           0 :                         _sport_push_buffer.pending = false;
     163             :                     }
     164           0 :                 }
     165           0 :                 break;
     166             :             }
     167          49 :             _SPort.sport_status = false;
     168             :         }
     169             :     }
     170             : }
     171             : 
     172             : /*
     173             :  * prepare gps latitude/longitude data
     174             :  * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers)
     175             :  */
     176          48 : uint32_t AP_Frsky_SPort::calc_gps_latlng(bool &send_latitude)
     177             : {
     178          48 :     const Location &loc = AP::gps().location(0); // use the first gps instance (same as in send_mavlink_gps_raw)
     179             : 
     180             :     // alternate between latitude and longitude
     181          48 :     if (send_latitude == true) {
     182          24 :         send_latitude = false;
     183          24 :         if (loc.lat < 0) {
     184          23 :             return ((labs(loc.lat)/100)*6) | 0x40000000;
     185             :         } else {
     186           1 :             return ((labs(loc.lat)/100)*6);
     187             :         }
     188             :     } else {
     189          24 :         send_latitude = true;
     190          24 :         if (loc.lng < 0) {
     191           0 :             return ((labs(loc.lng)/100)*6) | 0xC0000000;
     192             :         } else {
     193          24 :             return ((labs(loc.lng)/100)*6) | 0x80000000;
     194             :         }
     195             :     }
     196             : }
     197             : 
     198             : /*
     199             :  * send an 8 bytes SPort frame of FrSky data - for FrSky SPort protocol (X-receivers)
     200             :  */
     201         378 : void  AP_Frsky_SPort::send_sport_frame(uint8_t frame, uint16_t appid, uint32_t data)
     202             : {
     203             :     uint8_t buf[8];
     204             : 
     205         378 :     buf[0] = frame;
     206         378 :     buf[1] = appid & 0xFF;
     207         378 :     buf[2] = appid >> 8;
     208         378 :     memcpy(&buf[3], &data, 4);
     209             : 
     210         378 :     uint16_t sum = 0;
     211        3024 :     for (uint8_t i=0; i<sizeof(buf)-1; i++) {
     212        2646 :         sum += buf[i];
     213        2646 :         sum += sum >> 8;
     214        2646 :         sum &= 0xFF;
     215             :     }
     216         378 :     sum = 0xff - ((sum & 0xff) + (sum >> 8));
     217         378 :     buf[7] = (uint8_t)sum;
     218             : 
     219             :     // perform byte stuffing per SPort spec
     220         378 :     uint8_t len = 0;
     221             :     uint8_t buf2[sizeof(buf)*2+1];
     222             : 
     223        3402 :     for (uint8_t i=0; i<sizeof(buf); i++) {
     224        3024 :         uint8_t c = buf[i];
     225        3024 :         if (c == FRAME_DLE || buf[i] == FRAME_HEAD) {
     226          36 :             buf2[len++] = FRAME_DLE;
     227          36 :             buf2[len++] = c ^ FRAME_XOR;
     228             :         } else {
     229        2988 :             buf2[len++] = c;
     230             :         }
     231             :     }
     232             : #ifndef HAL_BOARD_SITL
     233             :     /*
     234             :       check that we haven't been too slow in responding to the new
     235             :       UART data. If we respond too late then we will overwrite the next
     236             :       polling frame.
     237             :       SPort poll-to-poll period is 11.65ms, a frame takes 1.38ms
     238             :       but specs require we release the bus before 8ms leaving us with 6500us
     239             :      */
     240             :     const uint64_t tend_us = port->receive_time_constraint_us(1);
     241             :     const uint64_t now_us = AP_HAL::micros64();
     242             :     const uint64_t tdelay_us = now_us - tend_us;
     243             :     if (tdelay_us > 6500) {
     244             :         // we've been too slow in responding
     245             :         return;
     246             :     }
     247             : #endif
     248         378 :     _port->write(buf2, len);
     249         378 : }
     250             : 
     251             : extern const AP_HAL::HAL& hal;
     252             : 
     253          19 : bool AP_Frsky_SPortParser::should_process_packet(const uint8_t *packet, bool discard_duplicates)
     254             : {
     255             :     // check for duplicate packets
     256          19 :     if (discard_duplicates) {
     257             :         /*
     258             :           Note: the polling byte packet[0] should be ignored in the comparison
     259             :           because we might get the same packet with different polling bytes
     260             :           We have 2 types of duplicate packets: ghost identical packets sent by the receiver
     261             :           and user duplicate packets sent to compensate for bad link and frame loss, this
     262             :           check should address both types.
     263             :         */
     264          19 :         if (memcmp(&packet[1], &_parse_state.last_packet[1], SPORT_PACKET_SIZE-1) == 0) {
     265           0 :             return false;
     266             :         }
     267          19 :         memcpy(_parse_state.last_packet, packet, SPORT_PACKET_SIZE);
     268             :     }
     269             :     //check CRC
     270          19 :     int16_t crc = 0;
     271         171 :     for (uint8_t i=1; i<SPORT_PACKET_SIZE; ++i) {
     272         152 :         crc += _parse_state.rx_buffer[i]; // 0-1FE
     273         152 :         crc += crc >> 8;  // 0-1FF
     274         152 :         crc &= 0x00ff;    // 0-FF
     275             :     }
     276          19 :     return (crc == 0x00ff);
     277             : }
     278             : 
     279         878 : bool AP_Frsky_SPortParser::process_byte(AP_Frsky_SPort::sport_packet_t &sport_packet, const uint8_t data)
     280             : {
     281         878 :     switch (_parse_state.state) {
     282          22 :     case ParseState::START:
     283          22 :         if (_parse_state.rx_buffer_count < TELEMETRY_RX_BUFFER_SIZE) {
     284          22 :             _parse_state.rx_buffer[_parse_state.rx_buffer_count++] = data;
     285             :         }
     286          22 :         _parse_state.state = ParseState::IN_FRAME;
     287          22 :         break;
     288             : 
     289         834 :     case ParseState::IN_FRAME:
     290         834 :         if (data == FRAME_DLE) {
     291           0 :             _parse_state.state = ParseState::XOR; // XOR next byte
     292         834 :         } else if (data == FRAME_HEAD) {
     293         341 :             _parse_state.state = ParseState::IN_FRAME ;
     294         341 :             _parse_state.rx_buffer_count = 0;
     295         341 :             break;
     296         493 :         } else if (_parse_state.rx_buffer_count < TELEMETRY_RX_BUFFER_SIZE) {
     297         493 :             _parse_state.rx_buffer[_parse_state.rx_buffer_count++] = data;
     298             :         }
     299         493 :         break;
     300             : 
     301           0 :     case ParseState::XOR:
     302           0 :         if (_parse_state.rx_buffer_count < TELEMETRY_RX_BUFFER_SIZE) {
     303           0 :             _parse_state.rx_buffer[_parse_state.rx_buffer_count++] = data ^ STUFF_MASK;
     304             :         }
     305           0 :         _parse_state.state = ParseState::IN_FRAME;
     306           0 :         break;
     307             : 
     308          22 :     case ParseState::IDLE:
     309          22 :         if (data == FRAME_HEAD) {
     310          22 :             _parse_state.rx_buffer_count = 0;
     311          22 :             _parse_state.state = ParseState::START;
     312             :         }
     313          22 :         break;
     314             : 
     315             :     } // switch
     316             : 
     317         878 :     if (_parse_state.rx_buffer_count >= SPORT_PACKET_SIZE) {
     318          19 :         _parse_state.rx_buffer_count = 0;
     319          19 :         _parse_state.state = ParseState::IDLE;
     320             :         // feed the packet only if it's not a duplicate
     321          19 :         return get_packet(sport_packet, true);
     322             :     }
     323         859 :     return false;
     324             : }
     325             : 
     326          19 : bool AP_Frsky_SPortParser::get_packet(AP_Frsky_SPort::sport_packet_t &sport_packet, bool discard_duplicates)
     327             : {
     328          19 :     if (!should_process_packet(_parse_state.rx_buffer, discard_duplicates)) {
     329           0 :         return false;
     330             :     }
     331             : 
     332             :     const AP_Frsky_SPort::sport_packet_t sp {
     333          19 :         { _parse_state.rx_buffer[0],
     334          19 :         _parse_state.rx_buffer[1],
     335          19 :         le16toh_ptr(&_parse_state.rx_buffer[2]),
     336          19 :         le32toh_ptr(&_parse_state.rx_buffer[4]) },
     337          19 :     };
     338             : 
     339          19 :     sport_packet = sp;
     340          19 :     return true;
     341             : }
     342             : 
     343             : /*
     344             :  * Calculates the sensor id from the physical sensor index [0-27]
     345             :         0x00,   // Physical ID 0 - Vario2 (altimeter high precision)
     346             :         0xA1,   // Physical ID 1 - FLVSS Lipo sensor
     347             :         0x22,   // Physical ID 2 - FAS-40S current sensor
     348             :         0x83,   // Physical ID 3 - GPS / altimeter (normal precision)
     349             :         0xE4,   // Physical ID 4 - RPM
     350             :         0x45,   // Physical ID 5 - SP2UART(Host)
     351             :         0xC6,   // Physical ID 6 - SPUART(Remote)
     352             :         0x67,   // Physical ID 7 - Ardupilot/Betaflight EXTRA DOWNLINK
     353             :         0x48,   // Physical ID 8 -
     354             :         0xE9,   // Physical ID 9 -
     355             :         0x6A,   // Physical ID 10 -
     356             :         0xCB,   // Physical ID 11 -
     357             :         0xAC,   // Physical ID 12 -
     358             :         0x0D,   // Physical ID 13 - Ardupilot/Betaflight UPLINK
     359             :         0x8E,   // Physical ID 14 -
     360             :         0x2F,   // Physical ID 15 -
     361             :         0xD0,   // Physical ID 16 -
     362             :         0x71,   // Physical ID 17 -
     363             :         0xF2,   // Physical ID 18 -
     364             :         0x53,   // Physical ID 19 -
     365             :         0x34,   // Physical ID 20 - Ardupilot/Betaflight EXTRA DOWNLINK
     366             :         0x95,   // Physical ID 21 -
     367             :         0x16,   // Physical ID 22 - GAS Suite
     368             :         0xB7,   // Physical ID 23 - IMU ACC (x,y,z)
     369             :         0x98,   // Physical ID 24 -
     370             :         0x39,   // Physical ID 25 - Power Box
     371             :         0xBA    // Physical ID 26 - Temp
     372             :         0x1B    // Physical ID 27 - ArduPilot/Betaflight DEFAULT DOWNLINK
     373             :  * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers)
     374             :  */
     375             : #undef BIT
     376             : #define BIT(x, index) (((x) >> index) & 0x01)
     377          12 : uint8_t AP_Frsky_SPort::calc_sensor_id(const uint8_t physical_id)
     378             : {
     379          12 :     uint8_t result = physical_id;
     380          12 :     result += (BIT(physical_id, 0) ^ BIT(physical_id, 1) ^ BIT(physical_id, 2)) << 5;
     381          12 :     result += (BIT(physical_id, 2) ^ BIT(physical_id, 3) ^ BIT(physical_id, 4)) << 6;
     382          12 :     result += (BIT(physical_id, 0) ^ BIT(physical_id, 2) ^ BIT(physical_id, 4)) << 7;
     383          12 :     return result;
     384             : }
     385             : 
     386             : /*
     387             :  * prepare value for transmission through FrSky link
     388             :  * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers)
     389             :  */
     390         262 : uint16_t AP_Frsky_SPort::prep_number(int32_t number, uint8_t digits, uint8_t power)
     391             : {
     392         262 :     uint16_t res = 0;
     393         262 :     uint32_t abs_number = abs(number);
     394             : 
     395         262 :     if ((digits == 2) && (power == 0)) { // number encoded on 7 bits, client side needs to know if expected range is 0,127 or -63,63
     396          41 :         uint8_t max_value = number < 0 ? (0x1<<6)-1 : (0x1<<7)-1;
     397          41 :         res = constrain_int16(abs_number,0,max_value);
     398          41 :         if (number < 0) {   // if number is negative, add sign bit in front
     399           0 :             res |= 1U<<6;
     400             :         }
     401         262 :     } else if ((digits == 2) && (power == 1)) { // number encoded on 8 bits: 7 bits for digits + 1 for 10^power
     402         104 :         if (abs_number < 100) {
     403         103 :             res = abs_number<<1;
     404           1 :         } else if (abs_number < 1270) {
     405           0 :             res = ((uint8_t)roundf(abs_number * 0.1f)<<1)|0x1;
     406             :         } else { // transmit max possible value (0x7F x 10^1 = 1270)
     407           1 :             res = 0xFF;
     408             :         }
     409         104 :         if (number < 0) { // if number is negative, add sign bit in front
     410           1 :             res |= 0x1<<8;
     411             :         }
     412         117 :     } else if ((digits == 2) && (power == 2)) { // number encoded on 9 bits: 7 bits for digits + 2 for 10^power
     413          20 :         if (abs_number < 100) {
     414           1 :             res = abs_number<<2;
     415          19 :         } else if (abs_number < 1000) {
     416           0 :             res = ((uint8_t)roundf(abs_number * 0.1f)<<2)|0x1;
     417          19 :         } else if (abs_number < 10000) {
     418          19 :             res = ((uint8_t)roundf(abs_number * 0.01f)<<2)|0x2;
     419           0 :         } else if (abs_number < 127000) {
     420           0 :             res = ((uint8_t)roundf(abs_number * 0.001f)<<2)|0x3;
     421             :         } else { // transmit max possible value (0x7F x 10^3 = 127000)
     422           0 :             res = 0x1FF;
     423             :         }
     424          20 :         if (number < 0) { // if number is negative, add sign bit in front
     425           0 :             res |= 0x1<<9;
     426             :         }
     427          97 :     } else if ((digits == 3) && (power == 1)) { // number encoded on 11 bits: 10 bits for digits + 1 for 10^power
     428          45 :         if (abs_number < 1000) {
     429          45 :             res = abs_number<<1;
     430           0 :         } else if (abs_number < 10240) {
     431           0 :             res = ((uint16_t)roundf(abs_number * 0.1f)<<1)|0x1;
     432             :         } else { // transmit max possible value (0x3FF x 10^1 = 10230)
     433           0 :             res = 0x7FF;
     434             :         }
     435          45 :         if (number < 0) { // if number is negative, add sign bit in front
     436           0 :             res |= 0x1<<11;
     437             :         }
     438          52 :     } else if ((digits == 3) && (power == 2)) { // number encoded on 12 bits: 10 bits for digits + 2 for 10^power
     439          52 :         if (abs_number < 1000) {
     440          52 :             res = abs_number<<2;
     441           0 :         } else if (abs_number < 10000) {
     442           0 :             res = ((uint16_t)roundf(abs_number * 0.1f)<<2)|0x1;
     443           0 :         } else if (abs_number < 100000) {
     444           0 :             res = ((uint16_t)roundf(abs_number * 0.01f)<<2)|0x2;
     445           0 :         } else if (abs_number < 1024000) {
     446           0 :             res = ((uint16_t)roundf(abs_number * 0.001f)<<2)|0x3;
     447             :         } else { // transmit max possible value (0x3FF x 10^3 = 1023000)
     448           0 :             res = 0xFFF;
     449             :         }
     450          52 :         if (number < 0) { // if number is negative, add sign bit in front
     451           5 :             res |= 0x1<<12;
     452             :         }
     453             :     }
     454         262 :     return res;
     455             : }
     456             : 
     457             : /*
     458             :  * Push user data down the telemetry link by responding to sensor polling (sport)
     459             :  * or by using dedicated slots in the scheduler (fport)
     460             :  * for SPort and FPort protocols (X-receivers)
     461             :  */
     462           0 : bool AP_Frsky_SPort::sport_telemetry_push(uint8_t sensor, uint8_t frame, uint16_t appid, int32_t data)
     463             : {
     464           0 :     WITH_SEMAPHORE(_sport_push_buffer.sem);
     465           0 :     if (_sport_push_buffer.pending) {
     466           0 :         return false;
     467             :     }
     468           0 :     _sport_push_buffer.packet.sensor = sensor;
     469           0 :     _sport_push_buffer.packet.frame = frame;
     470           0 :     _sport_push_buffer.packet.appid = appid;
     471           0 :     _sport_push_buffer.packet.data = static_cast<uint32_t>(data);
     472           0 :     _sport_push_buffer.pending = true;
     473           0 :     return true;
     474           0 : }
     475             : 
     476             : namespace AP {
     477           0 :     AP_Frsky_SPort *frsky_sport() {
     478           0 :         return AP_Frsky_SPort::get_singleton();
     479             :     }
     480             : };
     481             : 
     482             : #endif  // AP_FRSKY_SPORT_TELEM_ENABLED

Generated by: LCOV version 1.14