2018-02-16 18:32:25 -04:00
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
|
|
|
2020-05-31 09:03:02 -03:00
|
|
|
#if HAL_ENABLE_LIBUAVCAN_DRIVERS
|
2018-02-16 18:32:25 -04:00
|
|
|
|
|
|
|
#include "AP_BattMonitor.h"
|
|
|
|
#include "AP_BattMonitor_UAVCAN.h"
|
|
|
|
|
2020-05-31 09:03:02 -03:00
|
|
|
#include <AP_CANManager/AP_CANManager.h>
|
2018-07-20 10:01:13 -03:00
|
|
|
#include <AP_Common/AP_Common.h>
|
2021-12-01 21:30:13 -04:00
|
|
|
#include <GCS_MAVLink/GCS.h>
|
2018-07-20 10:01:13 -03:00
|
|
|
#include <AP_Math/AP_Math.h>
|
|
|
|
#include <AP_UAVCAN/AP_UAVCAN.h>
|
2021-10-11 04:26:42 -03:00
|
|
|
#include <AP_BoardConfig/AP_BoardConfig.h>
|
2018-02-16 18:32:25 -04:00
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
#include <uavcan/equipment/power/BatteryInfo.hpp>
|
2021-10-11 04:26:42 -03:00
|
|
|
#include <ardupilot/equipment/power/BatteryInfoAux.hpp>
|
2021-12-01 21:30:13 -04:00
|
|
|
#include <mppt/Stream.hpp>
|
|
|
|
#include <mppt/OutputEnable.hpp>
|
2018-02-16 18:32:25 -04:00
|
|
|
|
2020-05-31 09:03:02 -03:00
|
|
|
#define LOG_TAG "BattMon"
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
extern const AP_HAL::HAL& hal;
|
2018-02-16 18:32:25 -04:00
|
|
|
|
2022-01-12 12:58:45 -04:00
|
|
|
const AP_Param::GroupInfo AP_BattMonitor_UAVCAN::var_info[] = {
|
|
|
|
|
2022-01-24 23:10:34 -04:00
|
|
|
// @Param: CURR_MULT
|
2022-01-12 12:58:45 -04:00
|
|
|
// @DisplayName: Scales reported power monitor current
|
|
|
|
// @Description: Multiplier applied to all current related reports to allow for adjustment if no UAVCAN param access or current splitting applications
|
|
|
|
// @Range: .1 10
|
|
|
|
// @User: Advanced
|
2022-02-11 05:00:29 -04:00
|
|
|
AP_GROUPINFO("CURR_MULT", 30, AP_BattMonitor_UAVCAN, _curr_mult, 1.0),
|
|
|
|
|
|
|
|
// Param indexes must be between 30 and 39 to avoid conflict with other battery monitor param tables loaded by pointer
|
|
|
|
|
2022-01-12 12:58:45 -04:00
|
|
|
AP_GROUPEND
|
|
|
|
};
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
UC_REGISTRY_BINDER(BattInfoCb, uavcan::equipment::power::BatteryInfo);
|
2021-10-11 04:26:42 -03:00
|
|
|
UC_REGISTRY_BINDER(BattInfoAuxCb, ardupilot::equipment::power::BatteryInfoAux);
|
2021-12-01 21:30:13 -04:00
|
|
|
UC_REGISTRY_BINDER(MpptStreamCb, mppt::Stream);
|
2018-07-20 10:01:13 -03:00
|
|
|
|
2018-02-16 18:32:25 -04:00
|
|
|
/// Constructor
|
|
|
|
AP_BattMonitor_UAVCAN::AP_BattMonitor_UAVCAN(AP_BattMonitor &mon, AP_BattMonitor::BattMonitor_State &mon_state, BattMonitor_UAVCAN_Type type, AP_BattMonitor_Params ¶ms) :
|
|
|
|
AP_BattMonitor_Backend(mon, mon_state, params),
|
|
|
|
_type(type)
|
|
|
|
{
|
2022-01-12 12:58:45 -04:00
|
|
|
AP_Param::setup_object_defaults(this,var_info);
|
|
|
|
_state.var_info = var_info;
|
|
|
|
|
2018-02-16 18:32:25 -04:00
|
|
|
// starts with not healthy
|
|
|
|
_state.healthy = false;
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
void AP_BattMonitor_UAVCAN::subscribe_msgs(AP_UAVCAN* ap_uavcan)
|
2018-02-16 18:32:25 -04:00
|
|
|
{
|
2018-07-20 10:01:13 -03:00
|
|
|
if (ap_uavcan == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
2018-03-09 04:31:53 -04:00
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
auto* node = ap_uavcan->get_node();
|
2018-07-18 03:27:37 -03:00
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
uavcan::Subscriber<uavcan::equipment::power::BatteryInfo, BattInfoCb> *battinfo_listener;
|
|
|
|
battinfo_listener = new uavcan::Subscriber<uavcan::equipment::power::BatteryInfo, BattInfoCb>(*node);
|
|
|
|
// Backend Msg Handler
|
|
|
|
const int battinfo_listener_res = battinfo_listener->start(BattInfoCb(ap_uavcan, &handle_battery_info_trampoline));
|
|
|
|
if (battinfo_listener_res < 0) {
|
2021-10-11 04:26:42 -03:00
|
|
|
AP_BoardConfig::allocation_error("UAVCAN BatteryInfo subscriber start problem\n\r");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uavcan::Subscriber<ardupilot::equipment::power::BatteryInfoAux, BattInfoAuxCb> *battinfo_aux_listener;
|
|
|
|
battinfo_aux_listener = new uavcan::Subscriber<ardupilot::equipment::power::BatteryInfoAux, BattInfoAuxCb>(*node);
|
|
|
|
// Backend Msg Handler
|
|
|
|
const int battinfo_aux_listener_res = battinfo_aux_listener->start(BattInfoAuxCb(ap_uavcan, &handle_battery_info_aux_trampoline));
|
|
|
|
if (battinfo_aux_listener_res < 0) {
|
|
|
|
AP_BoardConfig::allocation_error("UAVCAN BatteryInfoAux subscriber start problem");
|
2018-07-20 10:01:13 -03:00
|
|
|
return;
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
2021-12-01 21:30:13 -04:00
|
|
|
|
|
|
|
uavcan::Subscriber<mppt::Stream, MpptStreamCb> *mppt_stream_listener;
|
|
|
|
mppt_stream_listener = new uavcan::Subscriber<mppt::Stream, MpptStreamCb>(*node);
|
|
|
|
// Backend Msg Handler
|
|
|
|
const int mppt_stream_listener_res = mppt_stream_listener->start(MpptStreamCb(ap_uavcan, &handle_mppt_stream_trampoline));
|
|
|
|
if (mppt_stream_listener_res < 0) {
|
|
|
|
AP_BoardConfig::allocation_error("UAVCAN Mppt::Stream subscriber start problem");
|
|
|
|
return;
|
|
|
|
}
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
|
|
|
|
2020-11-21 01:28:12 -04:00
|
|
|
AP_BattMonitor_UAVCAN* AP_BattMonitor_UAVCAN::get_uavcan_backend(AP_UAVCAN* ap_uavcan, uint8_t node_id, uint8_t battery_id)
|
2018-02-16 18:32:25 -04:00
|
|
|
{
|
2018-07-20 10:01:13 -03:00
|
|
|
if (ap_uavcan == nullptr) {
|
|
|
|
return nullptr;
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
2018-07-20 10:01:13 -03:00
|
|
|
for (uint8_t i = 0; i < AP::battery()._num_instances; i++) {
|
|
|
|
if (AP::battery().drivers[i] == nullptr ||
|
2020-08-06 02:33:07 -03:00
|
|
|
AP::battery().get_type(i) != AP_BattMonitor::Type::UAVCAN_BatteryInfo) {
|
2018-07-20 10:01:13 -03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
AP_BattMonitor_UAVCAN* driver = (AP_BattMonitor_UAVCAN*)AP::battery().drivers[i];
|
2020-11-21 01:28:12 -04:00
|
|
|
if (driver->_ap_uavcan == ap_uavcan && driver->_node_id == node_id && match_battery_id(i, battery_id)) {
|
2018-07-20 10:01:13 -03:00
|
|
|
return driver;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// find empty uavcan driver
|
|
|
|
for (uint8_t i = 0; i < AP::battery()._num_instances; i++) {
|
|
|
|
if (AP::battery().drivers[i] != nullptr &&
|
2020-11-21 01:28:12 -04:00
|
|
|
AP::battery().get_type(i) == AP_BattMonitor::Type::UAVCAN_BatteryInfo &&
|
|
|
|
match_battery_id(i, battery_id)) {
|
2018-07-20 10:01:13 -03:00
|
|
|
|
|
|
|
AP_BattMonitor_UAVCAN* batmon = (AP_BattMonitor_UAVCAN*)AP::battery().drivers[i];
|
2020-08-09 23:58:02 -03:00
|
|
|
if(batmon->_ap_uavcan != nullptr || batmon->_node_id != 0) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-07-20 10:01:13 -03:00
|
|
|
batmon->_ap_uavcan = ap_uavcan;
|
|
|
|
batmon->_node_id = node_id;
|
2021-12-01 21:30:13 -04:00
|
|
|
batmon->_instance = i;
|
|
|
|
batmon->_node = ap_uavcan->get_node();
|
2018-07-20 10:01:13 -03:00
|
|
|
batmon->init();
|
2020-05-31 09:03:02 -03:00
|
|
|
AP::can().log_text(AP_CANManager::LOG_INFO,
|
|
|
|
LOG_TAG,
|
2018-07-20 10:01:13 -03:00
|
|
|
"Registered BattMonitor Node %d on Bus %d\n",
|
|
|
|
node_id,
|
|
|
|
ap_uavcan->get_driver_index());
|
|
|
|
return batmon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_battery_info(const BattInfoCb &cb)
|
2018-02-16 18:32:25 -04:00
|
|
|
{
|
2021-12-01 21:30:13 -04:00
|
|
|
update_interim_state(cb.msg->voltage, cb.msg->current, cb.msg->temperature, cb.msg->state_of_charge_pct);
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
WITH_SEMAPHORE(_sem_battmon);
|
2021-10-11 04:26:42 -03:00
|
|
|
_remaining_capacity_wh = cb.msg->remaining_capacity_wh;
|
|
|
|
_full_charge_capacity_wh = cb.msg->full_charge_capacity_wh;
|
2021-12-01 21:30:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void AP_BattMonitor_UAVCAN::update_interim_state(const float voltage, const float current, const float temperature_K, const uint8_t soc)
|
|
|
|
{
|
|
|
|
WITH_SEMAPHORE(_sem_battmon);
|
|
|
|
|
|
|
|
_interim_state.voltage = voltage;
|
2022-01-24 23:10:34 -04:00
|
|
|
_interim_state.current_amps = _curr_mult * current;
|
2021-12-01 21:30:13 -04:00
|
|
|
_soc = soc;
|
2018-02-16 18:32:25 -04:00
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
if (!isnanf(temperature_K) && temperature_K > 0) {
|
2021-05-24 23:07:48 -03:00
|
|
|
// Temperature reported from battery in kelvin and stored internally in Celsius.
|
2022-01-12 08:03:24 -04:00
|
|
|
_interim_state.temperature = KELVIN_TO_C(temperature_K);
|
2021-05-24 23:07:48 -03:00
|
|
|
_interim_state.temperature_time = AP_HAL::millis();
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
const uint32_t tnow = AP_HAL::micros();
|
2021-10-11 04:26:42 -03:00
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
if (!_has_battery_info_aux || _mppt.is_detected) {
|
2021-10-11 04:26:42 -03:00
|
|
|
uint32_t dt = tnow - _interim_state.last_time_micros;
|
|
|
|
|
|
|
|
// update total current drawn since startup
|
|
|
|
if (_interim_state.last_time_micros != 0 && dt < 2000000) {
|
2021-12-16 14:48:09 -04:00
|
|
|
float mah = calculate_mah(_interim_state.current_amps, dt);
|
2021-10-11 04:26:42 -03:00
|
|
|
_interim_state.consumed_mah += mah;
|
|
|
|
_interim_state.consumed_wh += 0.001f * mah * _interim_state.voltage;
|
|
|
|
}
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// record time
|
2018-07-20 10:01:13 -03:00
|
|
|
_interim_state.last_time_micros = tnow;
|
|
|
|
_interim_state.healthy = true;
|
|
|
|
}
|
|
|
|
|
2021-10-11 04:26:42 -03:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_battery_info_aux(const BattInfoAuxCb &cb)
|
|
|
|
{
|
|
|
|
WITH_SEMAPHORE(_sem_battmon);
|
|
|
|
uint8_t cell_count = MIN(ARRAY_SIZE(_interim_state.cell_voltages.cells), cb.msg->voltage_cell.size());
|
|
|
|
float remaining_capacity_ah = _remaining_capacity_wh / cb.msg->nominal_voltage;
|
|
|
|
float full_charge_capacity_ah = _full_charge_capacity_wh / cb.msg->nominal_voltage;
|
|
|
|
|
|
|
|
_cycle_count = cb.msg->cycle_count;
|
|
|
|
for (uint8_t i = 0; i < cell_count; i++) {
|
|
|
|
_interim_state.cell_voltages.cells[i] = cb.msg->voltage_cell[i] * 1000;
|
|
|
|
}
|
|
|
|
_interim_state.is_powering_off = cb.msg->is_powering_off;
|
|
|
|
_interim_state.consumed_mah = (full_charge_capacity_ah - remaining_capacity_ah) * 1000;
|
|
|
|
_interim_state.consumed_wh = _full_charge_capacity_wh - _remaining_capacity_wh;
|
|
|
|
_interim_state.time_remaining = is_zero(_interim_state.current_amps) ? 0 : (remaining_capacity_ah / _interim_state.current_amps * 3600);
|
|
|
|
_interim_state.has_time_remaining = true;
|
|
|
|
|
|
|
|
_has_cell_voltages = true;
|
|
|
|
_has_time_remaining = true;
|
|
|
|
_has_consumed_energy = true;
|
|
|
|
_has_battery_info_aux = true;
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_mppt_stream(const MpptStreamCb &cb)
|
|
|
|
{
|
|
|
|
const bool use_input_value = (uint32_t(_params._options.get()) & uint32_t(AP_BattMonitor_Params::Options::MPPT_Use_Input_Value)) != 0;
|
|
|
|
const float voltage = use_input_value ? cb.msg->input_voltage : cb.msg->output_voltage;
|
|
|
|
const float current = use_input_value ? cb.msg->input_current : cb.msg->output_current;
|
|
|
|
|
|
|
|
// use an invalid soc so we use the library calculated one
|
2021-12-10 18:33:02 -04:00
|
|
|
const uint8_t soc = 127;
|
2021-12-01 21:30:13 -04:00
|
|
|
|
|
|
|
// convert C to Kelvin
|
2022-01-12 08:03:24 -04:00
|
|
|
const float temperature_K = isnanf(cb.msg->temperature) ? 0 : C_TO_KELVIN(cb.msg->temperature);
|
2021-12-01 21:30:13 -04:00
|
|
|
|
|
|
|
update_interim_state(voltage, current, temperature_K, soc);
|
|
|
|
|
|
|
|
if (!_mppt.is_detected) {
|
|
|
|
// this is the first time the mppt message has been received
|
|
|
|
// so set powered up state
|
|
|
|
_mppt.is_detected = true;
|
|
|
|
mppt_set_bootup_powered_state();
|
|
|
|
}
|
|
|
|
|
|
|
|
mppt_check_and_report_faults(cb.msg->fault_flags);
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_battery_info_trampoline(AP_UAVCAN* ap_uavcan, uint8_t node_id, const BattInfoCb &cb)
|
|
|
|
{
|
2020-11-21 01:28:12 -04:00
|
|
|
AP_BattMonitor_UAVCAN* driver = get_uavcan_backend(ap_uavcan, node_id, cb.msg->battery_id);
|
2018-07-20 10:01:13 -03:00
|
|
|
if (driver == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
driver->handle_battery_info(cb);
|
|
|
|
}
|
|
|
|
|
2021-10-11 04:26:42 -03:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_battery_info_aux_trampoline(AP_UAVCAN* ap_uavcan, uint8_t node_id, const BattInfoAuxCb &cb)
|
|
|
|
{
|
|
|
|
AP_BattMonitor_UAVCAN* driver = get_uavcan_backend(ap_uavcan, node_id, cb.msg->battery_id);
|
|
|
|
if (driver == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
driver->handle_battery_info_aux(cb);
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
void AP_BattMonitor_UAVCAN::handle_mppt_stream_trampoline(AP_UAVCAN* ap_uavcan, uint8_t node_id, const MpptStreamCb &cb)
|
|
|
|
{
|
|
|
|
AP_BattMonitor_UAVCAN* driver = get_uavcan_backend(ap_uavcan, node_id, node_id);
|
|
|
|
if (driver == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
driver->handle_mppt_stream(cb);
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:01:13 -03:00
|
|
|
// read - read the voltage and current
|
|
|
|
void AP_BattMonitor_UAVCAN::read()
|
|
|
|
{
|
|
|
|
uint32_t tnow = AP_HAL::micros();
|
|
|
|
|
|
|
|
// timeout after 5 seconds
|
|
|
|
if ((tnow - _interim_state.last_time_micros) > AP_BATTMONITOR_UAVCAN_TIMEOUT_MICROS) {
|
|
|
|
_interim_state.healthy = false;
|
|
|
|
}
|
|
|
|
// Copy over relevant states over to main state
|
|
|
|
WITH_SEMAPHORE(_sem_battmon);
|
|
|
|
_state.temperature = _interim_state.temperature;
|
2021-05-24 23:07:48 -03:00
|
|
|
_state.temperature_time = _interim_state.temperature_time;
|
2018-07-20 10:01:13 -03:00
|
|
|
_state.voltage = _interim_state.voltage;
|
|
|
|
_state.current_amps = _interim_state.current_amps;
|
|
|
|
_state.consumed_mah = _interim_state.consumed_mah;
|
|
|
|
_state.consumed_wh = _interim_state.consumed_wh;
|
|
|
|
_state.last_time_micros = _interim_state.last_time_micros;
|
|
|
|
_state.healthy = _interim_state.healthy;
|
2021-10-11 04:26:42 -03:00
|
|
|
_state.time_remaining = _interim_state.time_remaining;
|
|
|
|
_state.has_time_remaining = _interim_state.has_time_remaining;
|
|
|
|
_state.is_powering_off = _interim_state.is_powering_off;
|
|
|
|
memcpy(_state.cell_voltages.cells, _interim_state.cell_voltages.cells, sizeof(_state.cell_voltages));
|
2021-05-24 23:07:48 -03:00
|
|
|
|
|
|
|
_has_temperature = (AP_HAL::millis() - _state.temperature_time) <= AP_BATT_MONITOR_TIMEOUT;
|
2021-12-01 21:30:13 -04:00
|
|
|
|
|
|
|
// check if MPPT should be powered on/off depending upon arming state
|
|
|
|
if (_mppt.is_detected) {
|
|
|
|
mppt_set_armed_powered_state();
|
|
|
|
}
|
2018-02-16 18:32:25 -04:00
|
|
|
}
|
|
|
|
|
2021-02-17 10:02:34 -04:00
|
|
|
/// capacity_remaining_pct - returns true if the percentage is valid and writes to percentage argument
|
|
|
|
bool AP_BattMonitor_UAVCAN::capacity_remaining_pct(uint8_t &percentage) const
|
2020-12-07 01:30:23 -04:00
|
|
|
{
|
|
|
|
if ((uint32_t(_params._options.get()) & uint32_t(AP_BattMonitor_Params::Options::Ignore_UAVCAN_SoC)) ||
|
2021-12-01 21:30:13 -04:00
|
|
|
_mppt.is_detected ||
|
2021-12-10 18:33:02 -04:00
|
|
|
_soc == 127) {
|
2020-12-07 01:30:23 -04:00
|
|
|
// a UAVCAN battery monitor may not be able to supply a state of charge. If it can't then
|
|
|
|
// the user can set the option to use current integration in the backend instead.
|
2021-12-10 18:33:02 -04:00
|
|
|
// SOC of 127 is used as an invalid SOC flag ie system configuration errors or SOC estimation unavailable
|
2021-02-17 10:02:34 -04:00
|
|
|
return AP_BattMonitor_Backend::capacity_remaining_pct(percentage);
|
2020-12-07 01:30:23 -04:00
|
|
|
}
|
2021-09-23 01:57:41 -03:00
|
|
|
|
|
|
|
// the monitor must have current readings in order to estimate consumed_mah and be healthy
|
|
|
|
if (!has_current() || !_state.healthy) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-17 10:02:34 -04:00
|
|
|
percentage = _soc;
|
|
|
|
return true;
|
2020-12-07 01:30:23 -04:00
|
|
|
}
|
|
|
|
|
2021-10-11 04:26:42 -03:00
|
|
|
/// get_cycle_count - return true if cycle count can be provided and fills in cycles argument
|
|
|
|
bool AP_BattMonitor_UAVCAN::get_cycle_count(uint16_t &cycles) const
|
|
|
|
{
|
|
|
|
if (_has_battery_info_aux) {
|
|
|
|
cycles = _cycle_count;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:30:13 -04:00
|
|
|
// request MPPT board to power on/off at boot as specified by BATT_OPTIONS
|
|
|
|
void AP_BattMonitor_UAVCAN::mppt_set_bootup_powered_state()
|
|
|
|
{
|
|
|
|
const uint32_t options = uint32_t(_params._options.get());
|
|
|
|
const bool on_at_boot = (options & uint32_t(AP_BattMonitor_Params::Options::MPPT_Power_On_At_Boot)) != 0;
|
|
|
|
const bool off_at_boot = (options & uint32_t(AP_BattMonitor_Params::Options::MPPT_Power_Off_At_Boot)) != 0;
|
|
|
|
|
|
|
|
if (on_at_boot) {
|
|
|
|
mppt_set_powered_state(true, true);
|
|
|
|
} else if (off_at_boot) {
|
|
|
|
mppt_set_powered_state(false, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// request MPPT board to power on/off depending upon vehicle arming state as specified by BATT_OPTIONS
|
|
|
|
void AP_BattMonitor_UAVCAN::mppt_set_armed_powered_state()
|
|
|
|
{
|
|
|
|
// check if vehicle armed state has changed
|
|
|
|
const bool vehicle_armed = hal.util->get_soft_armed();
|
|
|
|
if (vehicle_armed == _mppt.vehicle_armed_last) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_mppt.vehicle_armed_last = vehicle_armed;
|
|
|
|
|
|
|
|
// check options for arming state change events
|
|
|
|
const uint32_t options = uint32_t(_params._options.get());
|
|
|
|
const bool power_on_at_arm = (options & uint32_t(AP_BattMonitor_Params::Options::MPPT_Power_On_At_Arm)) != 0;
|
|
|
|
const bool power_off_at_disarm = (options & uint32_t(AP_BattMonitor_Params::Options::MPPT_Power_Off_At_Disarm)) != 0;
|
|
|
|
|
|
|
|
if (vehicle_armed && power_on_at_arm) {
|
|
|
|
mppt_set_powered_state(true, false);
|
|
|
|
} else if (!vehicle_armed && power_off_at_disarm) {
|
|
|
|
mppt_set_powered_state(false, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// request MPPT board to power on or off
|
|
|
|
// power_on should be true to power on the MPPT, false to power off
|
|
|
|
// force should be true to force sending the state change request to the MPPT
|
|
|
|
void AP_BattMonitor_UAVCAN::mppt_set_powered_state(bool power_on, bool force)
|
|
|
|
{
|
|
|
|
if (_ap_uavcan == nullptr || _node == nullptr || !_mppt.is_detected) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return immediately if already desired state and not forced
|
|
|
|
if ((_mppt.powered_state == power_on) && !force) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_mppt.powered_state = power_on;
|
|
|
|
_mppt.powered_state_changed = true;
|
|
|
|
|
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: powering %s", (unsigned)_instance+1, _mppt.powered_state ? "ON" : "OFF");
|
|
|
|
|
|
|
|
mppt::OutputEnable::Request request;
|
|
|
|
request.enable = _mppt.powered_state;
|
|
|
|
request.disable = !request.enable;
|
|
|
|
|
|
|
|
uavcan::ServiceClient<mppt::OutputEnable> client(*_node);
|
|
|
|
client.setCallback([](const uavcan::ServiceCallResult<mppt::OutputEnable>& handle_mppt_enable_output_response){});
|
|
|
|
client.call(_node_id, request);
|
|
|
|
}
|
|
|
|
|
|
|
|
// report changes in MPPT faults
|
2021-12-06 09:32:49 -04:00
|
|
|
void AP_BattMonitor_UAVCAN::mppt_check_and_report_faults(uint8_t fault_flags)
|
2021-12-01 21:30:13 -04:00
|
|
|
{
|
2021-12-06 09:32:49 -04:00
|
|
|
// return immediately if no changes
|
|
|
|
if (_mppt.fault_flags == fault_flags) {
|
2021-12-01 21:30:13 -04:00
|
|
|
return;
|
|
|
|
}
|
2021-12-06 09:32:49 -04:00
|
|
|
_mppt.fault_flags = fault_flags;
|
2021-12-01 21:30:13 -04:00
|
|
|
|
2021-12-06 09:32:49 -04:00
|
|
|
// handle recovery
|
|
|
|
if (_mppt.fault_flags == 0) {
|
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: OK", (unsigned)_instance+1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send battery faults via text messages
|
2021-12-01 21:30:13 -04:00
|
|
|
for (uint8_t fault_bit=0x01; fault_bit <= 0x08; fault_bit <<= 1) {
|
|
|
|
// this loop is to generate multiple messages if there are multiple concurrent faults, but also run once if there are no faults
|
2021-12-06 09:32:49 -04:00
|
|
|
if ((fault_bit & fault_flags) != 0) {
|
|
|
|
const MPPT_FaultFlags err = (MPPT_FaultFlags)fault_bit;
|
|
|
|
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Battery %u: %s", (unsigned)_instance+1, mppt_fault_string(err));
|
2021-12-01 21:30:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns string description of MPPT fault bit. Only handles single bit faults
|
2021-12-06 09:32:49 -04:00
|
|
|
const char* AP_BattMonitor_UAVCAN::mppt_fault_string(MPPT_FaultFlags fault)
|
2021-12-01 21:30:13 -04:00
|
|
|
{
|
|
|
|
switch (fault) {
|
2021-12-06 09:32:49 -04:00
|
|
|
case MPPT_FaultFlags::OVER_VOLTAGE:
|
|
|
|
return "over voltage";
|
|
|
|
case MPPT_FaultFlags::UNDER_VOLTAGE:
|
|
|
|
return "under voltage";
|
|
|
|
case MPPT_FaultFlags::OVER_CURRENT:
|
|
|
|
return "over current";
|
|
|
|
case MPPT_FaultFlags::OVER_TEMPERATURE:
|
|
|
|
return "over temp";
|
2021-12-01 21:30:13 -04:00
|
|
|
}
|
2021-12-06 09:32:49 -04:00
|
|
|
return "unknown";
|
2021-12-01 21:30:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// return mavlink fault bitmask (see MAV_BATTERY_FAULT enum)
|
|
|
|
uint32_t AP_BattMonitor_UAVCAN::get_mavlink_fault_bitmask() const
|
|
|
|
{
|
|
|
|
// return immediately if not mppt or no faults
|
|
|
|
if (!_mppt.is_detected || (_mppt.fault_flags == 0)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert mppt fault bitmask to mavlink fault bitmask
|
|
|
|
uint32_t mav_fault_bitmask = 0;
|
|
|
|
if ((_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_VOLTAGE) || (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::UNDER_VOLTAGE)) {
|
|
|
|
mav_fault_bitmask |= MAV_BATTERY_FAULT_INCOMPATIBLE_VOLTAGE;
|
|
|
|
}
|
|
|
|
if (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_CURRENT) {
|
|
|
|
mav_fault_bitmask |= MAV_BATTERY_FAULT_OVER_CURRENT;
|
|
|
|
}
|
|
|
|
if (_mppt.fault_flags & (uint8_t)MPPT_FaultFlags::OVER_TEMPERATURE) {
|
|
|
|
mav_fault_bitmask |= MAV_BATTERY_FAULT_OVER_TEMPERATURE;
|
2021-12-06 09:32:49 -04:00
|
|
|
}
|
|
|
|
return mav_fault_bitmask;
|
2021-12-01 21:30:13 -04:00
|
|
|
}
|
|
|
|
|
2018-02-16 18:32:25 -04:00
|
|
|
#endif
|