LCOV - code coverage report
Current view: top level - AP_IBus_Telem - AP_IBus_Telem.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 91 220 41.4 %
Date: 2026-09-09 11:02:08 Functions: 10 17 58.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             :    i-BUS telemetry for FlySky/Turnigy receivers and other peripherals
      17             :    (eg iA6B, iA10) by Nicole Ashley <[email protected]>.
      18             : 
      19             :    Originally based on work by Jan Verhulst:
      20             :      https://github.com/ArduPilot/ardupilot/pull/16545
      21             : 
      22             :    Libraries used for reference and inspiration:
      23             : 
      24             :    * iBUStelemetry
      25             :      https://github.com/Hrastovc/iBUStelemetry
      26             : 
      27             :    * IBusBM
      28             :      https://github.com/bmellink/IBusBM
      29             : 
      30             :    * BetaFlight
      31             :      https://github.com/betaflight/betaflight/blob/master/src/main/telemetry/ibus_shared.c
      32             :  */
      33             : 
      34             : #include <AP_IBus_Telem/AP_IBus_Telem.h>
      35             : 
      36             : #if AP_IBUS_TELEM_ENABLED
      37             : 
      38             : #include <AP_HAL/AP_HAL.h>
      39             : #include <AP_Vehicle/AP_Vehicle.h>
      40             : #include <AP_BattMonitor/AP_BattMonitor.h>
      41             : #include <AP_Arming/AP_Arming.h>
      42             : #include <AP_GPS/AP_GPS.h>
      43             : #include <AP_RPM/AP_RPM.h>
      44             : 
      45             : // 2-byte values
      46             : #define IBUS_SENSOR_TYPE_TEMPERATURE          0x01 // Temperature (in 0.1 degrees, where 0=-40'C)
      47             : #define IBUS_SENSOR_TYPE_RPM_FLYSKY           0x02 // FlySky-specific throttle value
      48             : #define IBUS_SENSOR_TYPE_EXTERNAL_VOLTAGE     0x03 // External voltage (in centivolts, so 1450 is 14.50V)
      49             : #define IBUS_SENSOR_TYPE_AVERAGE_CELL_VOLTAGE 0x04 // Avg cell voltage (in centivolts, so 1450 is 14.50V)
      50             : #define IBUS_SENSOR_TYPE_BATTERY_CURRENT      0x05 // Battery current (centi-amps)
      51             : #define IBUS_SENSOR_TYPE_FUEL                 0x06 // Remaining battery percentage
      52             : #define IBUS_SENSOR_TYPE_RPM                  0x07 // Throttle value (in 0.01, so 1200 is 12.00%)
      53             : #define IBUS_SENSOR_TYPE_COMPASS_HEADING      0x08 // Heading (0-360 degrees)
      54             : #define IBUS_SENSOR_TYPE_CLIMB_RATE           0x09 // Climb rate (cm/s)
      55             : #define IBUS_SENSOR_TYPE_COG                  0x0a // Course over ground (centidegrees, so 27015 is 270.15 degrees)
      56             : #define IBUS_SENSOR_TYPE_GPS_STATUS           0x0b // GPS status (2 values: fix type, and number of satellites)
      57             : #define IBUS_SENSOR_TYPE_ACC_X                0x0c // Acc X (cm/s)
      58             : #define IBUS_SENSOR_TYPE_ACC_Y                0x0d // Acc Y (cm/s)
      59             : #define IBUS_SENSOR_TYPE_ACC_Z                0x0e // Acc Z (cm/s)
      60             : #define IBUS_SENSOR_TYPE_ROLL                 0x0f // Roll (centidegrees)
      61             : #define IBUS_SENSOR_TYPE_PITCH                0x10 // Pitch (centidegrees)
      62             : #define IBUS_SENSOR_TYPE_YAW                  0x11 // Yaw (centidegrees)
      63             : #define IBUS_SENSOR_TYPE_VERTICAL_SPEED       0x12 // Vertical speed (cm/s)
      64             : #define IBUS_SENSOR_TYPE_GROUND_SPEED         0x13 // Speed (cm/s)
      65             : #define IBUS_SENSOR_TYPE_GPS_DIST             0x14 // Distance from home (m)
      66             : #define IBUS_SENSOR_TYPE_ARMED                0x15 // Armed / unarmed (1 = armed, 0 = unarmed)
      67             : #define IBUS_SENSOR_TYPE_FLIGHT_MODE          0x16 // Flight mode
      68             : #define IBUS_SENSOR_TYPE_ODO1                 0x7c // Odometer1
      69             : #define IBUS_SENSOR_TYPE_ODO2                 0x7d // Odometer2
      70             : #define IBUS_SENSOR_TYPE_SPEED                0x7e // Speed km/h
      71             : #define IBUS_SENSOR_TYPE_ALT_FLYSKY           0xf9 // FlySky-specific altitude (metres)
      72             : 
      73             : // 4-byte values
      74             : #define IBUS_SENSOR_TYPE_TEMPERATURE_PRESSURE 0x41 // Combined temperature & pressure value
      75             : #define IBUS_SENSOR_TYPE_GPS_LAT              0x80 // WGS84 in degrees * 1E7
      76             : #define IBUS_SENSOR_TYPE_GPS_LNG              0x81 // WGS84 in degrees * 1E7
      77             : #define IBUS_SENSOR_TYPE_GPS_ALT              0x82 // GPS (cm)
      78             : #define IBUS_SENSOR_TYPE_ALT                  0x83 // Alt (cm)
      79             : #define IBUS_SENSOR_TYPE_ALT_MAX              0x84 // MaxAlt (cm)
      80             : 
      81             : // i-BUS vehicle modes
      82             : #define IBUS_VEHICLE_MODE_STAB    0
      83             : #define IBUS_VEHICLE_MODE_ACRO    1
      84             : #define IBUS_VEHICLE_MODE_AHOLD   2
      85             : #define IBUS_VEHICLE_MODE_AUTO    3
      86             : #define IBUS_VEHICLE_MODE_GUIDED  4
      87             : #define IBUS_VEHICLE_MODE_LOITER  5
      88             : #define IBUS_VEHICLE_MODE_RTL     6
      89             : #define IBUS_VEHICLE_MODE_CIRCLE  7
      90             : #define IBUS_VEHICLE_MODE_PHOLD   8
      91             : #define IBUS_VEHICLE_MODE_LAND    9
      92             : #define IBUS_VEHICLE_MODE_UNKNOWN 255 // Must be positive and 0 is already used; out of range blanks the value
      93             : 
      94             : // All the sensors we can accurately provide are listed here.
      95             : // i-BUS will generally only query up to 15 sensors, so subjectively
      96             : // higher-value sensors are sorted to the top to make the most of a
      97             : // small telemetry window. In the future these could be configurable.
      98             : static const AP_IBus_Telem::SensorDefinition sensors[] {
      99             : #if AP_ARMING_ENABLED
     100             :     {.sensor_type = IBUS_SENSOR_TYPE_ARMED, .sensor_length = 2},
     101             : #endif
     102             :     {.sensor_type = IBUS_SENSOR_TYPE_FLIGHT_MODE, .sensor_length = 2},
     103             : #if AP_GPS_ENABLED
     104             :     {.sensor_type = IBUS_SENSOR_TYPE_GPS_STATUS, .sensor_length = 2},
     105             : #endif
     106             : #if AP_BATTERY_ENABLED
     107             :     {.sensor_type = IBUS_SENSOR_TYPE_FUEL, .sensor_length = 2},
     108             :     {.sensor_type = IBUS_SENSOR_TYPE_EXTERNAL_VOLTAGE, .sensor_length = 2},
     109             : #endif
     110             : #if AP_BARO_ENABLED
     111             :     {.sensor_type = IBUS_SENSOR_TYPE_ALT, .sensor_length = 4},
     112             : #endif
     113             : #if AP_AHRS_ENABLED
     114             :     {.sensor_type = IBUS_SENSOR_TYPE_GPS_DIST, .sensor_length = 2},
     115             : #endif
     116             : #if AP_BARO_ENABLED
     117             :     {.sensor_type = IBUS_SENSOR_TYPE_CLIMB_RATE, .sensor_length = 2},
     118             : #endif
     119             : #if AP_GPS_ENABLED
     120             :     {.sensor_type = IBUS_SENSOR_TYPE_GROUND_SPEED, .sensor_length = 2},
     121             : #endif
     122             : #if AP_AHRS_ENABLED
     123             :     {.sensor_type = IBUS_SENSOR_TYPE_ROLL, .sensor_length = 2},
     124             :     {.sensor_type = IBUS_SENSOR_TYPE_PITCH, .sensor_length = 2},
     125             :     {.sensor_type = IBUS_SENSOR_TYPE_YAW, .sensor_length = 2},
     126             : #endif
     127             : #if AP_AIRSPEED_ENABLED
     128             :     {.sensor_type = IBUS_SENSOR_TYPE_SPEED, .sensor_length = 2},
     129             : #endif
     130             : #if AP_BARO_ENABLED
     131             :     {.sensor_type = IBUS_SENSOR_TYPE_TEMPERATURE_PRESSURE, .sensor_length = 4},
     132             : #endif
     133             : #if AP_RPM_ENABLED
     134             :     {.sensor_type = IBUS_SENSOR_TYPE_RPM, .sensor_length = 2},
     135             : #endif
     136             : #if AP_BATTERY_ENABLED
     137             :     {.sensor_type = IBUS_SENSOR_TYPE_BATTERY_CURRENT, .sensor_length = 2},
     138             :     {.sensor_type = IBUS_SENSOR_TYPE_AVERAGE_CELL_VOLTAGE, .sensor_length = 2},
     139             : #endif
     140             : #if AP_AHRS_ENABLED
     141             :     {.sensor_type = IBUS_SENSOR_TYPE_COMPASS_HEADING, .sensor_length = 2},
     142             : #endif
     143             : #if AP_GPS_ENABLED
     144             :     {.sensor_type = IBUS_SENSOR_TYPE_COG, .sensor_length = 2},
     145             :     {.sensor_type = IBUS_SENSOR_TYPE_GPS_LAT, .sensor_length = 4},
     146             :     {.sensor_type = IBUS_SENSOR_TYPE_GPS_LNG, .sensor_length = 4},
     147             :     {.sensor_type = IBUS_SENSOR_TYPE_GPS_ALT, .sensor_length = 4},
     148             : #endif
     149             : #if AP_AHRS_ENABLED
     150             :     {.sensor_type = IBUS_SENSOR_TYPE_ACC_X, .sensor_length = 2},
     151             :     {.sensor_type = IBUS_SENSOR_TYPE_ACC_Y, .sensor_length = 2},
     152             :     {.sensor_type = IBUS_SENSOR_TYPE_ACC_Z, .sensor_length = 2},
     153             : #endif
     154             : };
     155             : 
     156             : #if APM_BUILD_TYPE(APM_BUILD_Rover)
     157             : 
     158             : /* Rover modes:
     159             :     MANUAL       = 0
     160             :     ACRO         = 1
     161             :     STEERING     = 3
     162             :     HOLD         = 4
     163             :     LOITER       = 5
     164             :     FOLLOW       = 6
     165             :     SIMPLE       = 7
     166             :     DOCK         = 8
     167             :     CIRCLE       = 9
     168             :     AUTO         = 10
     169             :     RTL          = 11
     170             :     SMART_RTL    = 12
     171             :     GUIDED       = 15
     172             :     INITIALISING = 16
     173             : */
     174             : static const AP_IBus_Telem::ModeMap mode_map[] {
     175             :     {.ap_mode = 1, .ibus_mode = IBUS_VEHICLE_MODE_ACRO},
     176             :     {.ap_mode = 4, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     177             :     {.ap_mode = 5, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     178             :     {.ap_mode = 9, .ibus_mode = IBUS_VEHICLE_MODE_CIRCLE},
     179             :     {.ap_mode = 10, .ibus_mode = IBUS_VEHICLE_MODE_AUTO},
     180             :     {.ap_mode = 11, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     181             :     {.ap_mode = 12, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     182             :     {.ap_mode = 15, .ibus_mode = IBUS_VEHICLE_MODE_GUIDED},
     183             : };
     184             : 
     185             : #elif APM_BUILD_COPTER_OR_HELI
     186             : 
     187             : /* Copter modes:
     188             :     STABILIZE    =  0
     189             :     ACRO         =  1
     190             :     ALT_HOLD     =  2
     191             :     AUTO         =  3
     192             :     GUIDED       =  4
     193             :     LOITER       =  5
     194             :     RTL          =  6
     195             :     CIRCLE       =  7
     196             :     LAND         =  9
     197             :     DRIFT        = 11
     198             :     SPORT        = 13
     199             :     FLIP         = 14
     200             :     AUTOTUNE     = 15
     201             :     POSHOLD      = 16
     202             :     BRAKE        = 17
     203             :     THROW        = 18
     204             :     AVOID_ADSB   = 19
     205             :     GUIDED_NOGPS = 20
     206             :     SMART_RTL    = 21
     207             :     FLOWHOLD     = 22
     208             :     FOLLOW       = 23
     209             :     ZIGZAG       = 24
     210             :     SYSTEMID     = 25
     211             :     AUTOROTATE   = 26
     212             :     AUTO_RTL     = 27
     213             :     TURTLE       = 28
     214             : */
     215             : static const AP_IBus_Telem::ModeMap mode_map[] {
     216             :     {.ap_mode = 0, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     217             :     {.ap_mode = 1, .ibus_mode = IBUS_VEHICLE_MODE_ACRO},
     218             :     {.ap_mode = 2, .ibus_mode = IBUS_VEHICLE_MODE_AHOLD},
     219             :     {.ap_mode = 3, .ibus_mode = IBUS_VEHICLE_MODE_AUTO},
     220             :     {.ap_mode = 4, .ibus_mode = IBUS_VEHICLE_MODE_GUIDED},
     221             :     {.ap_mode = 5, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     222             :     {.ap_mode = 6, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     223             :     {.ap_mode = 7, .ibus_mode = IBUS_VEHICLE_MODE_CIRCLE},
     224             :     {.ap_mode = 9, .ibus_mode = IBUS_VEHICLE_MODE_LAND},
     225             :     {.ap_mode = 16, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     226             :     {.ap_mode = 21, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     227             :     {.ap_mode = 22, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     228             :     {.ap_mode = 27, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     229             : };
     230             : 
     231             : #elif APM_BUILD_TYPE(APM_BUILD_ArduPlane)
     232             : 
     233             : /* Plane modes:
     234             :     MANUAL        = 0
     235             :     CIRCLE        = 1
     236             :     STABILIZE     = 2
     237             :     TRAINING      = 3
     238             :     ACRO          = 4
     239             :     FLY_BY_WIRE_A = 5
     240             :     FLY_BY_WIRE_B = 6
     241             :     CRUISE        = 7
     242             :     AUTOTUNE      = 8
     243             :     AUTO          = 10
     244             :     RTL           = 11
     245             :     LOITER        = 12
     246             :     TAKEOFF       = 13
     247             :     AVOID_ADSB    = 14
     248             :     GUIDED        = 15
     249             :     INITIALISING  = 16
     250             :     QSTABILIZE    = 17
     251             :     QHOVER        = 18
     252             :     QLOITER       = 19
     253             :     QLAND         = 20
     254             :     QRTL          = 21
     255             :     QAUTOTUNE     = 22
     256             :     QACRO         = 23
     257             :     THERMAL       = 24
     258             :     LOITER_ALT_QLAND = 25
     259             : */
     260             : static const AP_IBus_Telem::ModeMap mode_map[] {
     261             :     {.ap_mode = 1, .ibus_mode = IBUS_VEHICLE_MODE_CIRCLE},
     262             :     {.ap_mode = 2, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     263             :     {.ap_mode = 4, .ibus_mode = IBUS_VEHICLE_MODE_ACRO},
     264             :     {.ap_mode = 5, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     265             :     {.ap_mode = 6, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     266             :     {.ap_mode = 7, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     267             :     {.ap_mode = 10, .ibus_mode = IBUS_VEHICLE_MODE_AUTO},
     268             :     {.ap_mode = 11, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     269             :     {.ap_mode = 12, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     270             :     {.ap_mode = 13, .ibus_mode = IBUS_VEHICLE_MODE_AUTO},
     271             :     {.ap_mode = 15, .ibus_mode = IBUS_VEHICLE_MODE_GUIDED},
     272             :     {.ap_mode = 17, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     273             :     {.ap_mode = 18, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     274             :     {.ap_mode = 19, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     275             :     {.ap_mode = 20, .ibus_mode = IBUS_VEHICLE_MODE_LAND},
     276             :     {.ap_mode = 21, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     277             :     {.ap_mode = 23, .ibus_mode = IBUS_VEHICLE_MODE_ACRO},
     278             :     {.ap_mode = 25, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     279             : };
     280             : 
     281             : #elif APM_BUILD_TYPE(APM_BUILD_ArduSub)
     282             : 
     283             : /* Submarine modes:
     284             :     STABILIZE    = 0
     285             :     ACRO         = 1
     286             :     ALT_HOLD     = 2
     287             :     AUTO         = 3
     288             :     GUIDED       = 4
     289             :     CIRCLE       = 7
     290             :     SURFACE      = 9
     291             :     POSHOLD      = 16
     292             :     MANUAL       = 19
     293             :     MOTOR_DETECT = 20
     294             : */
     295             : static const AP_IBus_Telem::ModeMap mode_map[] {
     296             :     {.ap_mode = 0, .ibus_mode = IBUS_VEHICLE_MODE_STAB},
     297             :     {.ap_mode = 1, .ibus_mode = IBUS_VEHICLE_MODE_ACRO},
     298             :     {.ap_mode = 2, .ibus_mode = IBUS_VEHICLE_MODE_AHOLD},
     299             :     {.ap_mode = 3, .ibus_mode = IBUS_VEHICLE_MODE_AUTO},
     300             :     {.ap_mode = 4, .ibus_mode = IBUS_VEHICLE_MODE_GUIDED},
     301             :     {.ap_mode = 5, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     302             :     {.ap_mode = 6, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     303             :     {.ap_mode = 7, .ibus_mode = IBUS_VEHICLE_MODE_CIRCLE},
     304             :     {.ap_mode = 8, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     305             :     {.ap_mode = 9, .ibus_mode = IBUS_VEHICLE_MODE_LAND},
     306             :     {.ap_mode = 16, .ibus_mode = IBUS_VEHICLE_MODE_PHOLD},
     307             : };
     308             : 
     309             : #elif APM_BUILD_TYPE(APM_BUILD_Blimp)
     310             : 
     311             : /* Blimp modes:
     312             :     LAND     = 0
     313             :     MANUAL   = 1
     314             :     VELOCITY = 2
     315             :     LOITER   = 3
     316             :     RTL      = 4
     317             : */
     318             : static const AP_IBus_Telem::ModeMap mode_map[] {
     319             :     {.ap_mode = 0, .ibus_mode = IBUS_VEHICLE_MODE_LAND},
     320             :     {.ap_mode = 3, .ibus_mode = IBUS_VEHICLE_MODE_LOITER},
     321             :     {.ap_mode = 4, .ibus_mode = IBUS_VEHICLE_MODE_RTL},
     322             : };
     323             : 
     324             : #else
     325             : 
     326             : static const AP_IBus_Telem::ModeMap mode_map[] {};
     327             : 
     328             : #endif
     329             : 
     330        2086 : void AP_IBus_Telem::init()
     331             : {
     332        2086 :     const AP_SerialManager &serial_manager = AP::serialmanager();
     333        2086 :     if ((port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_IBUS_Telem, 0))) {
     334           1 :         port->set_options(port->OPTION_HDPLEX);
     335           1 :         hal.scheduler->register_timer_process(FUNCTOR_BIND_MEMBER(&AP_IBus_Telem::tick, void));
     336             :     }
     337        2086 : }
     338             : 
     339        7916 : void AP_IBus_Telem::tick(void)
     340             : {
     341        7916 :     if (!initialized) {
     342           1 :         port->begin(115200);
     343           1 :         initialized = true;
     344             :     }
     345             : 
     346        7916 :     const uint16_t available = MIN(port->available(), 1024U);
     347        7916 :     if (available == 0) {
     348        7907 :         return;
     349             :     }
     350             : 
     351           9 :     const uint32_t now = AP_HAL::millis();
     352           9 :     if ((now - last_received_message_time_ms) < PROTOCOL_TIMEGAP) {
     353             :         // Any bytes that arrive too soon since the last message should be discarded
     354           0 :         port->discard_input();
     355           0 :         return;
     356             :     }
     357             : 
     358             :     union {
     359             :         CommandPacket incoming_message;
     360             :         uint8_t buffer[sizeof(CommandPacket)];
     361             :     } u;
     362             : 
     363           9 :     if (available < sizeof(u.incoming_message)) {
     364             :         // We've got here mid-message; come back in the next loop when it might be complete
     365           0 :         return;
     366             :     }
     367             : 
     368           9 :     last_received_message_time_ms = now;
     369             : 
     370           9 :     if (available > sizeof(u.incoming_message)) {
     371             :         // We've received too many bytes, so probably a malformed message
     372           0 :         port->discard_input();
     373           0 :         return;
     374             :     }
     375             : 
     376           9 :     if (port->read(u.buffer, sizeof(u.buffer)) != sizeof(u.buffer)) {
     377             :         // Despite all our checks this is still the wrong size; safest to wait until the next message
     378           0 :         port->discard_input();
     379           0 :         return;
     380             :     }
     381             : 
     382           9 :     if (u.incoming_message.message_length != PROTOCOL_INCOMING_MESSAGE_LENGTH) {
     383             :         // Every message starts with this fixed message length; if not then it's not valid
     384           0 :         return;
     385             :     }
     386             : 
     387           9 :     uint16_t calculated_checksum = 0xFFFF;
     388          27 :     for (uint8_t i = 0; i < sizeof(u.buffer) - 2; i++) {
     389          18 :         calculated_checksum -= u.buffer[i];
     390             :     }
     391             : 
     392           9 :     if (u.incoming_message.checksum != calculated_checksum) {
     393           0 :         return;
     394             :     }
     395             : 
     396           9 :     handle_incoming_message(u.incoming_message);
     397             : }
     398             : 
     399           9 : void AP_IBus_Telem::handle_incoming_message(const CommandPacket &incoming)
     400             : {
     401             :     // Sensor 0 is reserved for the transmitter, so if for some reason it's requested we should ignore it
     402           9 :     if (incoming.sensor_id == 0) {
     403           0 :         return;
     404             :     }
     405             : 
     406             :     // If we've reached the end of our sensor list, we shouldn't respond; this tells the receiver that there
     407             :     // are no more sensors to discover.
     408             :     if (incoming.sensor_id > ARRAY_SIZE(sensors)) {
     409             :         return;
     410             :     }
     411             : 
     412           9 :     const auto &sensor_definition = sensors[incoming.sensor_id - 1];
     413             : 
     414           9 :     switch (incoming.command << 4) {
     415           3 :     case PROTOCOL_COMMAND_DISCOVER:
     416           3 :         handle_discover_command(incoming);
     417           3 :         break;
     418             : 
     419           3 :     case PROTOCOL_COMMAND_TYPE:
     420           3 :         handle_type_command(incoming, sensor_definition);
     421           3 :         break;
     422             : 
     423           3 :     case PROTOCOL_COMMAND_VALUE:
     424           3 :         handle_value_command(incoming, sensor_definition);
     425           3 :         break;
     426             :     }
     427             : }
     428             : 
     429             : /* A discovery query has the following format:
     430             :    * 0x04: Message length
     431             :    * 0x81: 0x80 for discovery + 0x01 for sensor ID 1
     432             :    * 0x7A: Checksum low byte
     433             :    * 0xFF: Checksum high byte
     434             :    Checksums are 0xFFFF minus the sum of the previous bytes
     435             : 
     436             :    To acknowledge a discovery query, we echo the command back.
     437             :  */
     438           3 : void AP_IBus_Telem::handle_discover_command(const CommandPacket &incoming)
     439             : {
     440             :     struct protocol_command_discover_response_t {
     441             :         uint8_t command_length;
     442             :         uint8_t address;
     443             :         uint16_t checksum;
     444           3 :     } packet {
     445             :         PROTOCOL_FOUR_LENGTH,
     446           3 :         (uint8_t)(PROTOCOL_COMMAND_DISCOVER | incoming.sensor_id)
     447           3 :     };
     448           3 :     populate_checksum((uint8_t*)&packet, sizeof(packet));
     449           3 :     port->write((uint8_t*)&packet, sizeof(packet));
     450           3 : }
     451             : 
     452             : /* A type query has the following format:
     453             :    * 0x04: Message length
     454             :    * 0x91: 0x90 for type + 0x01 for sensor ID 1
     455             :    * 0x6A: Checksum low byte
     456             :    * 0xFF: Checksum high byte
     457             :    Checksums are 0xFFFF minus the sum of the previous bytes
     458             : 
     459             :    To respond to a type query, we send:
     460             :    * 0x06: Message length
     461             :    * 0x91: 0x90 for type + 0x01 for sensor ID 1
     462             :    * 0x03: Sensor type, eg 0x03 for external voltage
     463             :    * 0x02: Sensor length (2 or 4 bytes)
     464             :    * 0x63: Checksum low byte
     465             :    * 0xFF: Checksum high byte
     466             :    Checksums are 0xFFFF minus the sum of the previous bytes
     467             :  */
     468           3 : void AP_IBus_Telem::handle_type_command(const CommandPacket &incoming, const SensorDefinition &sensor)
     469             : {
     470             :     struct protocol_command_type_response_t {
     471             :         uint8_t command_length;
     472             :         uint8_t address;
     473             :         uint8_t type;
     474             :         uint8_t length;
     475             :         uint16_t checksum;
     476           3 :     } packet {
     477             :         PROTOCOL_SIX_LENGTH,
     478           3 :         (uint8_t)(PROTOCOL_COMMAND_TYPE | incoming.sensor_id),
     479           3 :         sensor.sensor_type,
     480           3 :         sensor.sensor_length
     481           3 :     };
     482           3 :     populate_checksum((uint8_t*)&packet, sizeof(packet));
     483           3 :     port->write((uint8_t*)&packet, sizeof(packet));
     484           3 : }
     485             : 
     486             : /* A value query has the following format:
     487             :    * 0x04: Message length
     488             :    * 0xA1: 0xA0 for value + 0x01 for sensor ID 1
     489             :    * 0x5A: Checksum low byte
     490             :    * 0xFF: Checksum high byte
     491             :    Checksums are 0xFFFF minus the sum of the previous bytes
     492             : 
     493             :    To respond to a value query, we send:
     494             :    * 0x06: Message length (or 0x08 for a 4-byte sensor)
     495             :    * 0xA1: 0xA1 for value + 0x01 for sensor ID 1
     496             :    * 0xD4: Value byte (value is 12,500 in this example)
     497             :    * 0x30: Value byte
     498             :    * 0x54: Checksum low byte
     499             :    * 0xFE: Checksum high byte
     500             :    Checksums are 0xFFFF minus the sum of the previous bytes
     501             :  */
     502           3 : void AP_IBus_Telem::handle_value_command(const CommandPacket &incoming, const SensorDefinition &sensor)
     503             : {
     504           3 :     const SensorValue value = get_sensor_value(sensor.sensor_type);
     505           3 :     if (sensor.sensor_length == 2) {
     506           3 :         handle_2_byte_value_command(incoming, value);
     507             :     } else {
     508           0 :         handle_4_byte_value_command(incoming, value);
     509             :     }
     510           3 : }
     511             : 
     512           3 : void AP_IBus_Telem::handle_2_byte_value_command(const CommandPacket &incoming, const SensorValue &value)
     513             : {
     514             :     struct protocol_command_2byte_value_response_t {
     515             :         uint8_t command_length;
     516             :         uint8_t address;
     517             :         uint8_t value[2];
     518             :         uint16_t checksum;
     519           3 :     } packet {
     520             :         PROTOCOL_SIX_LENGTH,
     521           3 :         (uint8_t)(PROTOCOL_COMMAND_VALUE | incoming.sensor_id),
     522           3 :         {value.byte[0], value.byte[1]}
     523           3 :     };
     524           3 :     populate_checksum((uint8_t*)&packet, sizeof(packet));
     525           3 :     port->write((uint8_t*)&packet, sizeof(packet));
     526           3 : }
     527             : 
     528           0 : void AP_IBus_Telem::handle_4_byte_value_command(const CommandPacket &incoming, const SensorValue &value)
     529             : {
     530             :     struct protocol_command_4byte_value_response_t {
     531             :         uint8_t command_length;
     532             :         uint8_t address;
     533             :         uint8_t value[4];
     534             :         uint16_t checksum;
     535           0 :     } packet {
     536             :         PROTOCOL_EIGHT_LENGTH,
     537           0 :         (uint8_t)(PROTOCOL_COMMAND_VALUE | incoming.sensor_id),
     538           0 :         {value.byte[0], value.byte[1], value.byte[2], value.byte[3]}
     539           0 :     };
     540           0 :     populate_checksum((uint8_t*)&packet, sizeof(packet));
     541           0 :     port->write((uint8_t*)&packet, sizeof(packet));
     542           0 : }
     543             : 
     544           3 : AP_IBus_Telem::SensorValue AP_IBus_Telem::get_sensor_value(const uint8_t sensor_type)
     545             : {
     546             :     SensorValue value;
     547           3 :     switch (sensor_type) {
     548             : #if AP_BATTERY_ENABLED
     549           1 :     case IBUS_SENSOR_TYPE_EXTERNAL_VOLTAGE:
     550           1 :         value.uint16 = AP::battery().voltage() * 100;
     551           1 :         break;
     552             : 
     553           0 :     case IBUS_SENSOR_TYPE_AVERAGE_CELL_VOLTAGE:
     554           0 :         value.uint16 = get_average_cell_voltage_cV();
     555           0 :         break;
     556             : 
     557           0 :     case IBUS_SENSOR_TYPE_BATTERY_CURRENT:
     558           0 :         value.uint16 = get_current_cAh();
     559           0 :         break;
     560             : 
     561           0 :     case IBUS_SENSOR_TYPE_FUEL:
     562           0 :         value.uint16 = get_battery_or_fuel_level_pct();
     563           0 :         break;
     564             : #endif // AP_BATTERY_ENABLED
     565             : 
     566             : #if AP_RPM_ENABLED
     567           0 :     case IBUS_SENSOR_TYPE_RPM:
     568             :     case IBUS_SENSOR_TYPE_RPM_FLYSKY:
     569           0 :         value.uint16 = get_rpm();
     570           0 :         break;
     571             : #endif
     572             : 
     573             : #if AP_AHRS_ENABLED
     574           0 :     case IBUS_SENSOR_TYPE_COMPASS_HEADING:
     575           0 :         value.uint16 = uint16_t(AP::ahrs().get_yaw_deg());
     576           0 :         break;
     577             : #endif
     578             : 
     579             : #if AP_BARO_ENABLED
     580           0 :     case IBUS_SENSOR_TYPE_CLIMB_RATE:
     581             :     case IBUS_SENSOR_TYPE_VERTICAL_SPEED:
     582           0 :         value.int16 = AP::baro().get_climb_rate() * 100;
     583           0 :         break;
     584             : #endif
     585             : 
     586             : #if AP_GPS_ENABLED
     587           0 :     case IBUS_SENSOR_TYPE_COG:
     588           0 :         value.uint16 = AP::gps().ground_course() * 100;
     589           0 :         break;
     590             : 
     591           0 :     case IBUS_SENSOR_TYPE_GPS_STATUS:
     592           0 :         value.byte[0] = get_gps_status();
     593           0 :         value.byte[1] = AP::gps().num_sats();
     594           0 :         break;
     595             : #endif // AP_GPS_ENABLED
     596             : 
     597             : #if AP_AHRS_ENABLED
     598           0 :     case IBUS_SENSOR_TYPE_ACC_X:
     599           0 :         value.int16 = (AP::ahrs().get_accel() - AP::ahrs().get_accel_bias()).x * 1000;
     600           0 :         break;
     601             : 
     602           0 :     case IBUS_SENSOR_TYPE_ACC_Y:
     603           0 :         value.int16 = (AP::ahrs().get_accel() - AP::ahrs().get_accel_bias()).y * 1000;
     604           0 :         break;
     605             : 
     606           0 :     case IBUS_SENSOR_TYPE_ACC_Z:
     607           0 :         value.int16 = (AP::ahrs().get_accel() - AP::ahrs().get_accel_bias()).z * 1000;
     608           0 :         break;
     609             : 
     610           0 :     case IBUS_SENSOR_TYPE_ROLL:
     611           0 :         value.int16 = AP::ahrs().roll_sensor;
     612           0 :         break;
     613             : 
     614           0 :     case IBUS_SENSOR_TYPE_PITCH:
     615           0 :         value.int16 = AP::ahrs().pitch_sensor;
     616           0 :         break;
     617             : 
     618           0 :     case IBUS_SENSOR_TYPE_YAW:
     619           0 :         value.int16 = AP::ahrs().yaw_sensor;
     620           0 :         break;
     621             : #endif // AP_AHRS_ENABLED
     622             : 
     623             : #if AP_GPS_ENABLED
     624           0 :     case IBUS_SENSOR_TYPE_GROUND_SPEED:
     625           0 :         value.uint16 = AP::gps().ground_speed()*100;
     626           0 :         break;
     627             : #endif
     628             : 
     629             : #if AP_AHRS_ENABLED
     630           0 :     case IBUS_SENSOR_TYPE_GPS_DIST:
     631           0 :         value.uint16 = get_distance_from_home_m();
     632           0 :         break;
     633             : #endif
     634             : 
     635             : #if AP_ARMING_ENABLED
     636           1 :     case IBUS_SENSOR_TYPE_ARMED:
     637           1 :         value.uint16 = AP::arming().is_armed();
     638           1 :         break;
     639             : #endif
     640             : 
     641           1 :     case IBUS_SENSOR_TYPE_FLIGHT_MODE:
     642           1 :         value.uint16 = get_vehicle_mode();
     643           1 :         break;
     644             : 
     645             : #if AP_AIRSPEED_ENABLED
     646           0 :     case IBUS_SENSOR_TYPE_SPEED:
     647           0 :         value.uint16 = AP::airspeed()->get_airspeed();
     648           0 :         break;
     649             : #endif
     650             : 
     651             : #if AP_BARO_ENABLED
     652           0 :     case IBUS_SENSOR_TYPE_TEMPERATURE_PRESSURE: {
     653           0 :         const uint32_t pressure = AP::baro().get_pressure();
     654           0 :         const uint32_t temperature = (AP::baro().get_temperature() + 40) * 10;
     655           0 :         value.uint32 = pressure | (temperature << 19);
     656           0 :         break;
     657             :     }
     658             : #endif
     659             : 
     660             : #if AP_GPS_ENABLED
     661           0 :     case IBUS_SENSOR_TYPE_GPS_LAT:
     662           0 :         value.int32 = AP::gps().location().lat;
     663           0 :         break;
     664             : 
     665           0 :     case IBUS_SENSOR_TYPE_GPS_LNG:
     666           0 :         value.int32 = AP::gps().location().lng;
     667           0 :         break;
     668             : 
     669           0 :     case IBUS_SENSOR_TYPE_GPS_ALT:
     670           0 :         value.int32 = AP::gps().location().alt;
     671           0 :         break;
     672             : #endif // AP_GPS_ENABLED
     673             : 
     674             : #if AP_BARO_ENABLED
     675           0 :     case IBUS_SENSOR_TYPE_ALT:
     676           0 :         value.int32 = AP::baro().get_altitude() * 100;
     677           0 :         break;
     678             : #endif
     679             : 
     680           0 :     default:
     681           0 :         value.int32 = 0;
     682             :     }
     683             : 
     684           3 :     return value;
     685             : }
     686             : 
     687             : #if AP_BATTERY_ENABLED
     688           0 : uint16_t AP_IBus_Telem::get_average_cell_voltage_cV()
     689             : {
     690           0 :     if (!AP::battery().has_cell_voltages()) {
     691           0 :         return 0;
     692             :     }
     693             : 
     694           0 :     const auto &cell_voltages = AP::battery().get_cell_voltages();
     695           0 :     const uint8_t number_of_cells = ARRAY_SIZE(cell_voltages.cells);
     696             :     if (number_of_cells == 0) {
     697             :         return 0;
     698             :     }
     699             : 
     700           0 :     uint32_t voltage_sum = 0;
     701           0 :     for (auto i = 0; i < number_of_cells; i++) {
     702           0 :         voltage_sum += cell_voltages.cells[i] * 0.001;
     703             :     }
     704           0 :     return voltage_sum / number_of_cells * 100;
     705             : 
     706             : }
     707             : 
     708           0 : uint16_t AP_IBus_Telem::get_current_cAh()
     709             : {
     710           0 :     float current = 0;
     711           0 :     IGNORE_RETURN(AP::battery().current_amps(current));
     712           0 :     return current * 100;
     713             : }
     714             : 
     715           0 : uint8_t AP_IBus_Telem::get_battery_or_fuel_level_pct()
     716             : {
     717           0 :     uint8_t percentage = 0;
     718           0 :     IGNORE_RETURN(AP::battery().capacity_remaining_pct(percentage));
     719           0 :     return percentage;
     720             : }
     721             : #endif // AP_BATTERY_ENABLED
     722             : 
     723             : #if AP_RPM_ENABLED
     724           0 : uint16_t AP_IBus_Telem::get_rpm()
     725             : {
     726           0 :     const AP_RPM *rpm = AP::rpm();
     727             :     float rpm_value;
     728           0 :     if (rpm && rpm->get_rpm(0, rpm_value)) {
     729           0 :         return rpm_value;
     730             :     }
     731             : 
     732           0 :     return 0;
     733             : }
     734             : #endif
     735             : 
     736             : #if AP_GPS_ENABLED
     737           0 : uint8_t AP_IBus_Telem::get_gps_status()
     738             : {
     739           0 :     if (!AP::gps().is_healthy()) {
     740           0 :         return 0;
     741             :     }
     742             : 
     743           0 :     const AP_GPS_FixType gps_status = AP::gps().status();
     744           0 :     if (gps_status >= AP_GPS_FixType::FIX_3D) {
     745           0 :         return 3;
     746           0 :     } else if (gps_status >= AP_GPS_FixType::FIX_2D) {
     747           0 :         return 2;
     748           0 :     } else if (gps_status == AP_GPS_FixType::NONE) {
     749           0 :         return 1;
     750             :     } else {
     751           0 :         return 0;
     752             :     }
     753             : }
     754             : #endif // AP_GPS_ENABLED
     755             : 
     756             : #if AP_AHRS_ENABLED
     757           0 : uint16_t AP_IBus_Telem::get_distance_from_home_m()
     758             : {
     759           0 :     Vector2f home;
     760           0 :     if (AP::ahrs().get_relative_position_NE_home(home)) {
     761           0 :         return home.length();
     762             :     }
     763             : 
     764           0 :     return 0;
     765             : }
     766             : #endif
     767             : 
     768           1 : uint16_t AP_IBus_Telem::get_vehicle_mode()
     769             : {
     770           1 :     const uint8_t vehicle_mode = AP::vehicle()->get_mode();
     771           1 :     for (uint8_t i = 0; i < ARRAY_SIZE(mode_map); i++) {
     772           1 :         if (mode_map[i].ap_mode == vehicle_mode) {
     773           1 :             return mode_map[i].ibus_mode;
     774             :         }
     775             :     }
     776           0 :     return IBUS_VEHICLE_MODE_UNKNOWN;
     777             : }
     778             : 
     779             : // Populate the last two bytes of packet with the checksum of the preceding bytes
     780           9 : void AP_IBus_Telem::populate_checksum(uint8_t *packet, const uint16_t size)
     781             : {
     782           9 :     uint16_t checksum = 0xFFFF;
     783             : 
     784          39 :     for (int i=0; i<size-2; i++) {
     785          30 :         checksum -= packet[i];
     786             :     }
     787             : 
     788           9 :     packet[size-2] = checksum & 0x0ff;
     789           9 :     packet[size-1] = checksum >> 8;
     790           9 : }
     791             : 
     792             : #endif

Generated by: LCOV version 1.14