LCOV - code coverage report
Current view: top level - AP_BattMonitor - AP_BattMonitor_DroneCAN.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 5 219 2.3 %
Date: 2026-09-09 11:02:08 Functions: 1 20 5.0 %

          Line data    Source code
       1             : #include "AP_BattMonitor_config.h"
       2             : 
       3             : #if AP_BATTERY_UAVCAN_BATTERYINFO_ENABLED
       4             : 
       5             : #include <AP_HAL/AP_HAL.h>
       6             : #include "AP_BattMonitor.h"
       7             : #include "AP_BattMonitor_DroneCAN.h"
       8             : 
       9             : #include <AP_CANManager/AP_CANManager.h>
      10             : #include <AP_Common/AP_Common.h>
      11             : #include <GCS_MAVLink/GCS.h>
      12             : #include <AP_Math/AP_Math.h>
      13             : #include <AP_DroneCAN/AP_DroneCAN.h>
      14             : #include <AP_BoardConfig/AP_BoardConfig.h>
      15             : 
      16             : extern const AP_HAL::HAL& hal;
      17             : 
      18             : const AP_Param::GroupInfo AP_BattMonitor_DroneCAN::var_info[] = {
      19             : 
      20             :     // @Param: CURR_MULT
      21             :     // @DisplayName: Scales reported power monitor current
      22             :     // @Description: Multiplier applied to all current related reports to allow for adjustment if no UAVCAN param access or current splitting applications
      23             :     // @Range: 0.1 10
      24             :     // @User: Advanced
      25             :     AP_GROUPINFO("CURR_MULT", 30, AP_BattMonitor_DroneCAN, _curr_mult, 1.0),
      26             : 
      27             :     // CHECK/UPDATE INDEX TABLE IN AP_BattMonitor_Backend.cpp WHEN CHANGING OR ADDING PARAMETERS
      28             : 
      29             :     AP_GROUPEND
      30             : };
      31             : 
      32             : /// Constructor
      33           0 : AP_BattMonitor_DroneCAN::AP_BattMonitor_DroneCAN(AP_BattMonitor &mon, AP_BattMonitor::BattMonitor_State &mon_state, BattMonitor_DroneCAN_Type type, AP_BattMonitor_Params &params) :
      34           0 :     AP_BattMonitor_Backend(mon, mon_state, params)
      35             : {
      36           0 :     AP_Param::setup_object_defaults(this,var_info);
      37           0 :     _state.var_info = var_info;
      38             : 
      39             :     // starts with not healthy
      40           0 :     _state.healthy = false;
      41           0 : }
      42             : 
      43           5 : bool AP_BattMonitor_DroneCAN::subscribe_msgs(AP_DroneCAN* ap_dronecan)
      44             : {
      45           5 :     const auto driver_index = ap_dronecan->get_driver_index();
      46             : 
      47           5 :     return (Canard::allocate_sub_arg_callback(ap_dronecan, &handle_battery_info_trampoline, driver_index) != nullptr)
      48           5 :         && (Canard::allocate_sub_arg_callback(ap_dronecan, &handle_battery_info_aux_trampoline, driver_index) != nullptr)
      49          10 :         && (Canard::allocate_sub_arg_callback(ap_dronecan, &handle_mppt_stream_trampoline, driver_index) != nullptr)
      50             :     ;
      51             : }
      52             : 
      53             : /*
      54             :   match a battery ID to driver serial number
      55             :   when serial number is negative, all batteries are accepted, otherwise it must match
      56             : */
      57           0 : bool AP_BattMonitor_DroneCAN::match_battery_id(uint8_t instance, uint8_t battery_id)
      58             : {
      59           0 :     const auto serial_num = AP::battery().get_serial_number(instance);
      60           0 :     return serial_num < 0 || serial_num == (int32_t)battery_id;
      61             : }
      62             : 
      63           0 : AP_BattMonitor_DroneCAN* AP_BattMonitor_DroneCAN::get_dronecan_backend(AP_DroneCAN* ap_dronecan, uint8_t node_id, uint8_t battery_id)
      64             : {
      65           0 :     if (ap_dronecan == nullptr) {
      66           0 :         return nullptr;
      67             :     }
      68           0 :     const auto &batt = AP::battery();
      69           0 :     for (uint8_t i = 0; i < batt._num_instances; i++) {
      70           0 :         if (batt.drivers[i] == nullptr ||
      71           0 :             batt.allocated_type(i) != AP_BattMonitor::Type::UAVCAN_BatteryInfo) {
      72           0 :             continue;
      73             :         }
      74           0 :         AP_BattMonitor_DroneCAN* driver = (AP_BattMonitor_DroneCAN*)batt.drivers[i];
      75           0 :         if (driver->_ap_dronecan == ap_dronecan && match_battery_id(i, battery_id)) {
      76           0 :             if (driver->_node_id == node_id) {
      77           0 :                 return driver;
      78           0 :             } else if (!driver->_interim_state.healthy && driver->option_is_set(AP_BattMonitor_Params::Options::AllowDynamicNodeUpdate)) {
      79           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: Node change from %d to %d for Id %d",
      80             :                     (unsigned)i+1, driver->_node_id, node_id, battery_id);
      81           0 :                 driver->_node_id = node_id;
      82           0 :                 driver->init();
      83           0 :                 return driver;
      84             :             }
      85             :         }
      86             :     }
      87             :     // find empty uavcan driver
      88           0 :     for (uint8_t i = 0; i < batt._num_instances; i++) {
      89           0 :         if (batt.drivers[i] != nullptr &&
      90           0 :             batt.allocated_type(i) == AP_BattMonitor::Type::UAVCAN_BatteryInfo &&
      91           0 :             match_battery_id(i, battery_id)) {
      92             : 
      93           0 :             AP_BattMonitor_DroneCAN* batmon = (AP_BattMonitor_DroneCAN*)batt.drivers[i];
      94           0 :             if(batmon->_ap_dronecan != nullptr || batmon->_node_id != 0) {
      95           0 :                 continue;
      96             :             }
      97           0 :             batmon->_ap_dronecan = ap_dronecan;
      98           0 :             batmon->_node_id = node_id;
      99           0 :             batmon->_instance = i;
     100           0 :             batmon->init();
     101           0 :             return batmon;
     102             :         }
     103             :     }
     104           0 :     return nullptr;
     105             : }
     106             : 
     107           0 : void AP_BattMonitor_DroneCAN::handle_battery_info(const uavcan_equipment_power_BatteryInfo &msg)
     108             : {
     109           0 :     update_interim_state(msg.voltage, msg.current, msg.temperature, msg.state_of_charge_pct, msg.state_of_health_pct); 
     110             : 
     111           0 :     WITH_SEMAPHORE(_sem_battmon);
     112           0 :     _remaining_capacity_wh = msg.remaining_capacity_wh;
     113           0 :     _full_charge_capacity_wh = msg.full_charge_capacity_wh;
     114             : 
     115             :     // consume state of health
     116           0 :     if (msg.state_of_health_pct != UAVCAN_EQUIPMENT_POWER_BATTERYINFO_STATE_OF_HEALTH_UNKNOWN) {
     117           0 :         _interim_state.state_of_health_pct = msg.state_of_health_pct;
     118           0 :         _interim_state.has_state_of_health_pct = true;
     119             :     }
     120           0 : }
     121             : 
     122           0 : void AP_BattMonitor_DroneCAN::update_interim_state(const float voltage, const float current, const float temperature_K, const uint8_t soc, uint8_t soh_pct)
     123             : {
     124           0 :     WITH_SEMAPHORE(_sem_battmon);
     125             : 
     126           0 :     _interim_state.voltage = voltage;
     127           0 :     _interim_state.current_amps = _curr_mult * current;
     128           0 :     _soc = soc;
     129             : 
     130           0 :     if (!isnan(temperature_K) && temperature_K > 0) {
     131             :         // Temperature reported from battery in kelvin and stored internally in Celsius.
     132           0 :         _interim_state.temperature = KELVIN_TO_C(temperature_K);
     133           0 :         _interim_state.temperature_time = AP_HAL::millis();
     134             :     }
     135             : 
     136           0 :     const uint32_t tnow = AP_HAL::micros();
     137             : 
     138           0 :     if (!_has_battery_info_aux ||
     139           0 :         !use_CAN_SoC()) {
     140           0 :         const uint32_t dt_us = tnow - _interim_state.last_time_micros;
     141             : 
     142             :         // update total current drawn since startup
     143           0 :         update_consumed(_interim_state, dt_us);
     144             :     }
     145             : 
     146             :     // state of health
     147           0 :     if (soh_pct != UAVCAN_EQUIPMENT_POWER_BATTERYINFO_STATE_OF_HEALTH_UNKNOWN) {
     148           0 :         _interim_state.state_of_health_pct = soh_pct;
     149           0 :         _interim_state.has_state_of_health_pct = true;
     150             :     }
     151             : 
     152             :     // record time
     153           0 :     _interim_state.last_time_micros = tnow;
     154           0 :     _interim_state.healthy = true;
     155           0 : }
     156             : 
     157           0 : void AP_BattMonitor_DroneCAN::handle_battery_info_aux(const ardupilot_equipment_power_BatteryInfoAux &msg)
     158             : {
     159           0 :     WITH_SEMAPHORE(_sem_battmon);
     160           0 :     uint8_t cell_count = MIN(ARRAY_SIZE(_interim_state.cell_voltages.cells), msg.voltage_cell.len);
     161             : 
     162           0 :     _cycle_count = msg.cycle_count;
     163           0 :     for (uint8_t i = 0; i < cell_count; i++) {
     164           0 :         _interim_state.cell_voltages.cells[i] = msg.voltage_cell.data[i] * 1000;
     165             :     }
     166           0 :     _interim_state.is_powering_off = msg.is_powering_off;
     167           0 :     if (!isnan(msg.nominal_voltage) && msg.nominal_voltage > 0) {
     168           0 :         float remaining_capacity_ah = _remaining_capacity_wh / msg.nominal_voltage;
     169           0 :         float full_charge_capacity_ah = _full_charge_capacity_wh / msg.nominal_voltage;
     170           0 :         _interim_state.consumed_mah = (full_charge_capacity_ah - remaining_capacity_ah) * 1000;
     171           0 :         _interim_state.consumed_wh = _full_charge_capacity_wh - _remaining_capacity_wh;
     172           0 :         _interim_state.time_remaining =  is_zero(_interim_state.current_amps) ? 0 : (remaining_capacity_ah / _interim_state.current_amps * 3600);
     173           0 :         _interim_state.has_time_remaining = true;
     174             :     }
     175             : 
     176           0 :     _has_cell_voltages = true;
     177           0 :     _has_battery_info_aux = true;
     178           0 : }
     179             : 
     180           0 : void AP_BattMonitor_DroneCAN::handle_mppt_stream(const mppt_Stream &msg)
     181             : {
     182           0 :     const bool use_input_value = option_is_set(AP_BattMonitor_Params::Options::MPPT_Use_Input_Value);
     183           0 :     const float voltage = use_input_value ? msg.input_voltage : msg.output_voltage;
     184           0 :     const float current = use_input_value ? msg.input_current : msg.output_current;
     185             : 
     186             :     // use an invalid soc so we use the library calculated one
     187           0 :     const uint8_t soc = 127;
     188             : 
     189             :     // convert C to Kelvin
     190           0 :     const float temperature_K = isnan(msg.temperature) ? 0 : C_TO_KELVIN(msg.temperature);
     191             : 
     192           0 :     update_interim_state(voltage, current, temperature_K, soc, UAVCAN_EQUIPMENT_POWER_BATTERYINFO_STATE_OF_HEALTH_UNKNOWN); 
     193             : 
     194           0 :     if (!_mppt.is_detected) {
     195             :         // this is the first time the mppt message has been received
     196             :         // so set powered up state
     197           0 :         _mppt.is_detected = true;
     198             : 
     199             :         // Boot/Power-up event
     200           0 :         if (option_is_set(AP_BattMonitor_Params::Options::MPPT_Power_On_At_Boot)) {
     201           0 :             set_powered_state(true);
     202           0 :         } else if (option_is_set(AP_BattMonitor_Params::Options::MPPT_Power_Off_At_Boot)) {
     203           0 :             set_powered_state(false);
     204             :         }
     205             :     }
     206             : 
     207             : #if AP_BATTMONITOR_UAVCAN_MPPT_DEBUG
     208             :     if (_mppt.fault_flags != msg.fault_flags) {
     209             :         mppt_report_faults(_instance, msg.fault_flags);
     210             :     }
     211             : #endif
     212           0 :     _mppt.fault_flags = msg.fault_flags;
     213           0 : }
     214             : 
     215           0 : void AP_BattMonitor_DroneCAN::handle_battery_info_trampoline(AP_DroneCAN *ap_dronecan, const CanardRxTransfer& transfer, const uavcan_equipment_power_BatteryInfo &msg)
     216             : {
     217           0 :     AP_BattMonitor_DroneCAN* driver = get_dronecan_backend(ap_dronecan, transfer.source_node_id, msg.battery_id);
     218           0 :     if (driver == nullptr) {
     219           0 :         return;
     220             :     }
     221           0 :     driver->handle_battery_info(msg);
     222             : }
     223             : 
     224           0 : void AP_BattMonitor_DroneCAN::handle_battery_info_aux_trampoline(AP_DroneCAN *ap_dronecan, const CanardRxTransfer& transfer, const ardupilot_equipment_power_BatteryInfoAux &msg)
     225             : {
     226           0 :     const auto &batt = AP::battery();
     227           0 :     AP_BattMonitor_DroneCAN *driver = nullptr;
     228             : 
     229             :     /*
     230             :       check for a backend with AllowSplitAuxInfo set, allowing InfoAux
     231             :       from a different CAN node than the base battery information
     232             :      */
     233           0 :     for (uint8_t i = 0; i < batt._num_instances; i++) {
     234           0 :         const auto *drv = batt.drivers[i];
     235           0 :         if (drv != nullptr &&
     236           0 :             batt.allocated_type(i) == AP_BattMonitor::Type::UAVCAN_BatteryInfo &&
     237           0 :             drv->option_is_set(AP_BattMonitor_Params::Options::AllowSplitAuxInfo) &&
     238           0 :             batt.get_serial_number(i) == int32_t(msg.battery_id)) {
     239           0 :             driver = (AP_BattMonitor_DroneCAN *)batt.drivers[i];
     240           0 :             if (driver->_ap_dronecan == nullptr) {
     241             :                 /* we have not received the main battery information
     242             :                    yet. Discard InfoAux until we do so we can init the
     243             :                    backend with the right node ID
     244             :                  */
     245           0 :                 return;
     246             :             }
     247           0 :             break;
     248             :         }
     249             :     }
     250           0 :     if (driver == nullptr) {
     251           0 :         driver = get_dronecan_backend(ap_dronecan, transfer.source_node_id, msg.battery_id);
     252             :     }
     253           0 :     if (driver == nullptr) {
     254           0 :         return;
     255             :     }
     256           0 :     driver->handle_battery_info_aux(msg);
     257             : }
     258             : 
     259           0 : void AP_BattMonitor_DroneCAN::handle_mppt_stream_trampoline(AP_DroneCAN *ap_dronecan, const CanardRxTransfer& transfer, const mppt_Stream &msg)
     260             : {
     261           0 :     AP_BattMonitor_DroneCAN* driver = get_dronecan_backend(ap_dronecan, transfer.source_node_id, transfer.source_node_id);
     262           0 :     if (driver == nullptr) {
     263           0 :         return;
     264             :     }
     265           0 :     driver->handle_mppt_stream(msg);
     266             : }
     267             : 
     268             : // read - read the voltage and current
     269           0 : void AP_BattMonitor_DroneCAN::read()
     270             : {
     271           0 :     uint32_t tnow = AP_HAL::micros();
     272             : 
     273             :     // timeout after 5 seconds
     274           0 :     if ((tnow - _interim_state.last_time_micros) > AP_BATTMONITOR_UAVCAN_TIMEOUT_MICROS) {
     275           0 :         _interim_state.healthy = false;
     276             :     }
     277             :     // Copy over relevant states over to main state
     278           0 :     WITH_SEMAPHORE(_sem_battmon);
     279           0 :     _state.temperature = _interim_state.temperature;
     280           0 :     _state.temperature_time = _interim_state.temperature_time;
     281           0 :     _state.voltage = _interim_state.voltage;
     282           0 :     _state.current_amps = _interim_state.current_amps;
     283           0 :     _state.consumed_mah = _interim_state.consumed_mah;
     284           0 :     _state.consumed_wh = _interim_state.consumed_wh;
     285           0 :     _state.last_time_micros = _interim_state.last_time_micros;
     286           0 :     _state.healthy = _interim_state.healthy;
     287           0 :     _state.time_remaining = _interim_state.time_remaining;
     288           0 :     _state.has_time_remaining = _interim_state.has_time_remaining;
     289           0 :     _state.is_powering_off = _interim_state.is_powering_off;
     290           0 :     _state.state_of_health_pct = _interim_state.state_of_health_pct;
     291           0 :     _state.has_state_of_health_pct = _interim_state.has_state_of_health_pct;
     292           0 :     memcpy(_state.cell_voltages.cells, _interim_state.cell_voltages.cells, sizeof(_state.cell_voltages));
     293             : 
     294           0 :     _has_temperature = (AP_HAL::millis() - _state.temperature_time) <= AP_BATT_MONITOR_TIMEOUT;
     295             : 
     296             :     // check if MPPT should be powered on/off depending upon arming state
     297           0 :     if (_mppt.is_detected) {
     298           0 :         mppt_check_powered_state();
     299             :     }
     300           0 : }
     301             : 
     302             : // Return true if the DroneCAN state of charge should be used.
     303             : // Return false if state of charge should be calculated locally by counting mah.
     304           0 : bool AP_BattMonitor_DroneCAN::use_CAN_SoC() const
     305             : {
     306             :     // a UAVCAN battery monitor may not be able to supply a state of charge. If it can't then
     307             :     // the user can set the option to use current integration in the backend instead.
     308             :     // SOC of 127 is used as an invalid SOC flag ie system configuration errors or SOC estimation unavailable
     309           0 :     return !(option_is_set(AP_BattMonitor_Params::Options::Ignore_UAVCAN_SoC) ||
     310           0 :             _mppt.is_detected ||
     311           0 :             (_soc == 127));
     312             : }
     313             : 
     314             : /// capacity_remaining_pct - returns true if the percentage is valid and writes to percentage argument
     315           0 : bool AP_BattMonitor_DroneCAN::capacity_remaining_pct(uint8_t &percentage) const
     316             : {
     317           0 :     if (!use_CAN_SoC()) {
     318           0 :         return AP_BattMonitor_Backend::capacity_remaining_pct(percentage);
     319             :     }
     320             : 
     321             :     // the monitor must have current readings in order to estimate consumed_mah and be healthy
     322           0 :     if (!has_current() || !_state.healthy) {
     323           0 :         return false;
     324             :     }
     325             : 
     326           0 :     percentage = _soc;
     327           0 :     return true;
     328             : }
     329             : 
     330             : // reset remaining percentage to given value
     331           0 : bool AP_BattMonitor_DroneCAN::reset_remaining(float percentage)
     332             : {
     333           0 :     if (use_CAN_SoC()) {
     334             :         // Cannot reset external state of charge
     335           0 :         return false;
     336             :     }
     337             : 
     338           0 :     WITH_SEMAPHORE(_sem_battmon);
     339             : 
     340           0 :     if (!AP_BattMonitor_Backend::reset_remaining(percentage)) {
     341             :         // Base class reset failed
     342           0 :         return false;
     343             :     }
     344             : 
     345             :     // Reset interim state that is used internally, this is then copied back to the main state in the read() call
     346           0 :     _interim_state.consumed_mah = _state.consumed_mah;
     347           0 :     _interim_state.consumed_wh = _state.consumed_wh;
     348           0 :     return true;
     349           0 : }
     350             : 
     351             : /// get_cycle_count - return true if cycle count can be provided and fills in cycles argument
     352           0 : bool AP_BattMonitor_DroneCAN::get_cycle_count(uint16_t &cycles) const
     353             : {
     354           0 :     if (_has_battery_info_aux) {
     355           0 :         cycles = _cycle_count;
     356           0 :         return true;
     357             :     }
     358           0 :     return false;
     359             : }
     360             : 
     361             : // request MPPT board to power on/off depending upon vehicle arming state as specified by BATT_OPTIONS
     362           0 : void AP_BattMonitor_DroneCAN::mppt_check_powered_state()
     363             : {
     364           0 :     if ((_mppt.powered_state_remote_ms != 0) && (AP_HAL::millis() - _mppt.powered_state_remote_ms >= 1000)) {
     365             :         // there's already a set attempt that didnt' respond. Retry at 1Hz
     366           0 :         set_powered_state(_mppt.powered_state);
     367             :     }
     368             : 
     369             :     // check if vehicle armed state has changed
     370           0 :     const bool vehicle_armed = hal.util->get_soft_armed();
     371           0 :     if ((!_mppt.vehicle_armed_last && vehicle_armed) && option_is_set(AP_BattMonitor_Params::Options::MPPT_Power_On_At_Arm)) {
     372             :         // arm event
     373           0 :         set_powered_state(true);
     374           0 :     } else if ((_mppt.vehicle_armed_last && !vehicle_armed) && option_is_set(AP_BattMonitor_Params::Options::MPPT_Power_Off_At_Disarm)) {
     375             :         // disarm event
     376           0 :         set_powered_state(false);
     377             :     }
     378           0 :     _mppt.vehicle_armed_last = vehicle_armed;
     379           0 : }
     380             : 
     381             : // request MPPT or BMS board to power on or off
     382             : // power_on should be true to power on the MPPT, false to power off
     383             : // force should be true to force sending the state change request to the MPPT
     384           0 : void AP_BattMonitor_DroneCAN::set_powered_state(bool power_on)
     385             : {
     386           0 :     if (_ap_dronecan == nullptr || !_mppt.is_detected) {
     387           0 :         return;
     388             :     }
     389             : 
     390           0 :     _mppt.powered_state = power_on;
     391             : 
     392           0 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: powering %s%s", (unsigned)_instance+1, _mppt.powered_state ? "ON" : "OFF",
     393             :         (_mppt.powered_state_remote_ms == 0) ? "" : " Retry");
     394             : 
     395             :     mppt_OutputEnableRequest request;
     396           0 :     request.enable = _mppt.powered_state;
     397           0 :     request.disable = !request.enable;
     398             : 
     399           0 :     if (mppt_outputenable_client == nullptr) {
     400           0 :         mppt_outputenable_client = NEW_NOTHROW Canard::Client<mppt_OutputEnableResponse>{_ap_dronecan->get_canard_iface(), mppt_outputenable_res_cb};
     401           0 :         if (mppt_outputenable_client == nullptr) {
     402           0 :             return;
     403             :         }
     404             :     }
     405           0 :     mppt_outputenable_client->request(_node_id, request);
     406             : }
     407             : 
     408             : // callback from outputEnable to verify it is enabled or disabled
     409           0 : void AP_BattMonitor_DroneCAN::handle_outputEnable_response(const CanardRxTransfer& transfer, const mppt_OutputEnableResponse& response)
     410             : {
     411           0 :     if (transfer.source_node_id != _node_id) {
     412             :         // this response is not from the node we are looking for
     413           0 :         return;
     414             :     }
     415             : 
     416           0 :     if (response.enabled == _mppt.powered_state) {
     417             :         // we got back what we expected it to be. We set it on, it now says it on (or vice versa).
     418             :         // Clear the timer so we don't re-request
     419           0 :         _mppt.powered_state_remote_ms = 0;
     420             :     }
     421             : }
     422             : 
     423             : #if AP_BATTMONITOR_UAVCAN_MPPT_DEBUG
     424             : // report changes in MPPT faults
     425             : void AP_BattMonitor_DroneCAN::mppt_report_faults(const uint8_t instance, const uint8_t fault_flags)
     426             : {
     427             :     // handle recovery
     428             :     if (fault_flags == 0) {
     429             :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: OK", (unsigned)instance+1);
     430             :         return;
     431             :     }
     432             : 
     433             :     // send battery faults via text messages
     434             :     for (uint8_t fault_bit=0x01; fault_bit <= 0x08; fault_bit <<= 1) {
     435             :         // this loop is to generate multiple messages if there are multiple concurrent faults, but also run once if there are no faults
     436             :         if ((fault_bit & fault_flags) != 0) {
     437             :             const MPPT_FaultFlags err = (MPPT_FaultFlags)fault_bit;
     438             :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: %s", (unsigned)instance+1, mppt_fault_string(err));
     439             :         }
     440             :     }
     441             : }
     442             : 
     443             : // returns string description of MPPT fault bit. Only handles single bit faults
     444             : const char* AP_BattMonitor_DroneCAN::mppt_fault_string(const MPPT_FaultFlags fault)
     445             : {
     446             :     switch (fault) {
     447             :         case MPPT_FaultFlags::OVER_VOLTAGE:
     448             :             return "over voltage";
     449             :         case MPPT_FaultFlags::UNDER_VOLTAGE:
     450             :             return "under voltage";
     451             :         case MPPT_FaultFlags::OVER_CURRENT:
     452             :             return "over current";
     453             :         case MPPT_FaultFlags::OVER_TEMPERATURE:
     454             :             return "over temp";
     455             :     }
     456             :     return "unknown";
     457             : }
     458             : #endif
     459             : 
     460             : // return mavlink fault bitmask (see MAV_BATTERY_FAULT enum)
     461           0 : uint32_t AP_BattMonitor_DroneCAN::get_mavlink_fault_bitmask() const
     462             : {
     463             :     // return immediately if not mppt or no faults
     464           0 :     if (!_mppt.is_detected || (_mppt.fault_flags == 0)) {
     465           0 :         return 0;
     466             :     }
     467             : 
     468             :     // convert mppt fault bitmask to mavlink fault bitmask
     469           0 :     uint32_t mav_fault_bitmask = 0;
     470           0 :     if ((_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_VOLTAGE) || (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::UNDER_VOLTAGE)) {
     471           0 :         mav_fault_bitmask |= MAV_BATTERY_FAULT_INCOMPATIBLE_VOLTAGE;
     472             :     }
     473           0 :     if (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_CURRENT) {
     474           0 :         mav_fault_bitmask |= MAV_BATTERY_FAULT_OVER_CURRENT;
     475             :     }
     476           0 :     if (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_TEMPERATURE) {
     477           0 :         mav_fault_bitmask |= MAV_BATTERY_FAULT_OVER_TEMPERATURE;
     478             :     }
     479           0 :     return mav_fault_bitmask;
     480             : }
     481             : 
     482             : #endif  // AP_BATTERY_UAVCAN_BATTERYINFO_ENABLED

Generated by: LCOV version 1.14