Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions msg/BatteryInfo.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Battery information
#
# Static or near-invariant battery information.
# Should be streamed at low rate.

uint64 timestamp # [us] Time since system start

uint8 id # Must match the id in the battery_status message for the same battery
char[32] serial_number # [@invalid 0 All bytes] Serial number of the battery pack in ASCII characters, 0 terminated
1 change: 1 addition & 0 deletions msg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ set(msg_files
Airspeed.msg
AirspeedWind.msg
AutotuneAttitudeControlStatus.msg
BatteryInfo.msg
ButtonEvent.msg
CameraCapture.msg
CameraStatus.msg
Expand Down
79 changes: 79 additions & 0 deletions msg/px4_msgs_old/msg/BatteryStatusV0.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Battery status
#
# Battery status information for up to 4 battery instances.
# These are populated from power module and smart battery device drivers, and one battery updated from MAVLink.
# Battery instance information is also logged and streamed in MAVLink telemetry.

uint32 MESSAGE_VERSION = 0
uint8 MAX_INSTANCES = 4

uint64 timestamp # [us] Time since system start
bool connected # Whether or not a battery is connected. For power modules this is based on a voltage threshold.
float32 voltage_v # [V] [@invalid 0] Battery voltage
float32 current_a # [A] [@invalid -1] Battery current
float32 current_average_a # [A] [@invalid -1] Battery current average (for FW average in level flight)
float32 discharged_mah # [mAh] [@invalid -1] Discharged amount
float32 remaining # [@range 0,1] [@invalid -1] Remaining capacity
float32 scale # [@range 1,] [@invalid -1] Scaling factor to compensate for lower actuation power caused by voltage sag
float32 time_remaining_s # [s] [@invalid NaN] Predicted time remaining until battery is empty under previous averaged load
float32 temperature # [°C] [@invalid NaN] Temperature of the battery
uint8 cell_count # [@invalid 0] Number of cells


uint8 source # [@enum SOURCE] Battery source
uint8 SOURCE_POWER_MODULE = 0 # Power module
uint8 SOURCE_EXTERNAL = 1 # External
uint8 SOURCE_ESCS = 2 # ESCs

uint8 priority # Zero based priority is the connection on the Power Controller V1..Vn AKA BrickN-1
uint16 capacity # [mAh] Capacity of the battery when fully charged
uint16 cycle_count # Number of discharge cycles the battery has experienced
uint16 average_time_to_empty # [minutes] Predicted remaining battery capacity based on the average rate of discharge
uint16 serial_number # Serial number of the battery pack
uint16 manufacture_date # Manufacture date, part of serial number of the battery pack. Formatted as: Day + Month×32 + (Year–1980)×512
uint16 state_of_health # [%] [@range 0, 100] State of health. FullChargeCapacity/DesignCapacity
uint16 max_error # [%] [@range 1, 100] Max error, expected margin of error in the state-of-charge calculation
uint8 id # ID number of a battery. Should be unique and consistent for the lifetime of a vehicle. 1-indexed
uint16 interface_error # Interface error counter

float32[14] voltage_cell_v # [V] [@invalid 0] Battery individual cell voltages
float32 max_cell_voltage_delta # Max difference between individual cell voltages

bool is_powering_off # Power off event imminent indication, false if unknown
bool is_required # Set if the battery is explicitly required before arming

uint8 warning # [@enum WARNING STATE] Current battery warning
uint8 WARNING_NONE = 0 # No battery low voltage warning active
uint8 WARNING_LOW = 1 # Low voltage warning
uint8 WARNING_CRITICAL = 2 # Critical voltage, return / abort immediately
uint8 WARNING_EMERGENCY = 3 # Immediate landing required
uint8 WARNING_FAILED = 4 # Battery has failed completely
uint8 STATE_UNHEALTHY = 6 # Battery is diagnosed to be defective or an error occurred, usage is discouraged / prohibited. Possible causes (faults) are listed in faults field
uint8 STATE_CHARGING = 7 # Battery is charging

uint16 faults # [@enum FAULT] Smart battery supply status/fault flags (bitmask) for health indication
uint8 FAULT_DEEP_DISCHARGE = 0 # Battery has deep discharged
uint8 FAULT_SPIKES = 1 # Voltage spikes
uint8 FAULT_CELL_FAIL= 2 # One or more cells have failed
uint8 FAULT_OVER_CURRENT = 3 # Over-current
uint8 FAULT_OVER_TEMPERATURE = 4 # Over-temperature
uint8 FAULT_UNDER_TEMPERATURE = 5 # Under-temperature fault
uint8 FAULT_INCOMPATIBLE_VOLTAGE = 6 # Vehicle voltage is not compatible with this battery (batteries on same power rail should have similar voltage)
uint8 FAULT_INCOMPATIBLE_FIRMWARE = 7 # Battery firmware is not compatible with current autopilot firmware
uint8 FAULT_INCOMPATIBLE_MODEL = 8 # Battery model is not supported by the system
uint8 FAULT_HARDWARE_FAILURE = 9 # Hardware problem
uint8 FAULT_FAILED_TO_ARM = 10 # Battery had a problem while arming
uint8 FAULT_COUNT = 11 # Counter. Keep this as last element

float32 full_charge_capacity_wh # [Wh] Compensated battery capacity
float32 remaining_capacity_wh # [Wh] Compensated battery capacity remaining
uint16 over_discharge_count # Number of battery overdischarge
float32 nominal_voltage # [V] Nominal voltage of the battery pack

float32 internal_resistance_estimate # [Ohm] Internal resistance per cell estimate
float32 ocv_estimate # [V] Open circuit voltage estimate
float32 ocv_estimate_filtered # [V] Filtered open circuit voltage estimate
float32 volt_based_soc_estimate # [@range 0, 1] Normalized volt based state of charge estimate
float32 voltage_prediction # [V] Predicted voltage
float32 prediction_error # [V] Prediction error
float32 estimation_covariance_norm # Norm of the covariance matrix
9 changes: 3 additions & 6 deletions msg/translation_node/translations/all_translations.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@

#include <translation_util.h>

//#include "example_translation_direct_v1.h"
//#include "example_translation_multi_v2.h"
//#include "example_translation_service_v1.h"

#include "translation_vehicle_status_v1.h"
#include "translation_airspeed_validated_v1.h"
#include "translation_vehicle_attitude_setpoint_v1.h"
#include "translation_arming_check_reply_v1.h"
#include "translation_battery_status_v1.h"
#include "translation_event_v1.h"
#include "translation_vehicle_attitude_setpoint_v1.h"
#include "translation_vehicle_status_v1.h"
114 changes: 114 additions & 0 deletions msg/translation_node/translations/translation_battery_status_v1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/****************************************************************************
* Copyright (c) 2025 PX4 Development Team.
* SPDX-License-Identifier: BSD-3-Clause
****************************************************************************/
#pragma once

// Translate BatteryStatus v0 <--> v1
#include <px4_msgs_old/msg/battery_status_v0.hpp>
#include <px4_msgs/msg/battery_status.hpp>

class BatteryStatusV1Translation {
public:
using MessageOlder = px4_msgs_old::msg::BatteryStatusV0;
static_assert(MessageOlder::MESSAGE_VERSION == 0);

using MessageNewer = px4_msgs::msg::BatteryStatus;
static_assert(MessageNewer::MESSAGE_VERSION == 1);

static constexpr const char* kTopic = "fmu/out/battery_status";

static void fromOlder(const MessageOlder &msg_older, MessageNewer &msg_newer) {
msg_newer.timestamp = msg_older.timestamp;
msg_newer.connected = msg_older.connected;
msg_newer.voltage_v = msg_older.voltage_v;
msg_newer.current_a = msg_older.current_a;
msg_newer.current_average_a = msg_older.current_average_a;
msg_newer.discharged_mah = msg_older.discharged_mah;
msg_newer.remaining = msg_older.remaining;
msg_newer.scale = msg_older.scale;
msg_newer.time_remaining_s = msg_older.time_remaining_s;
msg_newer.temperature = msg_older.temperature;
msg_newer.cell_count = msg_older.cell_count;
msg_newer.source = msg_older.source;
msg_newer.priority = msg_older.priority;
msg_newer.capacity = msg_older.capacity;
msg_newer.cycle_count = msg_older.cycle_count;
msg_newer.average_time_to_empty = msg_older.average_time_to_empty;
// The serial number moved to the battery_info message and is char[32] instead of uint16
msg_newer.manufacture_date = msg_older.manufacture_date;
msg_newer.state_of_health = msg_older.state_of_health;
msg_newer.max_error = msg_older.max_error;
msg_newer.id = msg_older.id;
msg_newer.interface_error = msg_older.interface_error;

for (int i = 0; i < 14; ++i) {
msg_newer.voltage_cell_v[i] = msg_older.voltage_cell_v[i];
}

msg_newer.max_cell_voltage_delta = msg_older.max_cell_voltage_delta;
msg_newer.is_powering_off = msg_older.is_powering_off;
msg_newer.is_required = msg_older.is_required;
msg_newer.warning = msg_older.warning;
msg_newer.faults = msg_older.faults;
msg_newer.full_charge_capacity_wh = msg_older.full_charge_capacity_wh;
msg_newer.remaining_capacity_wh = msg_older.remaining_capacity_wh;
msg_newer.over_discharge_count = msg_older.over_discharge_count;
msg_newer.nominal_voltage = msg_older.nominal_voltage;
msg_newer.internal_resistance_estimate = msg_older.internal_resistance_estimate;
msg_newer.ocv_estimate = msg_older.ocv_estimate;
msg_newer.ocv_estimate_filtered = msg_older.ocv_estimate_filtered;
msg_newer.volt_based_soc_estimate = msg_older.volt_based_soc_estimate;
msg_newer.voltage_prediction = msg_older.voltage_prediction;
msg_newer.prediction_error = msg_older.prediction_error;
msg_newer.estimation_covariance_norm = msg_older.estimation_covariance_norm;
}

static void toOlder(const MessageNewer &msg_newer, MessageOlder &msg_older) {
msg_older.timestamp = msg_newer.timestamp;
msg_older.connected = msg_newer.connected;
msg_older.voltage_v = msg_newer.voltage_v;
msg_older.current_a = msg_newer.current_a;
msg_older.current_average_a = msg_newer.current_average_a;
msg_older.discharged_mah = msg_newer.discharged_mah;
msg_older.remaining = msg_newer.remaining;
msg_older.scale = msg_newer.scale;
msg_older.time_remaining_s = msg_newer.time_remaining_s;
msg_older.temperature = msg_newer.temperature;
msg_older.cell_count = msg_newer.cell_count;
msg_older.source = msg_newer.source;
msg_older.priority = msg_newer.priority;
msg_older.capacity = msg_newer.capacity;
msg_older.cycle_count = msg_newer.cycle_count;
msg_older.average_time_to_empty = msg_newer.average_time_to_empty;
msg_older.serial_number = 0; // The serial number moved to the battery_info message and is char[32] instead of uint16
msg_older.manufacture_date = msg_newer.manufacture_date;
msg_older.state_of_health = msg_newer.state_of_health;
msg_older.max_error = msg_newer.max_error;
msg_older.id = msg_newer.id;
msg_older.interface_error = msg_newer.interface_error;

for (int i = 0; i < 14; ++i) {
msg_older.voltage_cell_v[i] = msg_newer.voltage_cell_v[i];
}

msg_older.max_cell_voltage_delta = msg_newer.max_cell_voltage_delta;
msg_older.is_powering_off = msg_newer.is_powering_off;
msg_older.is_required = msg_newer.is_required;
msg_older.warning = msg_newer.warning;
msg_older.faults = msg_newer.faults;
msg_older.full_charge_capacity_wh = msg_newer.full_charge_capacity_wh;
msg_older.remaining_capacity_wh = msg_newer.remaining_capacity_wh;
msg_older.over_discharge_count = msg_newer.over_discharge_count;
msg_older.nominal_voltage = msg_newer.nominal_voltage;
msg_older.internal_resistance_estimate = msg_newer.internal_resistance_estimate;
msg_older.ocv_estimate = msg_newer.ocv_estimate;
msg_older.ocv_estimate_filtered = msg_newer.ocv_estimate_filtered;
msg_older.volt_based_soc_estimate = msg_newer.volt_based_soc_estimate;
msg_older.voltage_prediction = msg_newer.voltage_prediction;
msg_older.prediction_error = msg_newer.prediction_error;
msg_older.estimation_covariance_norm = msg_newer.estimation_covariance_norm;
}
};

REGISTER_TOPIC_TRANSLATION_DIRECT(BatteryStatusV1Translation);
3 changes: 1 addition & 2 deletions msg/versioned/BatteryStatus.msg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# These are populated from power module and smart battery device drivers, and one battery updated from MAVLink.
# Battery instance information is also logged and streamed in MAVLink telemetry.

uint32 MESSAGE_VERSION = 0
uint32 MESSAGE_VERSION = 1
uint8 MAX_INSTANCES = 4

uint64 timestamp # [us] Time since system start
Expand All @@ -29,7 +29,6 @@ uint8 priority # Zero based priority is the connection on the Power Controller V
uint16 capacity # [mAh] Capacity of the battery when fully charged
uint16 cycle_count # Number of discharge cycles the battery has experienced
uint16 average_time_to_empty # [minutes] Predicted remaining battery capacity based on the average rate of discharge
uint16 serial_number # Serial number of the battery pack
uint16 manufacture_date # Manufacture date, part of serial number of the battery pack. Formatted as: Day + Month×32 + (Year–1980)×512
uint16 state_of_health # [%] [@range 0, 100] State of health. FullChargeCapacity/DesignCapacity
uint16 max_error # [%] [@range 1, 100] Max error, expected margin of error in the state-of-charge calculation
Expand Down
9 changes: 8 additions & 1 deletion src/drivers/batt_smbus/batt_smbus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ BATT_SMBUS::BATT_SMBUS(const I2CSPIDriverConfig &config, SMBus *interface) :

BATT_SMBUS::~BATT_SMBUS()
{
orb_unadvertise(_battery_info_topic);
orb_unadvertise(_batt_topic);
perf_free(_cycle);

Expand Down Expand Up @@ -165,7 +166,6 @@ void BATT_SMBUS::RunImpl()
if (ret == PX4_OK) {
new_report.capacity = _batt_capacity;
new_report.cycle_count = _cycle_count;
new_report.serial_number = _serial_number;
new_report.max_cell_voltage_delta = _max_cell_voltage_delta;
new_report.cell_count = _cell_count;
new_report.state_of_health = _state_of_health;
Expand All @@ -192,6 +192,13 @@ void BATT_SMBUS::RunImpl()
int instance = 0;
orb_publish_auto(ORB_ID(battery_status), &_batt_topic, &new_report, &instance);

battery_info_s battery_info{};
battery_info.timestamp = new_report.timestamp;
battery_info.id = new_report.id;
snprintf(battery_info.serial_number, sizeof(battery_info.serial_number), "%" PRIu16, _serial_number);
orb_publish_auto(ORB_ID(battery_info), &_battery_info_topic, &battery_info, &instance);


_last_report = new_report;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/drivers/batt_smbus/batt_smbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include <px4_platform_common/param.h>
#include <px4_platform_common/getopt.h>
#include <px4_platform_common/i2c_spi_buses.h>
#include <uORB/topics/battery_info.h>
#include <uORB/topics/battery_status.h>

#include <board_config.h>
Expand Down Expand Up @@ -242,7 +243,7 @@ class BATT_SMBUS : public I2CSPIDriver<BATT_SMBUS>
/** @param _last_report Last published report, used for test(). */
battery_status_s _last_report{};

/** @param _batt_topic uORB battery topic. */
orb_advert_t _battery_info_topic{nullptr};
orb_advert_t _batt_topic{nullptr};

/** @param _cell_count Number of series cell. */
Expand Down
9 changes: 8 additions & 1 deletion src/drivers/cyphal/Subscribers/legacy/LegacyBatteryInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ class UavcanLegacyBatteryInfoSubscriber : public UavcanDynamicPortSubscriber
bat_status.connected = bat_info.status_flags & legacy_equipment_power_BatteryInfo_1_0_STATUS_FLAG_IN_USE;
bat_status.source = 1; // External
bat_status.capacity = bat_info.full_charge_capacity_wh;
bat_status.serial_number = bat_info.model_instance_id & 0xFFFF; // Take first 16 bits
bat_status.state_of_health = bat_info.state_of_health_pct; // External
bat_status.id = bat_info.battery_id;

Expand All @@ -118,9 +117,17 @@ class UavcanLegacyBatteryInfoSubscriber : public UavcanDynamicPortSubscriber

_battery_status_pub.publish(bat_status);
print_message(ORB_ID(battery_status), bat_status);

battery_info_s battery_info{};
battery_info.timestamp = bat_status.timestamp;
battery_info.id = bat_status.id;
snprintf(battery_info.serial_number, sizeof(battery_info.serial_number), "%" PRIu32,
bat_info.model_instance_id);
_battery_info_pub.publish(battery_info);
};

private:
uORB::PublicationMulti<battery_info_s> _battery_info_pub{ORB_ID(battery_info)};
uORB::PublicationMulti<battery_status_s> _battery_status_pub{ORB_ID(battery_status)};

};
9 changes: 8 additions & 1 deletion src/drivers/cyphal/Subscribers/udral/Battery.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

#pragma once

#include <uORB/topics/battery_info.h>
#include <uORB/topics/battery_status.h>
#include <uORB/PublicationMulti.hpp>

Expand Down Expand Up @@ -120,6 +121,10 @@ class UavcanBmsSubscriber : public UavcanDynamicPortSubscriber
// TODO uORB publication rate limiting
_battery_status_pub.publish(bat_status);

_battery_info.timestamp = bat_status.timestamp;
_battery_info.id = bat_status.id;
_battery_info_pub.publish(_battery_info);

} else if (receive.metadata.port_id == _status_sub._canard_sub.port_id) {
reg_udral_service_battery_Status_0_2 bat {};
size_t bat_size_in_bytes = receive.payload_size;
Expand Down Expand Up @@ -163,7 +168,7 @@ class UavcanBmsSubscriber : public UavcanDynamicPortSubscriber

bat_status.capacity = parameters.design_capacity.coulomb / 3.6f; // Coulomb -> mAh
bat_status.cycle_count = parameters.cycle_count;
bat_status.serial_number = parameters.unique_id & 0xFFFF;
snprintf(_battery_info.serial_number, sizeof(_battery_info.serial_number), "%" PRIu64, parameters.unique_id);
bat_status.state_of_health = parameters.state_of_health_pct;
bat_status.max_error = 1; // UAVCAN didn't spec'ed this, but we're optimistic
bat_status.id = 0; //TODO instancing
Expand All @@ -174,6 +179,7 @@ class UavcanBmsSubscriber : public UavcanDynamicPortSubscriber
private:
float _nominal_voltage = NAN;

uORB::PublicationMulti<battery_info_s> _battery_info_pub{ORB_ID(battery_info)};
uORB::PublicationMulti<battery_status_s> _battery_status_pub{ORB_ID(battery_status)};

SubjectSubscription _status_sub;
Expand All @@ -182,5 +188,6 @@ class UavcanBmsSubscriber : public UavcanDynamicPortSubscriber
const char *_status_name = "battery_status";
const char *_parameters_name = "battery_parameters";

battery_info_s _battery_info{};
battery_status_s bat_status {0};
};
7 changes: 6 additions & 1 deletion src/drivers/smart_battery/batmon/batmon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ void Batmon::RunImpl()
if (ret == PX4_OK) {
new_report.capacity = _batt_capacity;
new_report.cycle_count = _cycle_count;
new_report.serial_number = _serial_number;
new_report.max_cell_voltage_delta = _max_cell_voltage_delta;
new_report.cell_count = _cell_count;
new_report.state_of_health = _state_of_health;
Expand Down Expand Up @@ -220,6 +219,12 @@ void Batmon::RunImpl()
orb_publish_auto(ORB_ID(battery_status), &_batt_topic, &new_report, &instance);

_last_report = new_report;

battery_info_s battery_info{};
battery_info.timestamp = new_report.timestamp;
battery_info.id = new_report.id;
snprintf(battery_info.serial_number, sizeof(battery_info.serial_number), "%" PRIu16, _serial_number);
orb_publish_auto(ORB_ID(battery_info), &_battery_info_topic, &battery_info, &instance);
}
}

Expand Down
Loading
Loading