mirror of
https://github.com/google/pebble
synced 2026-10-18 01:04:12 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/math.h"
|
||||
#include "util/ratio.h"
|
||||
#include "util/size.h"
|
||||
|
||||
typedef struct VoltagePoint {
|
||||
uint8_t percent;
|
||||
uint16_t voltage;
|
||||
} VoltagePoint;
|
||||
|
||||
// TODO: Move these curves somewhere else. Related: PBL-21049
|
||||
|
||||
#ifdef PLATFORM_TINTIN
|
||||
// When the voltage drops below these (mV), the watch will start heading for standby (after delay)
|
||||
#define BATTERY_CRITICAL_VOLTAGE_CHARGING 3200
|
||||
#define BATTERY_CRITICAL_VOLTAGE_DISCHARGING 3100
|
||||
// Battery Tables for non-Snowy
|
||||
static VoltagePoint discharge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_DISCHARGING},
|
||||
{2, 3410},
|
||||
{5, 3600},
|
||||
{10, 3670},
|
||||
{20, 3710},
|
||||
{30, 3745},
|
||||
{40, 3775},
|
||||
{50, 3810},
|
||||
{60, 3860},
|
||||
{70, 3925},
|
||||
{80, 4000},
|
||||
{90, 4080},
|
||||
{100, 4120},
|
||||
};
|
||||
|
||||
static const VoltagePoint charge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_CHARGING},
|
||||
{5, 3725},
|
||||
{10, 3750},
|
||||
{20, 3790},
|
||||
{30, 3830},
|
||||
{40, 3845},
|
||||
{50, 3870},
|
||||
{60, 3905},
|
||||
{70, 3970},
|
||||
{80, 4025},
|
||||
{90, 4090},
|
||||
{100, 4130},
|
||||
};
|
||||
#elif BOARD_SNOWY_S3 || PLATFORM_ROBERT
|
||||
// When the voltage drops below these (mV), the watch will start heading for standby (after delay)
|
||||
#define BATTERY_CRITICAL_VOLTAGE_CHARGING 3700
|
||||
#define BATTERY_CRITICAL_VOLTAGE_DISCHARGING 3300
|
||||
// Battery Tables for Bobby Smiles
|
||||
static VoltagePoint discharge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_DISCHARGING},
|
||||
{2, 3465},
|
||||
{5, 3615},
|
||||
{10, 3685},
|
||||
{20, 3725},
|
||||
{30, 3760},
|
||||
{40, 3795},
|
||||
{50, 3830},
|
||||
{60, 3885},
|
||||
{70, 3955},
|
||||
{80, 4065},
|
||||
{90, 4160},
|
||||
{100, 4250},
|
||||
};
|
||||
|
||||
static const VoltagePoint charge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_CHARGING},
|
||||
{2, 3850},
|
||||
{5, 3935},
|
||||
{10, 4000},
|
||||
{20, 4040},
|
||||
{30, 4090},
|
||||
{40, 4145},
|
||||
{50, 4175},
|
||||
{60, 4225},
|
||||
{70, 4250},
|
||||
};
|
||||
#elif PLATFORM_SNOWY || PLATFORM_CALCULUS
|
||||
// When the voltage drops below these (mV), the watch will start heading for standby (after delay)
|
||||
#define BATTERY_CRITICAL_VOLTAGE_CHARGING 3500
|
||||
#define BATTERY_CRITICAL_VOLTAGE_DISCHARGING 3300
|
||||
// Battery Tables for Snowy
|
||||
static VoltagePoint discharge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_DISCHARGING},
|
||||
{2, 3500},
|
||||
{5, 3600},
|
||||
{10, 3640},
|
||||
{20, 3690},
|
||||
{30, 3730},
|
||||
{40, 3750},
|
||||
{50, 3790},
|
||||
{60, 3840},
|
||||
{70, 3910},
|
||||
{80, 4000},
|
||||
{90, 4120},
|
||||
{100, 4250},
|
||||
};
|
||||
|
||||
static const VoltagePoint charge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_CHARGING},
|
||||
{10, 3970},
|
||||
{20, 4020},
|
||||
{30, 4060},
|
||||
{40, 4090},
|
||||
{50, 4130},
|
||||
{60, 4190},
|
||||
{70, 4250},
|
||||
};
|
||||
#elif PLATFORM_SPALDING
|
||||
// When the voltage drops below these (mV), the watch will start heading for standby (after delay)
|
||||
#define BATTERY_CRITICAL_VOLTAGE_CHARGING 3700
|
||||
#define BATTERY_CRITICAL_VOLTAGE_DISCHARGING 3300
|
||||
// Battery Tables for Spalding
|
||||
static VoltagePoint discharge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_DISCHARGING},
|
||||
{2, 3470},
|
||||
{5, 3600},
|
||||
{10, 3680},
|
||||
{20, 3720},
|
||||
{30, 3760},
|
||||
{40, 3790},
|
||||
{50, 3830},
|
||||
{60, 3875},
|
||||
{70, 3950},
|
||||
{80, 4050},
|
||||
{90, 4130},
|
||||
{100, 4250},
|
||||
};
|
||||
|
||||
static const VoltagePoint charge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_CHARGING},
|
||||
{10, 3950},
|
||||
{20, 3990},
|
||||
{30, 4030},
|
||||
{40, 4090},
|
||||
{50, 4180},
|
||||
{60, 4230},
|
||||
{70, 4250},
|
||||
};
|
||||
|
||||
#elif PLATFORM_SILK
|
||||
// When the voltage drops below these (mV), the watch will start heading for standby (after delay)
|
||||
#define BATTERY_CRITICAL_VOLTAGE_CHARGING 3550
|
||||
#define BATTERY_CRITICAL_VOLTAGE_DISCHARGING 3300
|
||||
// Battery Tables for Silk
|
||||
static VoltagePoint discharge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_DISCHARGING},
|
||||
{2, 3490},
|
||||
{5, 3615},
|
||||
{10, 3655},
|
||||
{20, 3700},
|
||||
{30, 3735},
|
||||
{40, 3760},
|
||||
{50, 3800},
|
||||
{60, 3855},
|
||||
{70, 3935},
|
||||
{80, 4025},
|
||||
{90, 4120},
|
||||
{100, 4230}
|
||||
};
|
||||
|
||||
static const VoltagePoint charge_curve[] = {
|
||||
{0, BATTERY_CRITICAL_VOLTAGE_CHARGING},
|
||||
{2, 3570},
|
||||
{5, 3600},
|
||||
{10, 3645},
|
||||
{20, 3730},
|
||||
{30, 3800},
|
||||
{40, 3860},
|
||||
{50, 3915},
|
||||
{60, 3970},
|
||||
{70, 4030},
|
||||
{80, 4095},
|
||||
{90, 4175},
|
||||
{100, 4260}
|
||||
};
|
||||
|
||||
#else
|
||||
#error "No battery curve for platform!"
|
||||
#endif
|
||||
|
||||
static const uint8_t NUM_DISCHARGE_POINTS = ARRAY_LENGTH(discharge_curve);
|
||||
static const uint8_t NUM_CHARGE_POINTS = ARRAY_LENGTH(charge_curve);
|
||||
|
||||
static int s_battery_compensation_values[BATTERY_CURVE_COMPENSATE_COUNT];
|
||||
|
||||
// Shifts the 100% reference on the discharge curve, as long as it
|
||||
// doesn't drop below the next highest point.
|
||||
void battery_curve_set_full_voltage(uint16_t voltage) {
|
||||
voltage = MAX(voltage, discharge_curve[NUM_DISCHARGE_POINTS-2].voltage + 1);
|
||||
discharge_curve[NUM_DISCHARGE_POINTS-1].voltage = voltage;
|
||||
}
|
||||
|
||||
static uint32_t prv_lookup_percent_by_voltage(
|
||||
int battery_mv, bool is_charging, uint32_t scaling_factor) {
|
||||
VoltagePoint const * const battery_curve = (is_charging) ? charge_curve : discharge_curve;
|
||||
uint8_t const num_curve_points = (is_charging) ? NUM_CHARGE_POINTS : NUM_DISCHARGE_POINTS;
|
||||
|
||||
// Constrain the voltage between the min and max points of the curve
|
||||
if (battery_mv <= battery_curve[0].voltage) {
|
||||
return battery_curve[0].percent * scaling_factor;
|
||||
} else if (battery_mv >= battery_curve[num_curve_points-1].voltage) {
|
||||
return battery_curve[num_curve_points-1].percent * scaling_factor;
|
||||
}
|
||||
|
||||
// search through the curves for the next charge level...
|
||||
uint32_t charge_index;
|
||||
for (charge_index = num_curve_points-2;
|
||||
(charge_index > 0) && (battery_mv < battery_curve[charge_index].voltage);
|
||||
--charge_index) {
|
||||
}
|
||||
|
||||
// linearly interpolate between battery_curve[charge_index] and battery_curve[charge_index+1]
|
||||
uint32_t delta_mv = (battery_mv - battery_curve[charge_index].voltage);
|
||||
uint32_t delta_next_mv = (battery_curve[charge_index + 1].voltage -
|
||||
battery_curve[charge_index].voltage);
|
||||
uint32_t delta_next_percent = (battery_curve[charge_index + 1].percent -
|
||||
battery_curve[charge_index].percent);
|
||||
|
||||
uint32_t start_percent = battery_curve[charge_index].percent * scaling_factor;
|
||||
delta_next_percent *= scaling_factor;
|
||||
|
||||
uint32_t charge_percent =
|
||||
(((uint64_t)delta_next_percent * delta_mv) / delta_next_mv) + start_percent;
|
||||
return charge_percent;
|
||||
}
|
||||
|
||||
uint32_t battery_curve_lookup_percent_by_voltage(uint32_t battery_mv, bool is_charging) {
|
||||
return prv_lookup_percent_by_voltage(battery_mv, is_charging, 1);
|
||||
}
|
||||
|
||||
static uint32_t prv_sample_scaled_charge_percent(
|
||||
uint32_t battery_mv, bool is_charging, uint32_t scaling_factor) {
|
||||
int compensate = 0; // We compensate 5% during rounding, so don't do here
|
||||
|
||||
for (int i = 0; i < BATTERY_CURVE_COMPENSATE_COUNT; ++i) {
|
||||
compensate += s_battery_compensation_values[i];
|
||||
}
|
||||
|
||||
return prv_lookup_percent_by_voltage(battery_mv + compensate, is_charging, scaling_factor);
|
||||
}
|
||||
|
||||
uint32_t battery_curve_sample_ratio32_charge_percent(uint32_t battery_mv, bool is_charging) {
|
||||
const uint32_t scaling_factor = ratio32_from_percent(100) / 100 + 1;
|
||||
return prv_sample_scaled_charge_percent(battery_mv, is_charging, scaling_factor);
|
||||
}
|
||||
|
||||
void battery_curve_set_compensation(BatteryCurveVoltageCompensationKey key, int mv) {
|
||||
s_battery_compensation_values[key] = mv;
|
||||
}
|
||||
|
||||
// This is used by unit tests and QEMU
|
||||
uint32_t battery_curve_lookup_voltage_by_percent(uint32_t percent, bool is_charging) {
|
||||
VoltagePoint const * const battery_curve = (is_charging) ? charge_curve : discharge_curve;
|
||||
uint32_t const num_curve_points = (is_charging) ? NUM_CHARGE_POINTS : NUM_DISCHARGE_POINTS;
|
||||
|
||||
// Clip if above curve upper bound
|
||||
if (percent > battery_curve[num_curve_points-1].percent) {
|
||||
return battery_curve[num_curve_points-1].voltage;
|
||||
}
|
||||
|
||||
// search through the curves for the next charge level...
|
||||
uint32_t charge_index;
|
||||
for (charge_index = num_curve_points-2;
|
||||
(charge_index > 0) && (percent < battery_curve[charge_index].percent);
|
||||
--charge_index) {
|
||||
}
|
||||
|
||||
// linearly interpolate between battery_curve[charge_index] and battery_curve[charge_index+1]
|
||||
uint32_t delta_next_mv = (battery_curve[charge_index+1].voltage -
|
||||
battery_curve[charge_index].voltage);
|
||||
uint32_t delta_next_percent = (battery_curve[charge_index+1].percent -
|
||||
battery_curve[charge_index].percent);
|
||||
uint32_t battery_mv = battery_curve[charge_index].voltage +
|
||||
((percent - battery_curve[charge_index].percent) * delta_next_mv) /
|
||||
delta_next_percent;
|
||||
return battery_mv;
|
||||
}
|
||||
|
||||
//! This call is used internally with PreciseBatteryChargeState
|
||||
//! So must remove low_power_threshold to get correct remaining hours
|
||||
//! before low_power_mode is triggered
|
||||
uint32_t battery_curve_get_hours_remaining(uint32_t percent_remaining) {
|
||||
if (percent_remaining <= BOARD_CONFIG_POWER.low_power_threshold) {
|
||||
return 0;
|
||||
}
|
||||
percent_remaining -= BOARD_CONFIG_POWER.low_power_threshold;
|
||||
return ((BOARD_CONFIG_POWER.battery_capacity_hours * percent_remaining) / 100);
|
||||
}
|
||||
|
||||
//! This call is used internally with PreciseBatteryChargeState
|
||||
//! So must add low_power_threshold to get percentage in terms of
|
||||
//! PreciseBatteryChargeState (which includes low_power_mode)
|
||||
uint32_t battery_curve_get_percent_remaining(uint32_t hours) {
|
||||
return ((hours * 100) / BOARD_CONFIG_POWER.battery_capacity_hours) +
|
||||
BOARD_CONFIG_POWER.low_power_threshold;
|
||||
}
|
||||
|
||||
// for unit tests and analytics
|
||||
int32_t battery_curve_lookup_percent_with_scaling_factor(
|
||||
int battery_mv, bool is_charging, uint32_t scaling_factor) {
|
||||
return (prv_lookup_percent_by_voltage(battery_mv, is_charging, scaling_factor));
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Handles battery mV <-> % conversion
|
||||
|
||||
typedef enum {
|
||||
BATTERY_CURVE_COMPENSATE_STATUS_LED,
|
||||
BATTERY_CURVE_COMPENSATE_COUNT
|
||||
} BatteryCurveVoltageCompensationKey;
|
||||
|
||||
//! Set compensation value to be applied to battery voltage when calculating percentage charge.
|
||||
//! For example, if an LED is constantly on, the voltage being measured is going to drop due to the
|
||||
//! internal resistance of the battery.
|
||||
void battery_curve_set_compensation(BatteryCurveVoltageCompensationKey key, int mv);
|
||||
|
||||
void battery_curve_set_full_voltage(uint16_t voltage);
|
||||
|
||||
//! Returns the corresponding battery percentage as a ratio32.
|
||||
uint32_t battery_curve_sample_ratio32_charge_percent(uint32_t battery_mv, bool is_charging);
|
||||
|
||||
uint32_t battery_curve_lookup_percent_by_voltage(uint32_t battery_mv, bool is_charging);
|
||||
|
||||
int32_t battery_curve_lookup_percent_with_scaling_factor(
|
||||
int battery_mv, bool is_charging, uint32_t scaling_factor);
|
||||
|
||||
uint32_t battery_curve_get_hours_remaining(uint32_t percent_remaining);
|
||||
|
||||
uint32_t battery_curve_get_percent_remaining(uint32_t hours);
|
||||
|
||||
// This is used by unit tests and QEMU
|
||||
uint32_t battery_curve_lookup_voltage_by_percent(uint32_t percent, bool is_charging);
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/util/standby.h"
|
||||
#include "services/common/firmware_update.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/ratio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BATT_LOG_COLOR LOG_COLOR_YELLOW
|
||||
|
||||
// State machine stuff
|
||||
|
||||
typedef void (*Action)(void);
|
||||
|
||||
typedef struct PowerState {
|
||||
Action enter;
|
||||
Action exit;
|
||||
} PowerState;
|
||||
|
||||
typedef enum {
|
||||
PowerStateGood,
|
||||
PowerStateLowPower,
|
||||
PowerStateCritical,
|
||||
PowerStateStandby
|
||||
} PowerStateID;
|
||||
|
||||
static void prv_enter_lpm(void);
|
||||
static void prv_exit_lpm(void);
|
||||
static void prv_begin_standby_timer(void);
|
||||
static void prv_enter_standby(void);
|
||||
static void prv_exit_critical(void);
|
||||
|
||||
static const PowerState power_states[] = {
|
||||
[PowerStateGood] = { 0 },
|
||||
[PowerStateLowPower] = { .enter = prv_enter_lpm, .exit = prv_exit_lpm },
|
||||
[PowerStateCritical] = { .enter = prv_begin_standby_timer, .exit = prv_exit_critical },
|
||||
[PowerStateStandby] = { .enter = prv_enter_standby }
|
||||
};
|
||||
|
||||
////////////////////////
|
||||
// Business logic
|
||||
static TimerID s_standby_timer_id = TIMER_INVALID_ID;
|
||||
T_STATIC PowerStateID s_power_state;
|
||||
static bool s_low_on_first_run;
|
||||
static bool s_first_run;
|
||||
|
||||
static void prv_transition(PowerStateID next_state) {
|
||||
if (next_state == s_power_state) {
|
||||
return;
|
||||
}
|
||||
PowerStateID old_state = s_power_state;
|
||||
s_power_state = next_state;
|
||||
if (power_states[old_state].exit) {
|
||||
power_states[old_state].exit();
|
||||
}
|
||||
if (power_states[next_state].enter) {
|
||||
power_states[next_state].enter();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_enter_lpm(void) {
|
||||
#ifndef BATTERY_DEBUG
|
||||
if (!firmware_update_is_in_progress()) {
|
||||
low_power_enter();
|
||||
}
|
||||
#endif
|
||||
PBL_LOG_COLOR(LOG_LEVEL_INFO, BATT_LOG_COLOR, "Battery low: enter low power mode");
|
||||
}
|
||||
|
||||
static void prv_resume_normal_operation(void) {
|
||||
low_power_exit();
|
||||
PBL_LOG_COLOR(LOG_LEVEL_INFO, BATT_LOG_COLOR, "Battery good: resume normal operation");
|
||||
}
|
||||
|
||||
static void prv_exit_critical(void) {
|
||||
// Checking the state here is a bit of a hack because the state machine does not have proper
|
||||
// transition actions, only entry/exit actions.
|
||||
// We check that the state is PowerStateGood because the state machine does not transition through
|
||||
// all states in between the new and old states in a transition.
|
||||
if (s_power_state == PowerStateGood) {
|
||||
prv_resume_normal_operation();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_exit_lpm(void) {
|
||||
// Checking the state here is a bit of a hack because the state machine does not have proper
|
||||
// transition actions, only entry/exit actions
|
||||
if (s_power_state == PowerStateGood) {
|
||||
prv_resume_normal_operation();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_standby_timer_callback(void* data) {
|
||||
// FIXME This is so broken: battery_state_force_update schedules a new timer callback to execute
|
||||
// immediately, which then pends a background task callback to perform the update, so this will
|
||||
// never update before we check the power_state.
|
||||
battery_state_force_update();
|
||||
if (s_power_state == PowerStateCritical) {
|
||||
// Still critical after timeout, transition to standby
|
||||
prv_transition(PowerStateStandby);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_begin_standby_timer(void) {
|
||||
PBL_LOG_COLOR(LOG_LEVEL_INFO, BATT_LOG_COLOR, "Battery critical: begin standby timer");
|
||||
// If the watch was already running, give them 30s, otherwise just 2s.
|
||||
uint32_t standby_timeout = (s_first_run) ? 2000: 30000;
|
||||
new_timer_start(s_standby_timer_id, standby_timeout,
|
||||
prv_standby_timer_callback, NULL, 0 /*flags*/);
|
||||
}
|
||||
|
||||
static void system_task_handle_battery_critical(void* data) {
|
||||
PBL_LOG_COLOR(LOG_LEVEL_INFO, BATT_LOG_COLOR, "Battery critical: go to standby mode");
|
||||
if (low_power_is_active()) {
|
||||
low_power_standby();
|
||||
} else {
|
||||
enter_standby(RebootReasonCode_LowBattery);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_enter_standby(void) {
|
||||
system_task_add_callback(system_task_handle_battery_critical, NULL);
|
||||
}
|
||||
|
||||
static void prv_log_battery_state(PreciseBatteryChargeState state) {
|
||||
const uint16_t k_min_percent_diff = 5;
|
||||
const uint16_t percent = ratio32_to_percent(state.charge_percent);
|
||||
|
||||
union LoggingBattState{
|
||||
struct {
|
||||
uint16_t is_charging:1;
|
||||
uint16_t is_plugged:1;
|
||||
uint16_t percent:14;
|
||||
};
|
||||
uint16_t all;
|
||||
};
|
||||
static union LoggingBattState s_prev_batt_state;
|
||||
|
||||
union LoggingBattState new_batt_state = {
|
||||
.percent = percent / k_min_percent_diff,
|
||||
.is_charging = state.is_charging,
|
||||
.is_plugged = state.is_plugged,
|
||||
};
|
||||
|
||||
if ((percent < BOARD_CONFIG_POWER.low_power_threshold) ||
|
||||
(s_prev_batt_state.all != new_batt_state.all) ||
|
||||
s_first_run) {
|
||||
s_prev_batt_state.all = new_batt_state.all;
|
||||
PBL_LOG_COLOR(LOG_LEVEL_INFO, BATT_LOG_COLOR, "Percent: %d Charging: %d Plugged: %d",
|
||||
percent, state.is_charging, state.is_plugged);
|
||||
}
|
||||
}
|
||||
|
||||
void battery_monitor_handle_state_change_event(PreciseBatteryChargeState state) {
|
||||
// Update Critical/Low Power Mode
|
||||
|
||||
// Standby behaviour, as gleaned from the previous implementation:
|
||||
// Once the battery voltage falls below exactly 0%, the standby lockout is displayed.
|
||||
// If the USB cable is disconnected, the standby timer starts. This standby delay is 2s
|
||||
// (if at first start), otherwise it is 30s (if the watch was already running).
|
||||
// The shutdown can be averted if the watch is plugged in before the timer expires.
|
||||
// Similarly, if the battery voltage has rebounded when the timer expires, the shutdown
|
||||
// will not occur.
|
||||
|
||||
bool critical = (state.charge_percent == 0) && !state.is_charging;
|
||||
|
||||
#ifndef RECOVERY_FW
|
||||
const uint32_t LOW_POWER_PERCENT = ratio32_from_percent(BOARD_CONFIG_POWER.low_power_threshold);
|
||||
|
||||
bool low_power = !state.is_charging && (state.charge_percent <= LOW_POWER_PERCENT);
|
||||
s_low_on_first_run = s_low_on_first_run || (low_power && s_first_run);
|
||||
#else
|
||||
const uint32_t PRF_LOW_POWER_THRESHOLD_PERCENT = ratio32_from_percent(5);
|
||||
|
||||
// We want to keep the LPM UI up until we've hit 10% regardless of charging
|
||||
bool low_power = state.charge_percent < PRF_LOW_POWER_THRESHOLD_PERCENT;
|
||||
s_low_on_first_run = false;
|
||||
#endif
|
||||
|
||||
PowerStateID new_state;
|
||||
|
||||
if (critical || s_low_on_first_run) {
|
||||
new_state = PowerStateCritical;
|
||||
} else if (low_power) {
|
||||
new_state = PowerStateLowPower;
|
||||
} else {
|
||||
new_state = PowerStateGood;
|
||||
}
|
||||
|
||||
// All state transitions are valid in this state machine.
|
||||
prv_transition(new_state);
|
||||
|
||||
prv_log_battery_state(state);
|
||||
|
||||
s_first_run = false;
|
||||
}
|
||||
|
||||
void battery_monitor_init(void) {
|
||||
s_standby_timer_id = new_timer_create();
|
||||
s_power_state = PowerStateGood;
|
||||
s_low_on_first_run = false;
|
||||
s_first_run = true;
|
||||
|
||||
// Initialize driver interface
|
||||
battery_state_init();
|
||||
}
|
||||
|
||||
bool battery_monitor_critical_lockout(void) {
|
||||
// critical or low on first run
|
||||
return s_power_state == PowerStateCritical;
|
||||
}
|
||||
|
||||
TimerID battery_monitor_get_standby_timer_id(void) {
|
||||
return s_standby_timer_id;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "services/common/battery/battery_state.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
// The battery monitor handles power state and associated service control, in response to battery
|
||||
// state changes. This includes low power and critical modes.
|
||||
|
||||
void battery_monitor_init(void);
|
||||
void battery_monitor_handle_state_change_event(PreciseBatteryChargeState state);
|
||||
|
||||
// Use the battery state to determine if UI elements should be locked out
|
||||
// because the battery is too low
|
||||
bool battery_monitor_critical_lockout(void);
|
||||
|
||||
// For unit tests
|
||||
TimerID battery_monitor_get_standby_timer_id(void);
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/battery/battery_state.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "debug/power_tracking.h"
|
||||
#include "drivers/battery.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
#include "util/ratio.h"
|
||||
|
||||
#ifdef DEBUG_BATTERY_STATE
|
||||
#define BATTERY_SAMPLE_RATE_MS 1000
|
||||
#else
|
||||
#define BATTERY_SAMPLE_RATE_MS (60 * 1000)
|
||||
#endif
|
||||
|
||||
typedef void (*EntryFunc)(void);
|
||||
|
||||
typedef enum {
|
||||
ConnectionStateInvalid,
|
||||
ConnectionStateChargingPlugged,
|
||||
ConnectionStateDischargingPlugged,
|
||||
ConnectionStateDischargingUnplugged
|
||||
} ConnectionStateID;
|
||||
|
||||
typedef struct ConnectionState {
|
||||
EntryFunc enter;
|
||||
} ConnectionState;
|
||||
|
||||
static void prv_update_plugged_change(void);
|
||||
static void prv_update_done_charging(void);
|
||||
|
||||
static const ConnectionState s_transitions[] = {
|
||||
[ConnectionStateChargingPlugged] = { .enter = prv_update_plugged_change },
|
||||
[ConnectionStateDischargingPlugged] = { .enter = prv_update_done_charging },
|
||||
[ConnectionStateDischargingUnplugged] = { .enter = prv_update_plugged_change }
|
||||
};
|
||||
|
||||
typedef struct BatteryState {
|
||||
uint64_t init_time;
|
||||
uint32_t percent;
|
||||
uint16_t voltage;
|
||||
uint8_t skip_count;
|
||||
ConnectionStateID connection;
|
||||
} BatteryState;
|
||||
|
||||
static BatteryState s_last_battery_state;
|
||||
static TimerID s_periodic_timer_id = TIMER_INVALID_ID;
|
||||
static int s_analytics_previous_mv = 0;
|
||||
|
||||
static void prv_schedule_update(uint32_t delay, bool force_update);
|
||||
PreciseBatteryChargeState prv_get_precise_charge_state(const BatteryState *state);
|
||||
|
||||
static void prv_transition(BatteryState *state, ConnectionStateID next_state) {
|
||||
state->connection = next_state;
|
||||
s_transitions[state->connection].enter();
|
||||
}
|
||||
|
||||
static void prv_update_plugged_change(void) {
|
||||
// If the connection state changed or we finished charging, reset the filter since we're
|
||||
// probably switching to a new curve.
|
||||
battery_state_reset_filter();
|
||||
|
||||
|
||||
bool is_charging = battery_charge_controller_thinks_we_are_charging();
|
||||
if (is_charging) {
|
||||
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_BATTERY_CHARGE_TIME, AnalyticsClient_System);
|
||||
} else {
|
||||
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_BATTERY_CHARGE_TIME);
|
||||
}
|
||||
|
||||
bool is_plugged = battery_is_usb_connected();
|
||||
if (is_plugged) {
|
||||
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_BATTERY_PLUGGED_TIME, AnalyticsClient_System);
|
||||
} else {
|
||||
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_BATTERY_PLUGGED_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_update_done_charging(void) {
|
||||
prv_update_plugged_change();
|
||||
|
||||
// Amount in mV to drop the "Full" voltage by to briefly stay at 100% once unplugged
|
||||
const uint16_t BATTERY_FULL_FUDGE_AMOUNT = 10;
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Done charging - Updating curve");
|
||||
battery_curve_set_full_voltage(s_last_battery_state.voltage - BATTERY_FULL_FUDGE_AMOUNT);
|
||||
}
|
||||
|
||||
static void battery_state_put_change_event(PreciseBatteryChargeState state) {
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_BATTERY_STATE_CHANGE_EVENT,
|
||||
.battery_state = {
|
||||
.new_state = state,
|
||||
},
|
||||
};
|
||||
event_put(&e);
|
||||
}
|
||||
|
||||
void battery_state_reset_filter(void) {
|
||||
s_last_battery_state.voltage = battery_get_millivolts();
|
||||
// Reset the stablization timer in case we encountered a current spike during the reset
|
||||
s_last_battery_state.init_time = rtc_get_ticks();
|
||||
}
|
||||
|
||||
static uint32_t prv_filter_voltage(uint32_t avg_mv, uint32_t battery_mv) {
|
||||
// Basic low-pass filter - See PBL-23637
|
||||
const uint8_t VOLTAGE_FILTER_BETA = 2;
|
||||
uint32_t avg = (avg_mv << VOLTAGE_FILTER_BETA);
|
||||
avg -= avg_mv;
|
||||
avg += battery_mv;
|
||||
return avg >> VOLTAGE_FILTER_BETA;
|
||||
}
|
||||
|
||||
static bool prv_is_stable(const BatteryState *state) {
|
||||
// After a reboot, we typically source a lot of current which can drastically impact
|
||||
// our mV readings due to the internal resistance of the battery. We use the
|
||||
// system_likely_stabilized flag as an indicator of how trustworthy our readings are
|
||||
const uint64_t STABLE_TICKS = 3 * 60 * RTC_TICKS_HZ;
|
||||
uint64_t elapsed_ticks = rtc_get_ticks() - state->init_time;
|
||||
return elapsed_ticks > STABLE_TICKS;
|
||||
}
|
||||
|
||||
static ConnectionStateID prv_get_connection_state(void) {
|
||||
const bool charging = battery_charge_controller_thinks_we_are_charging();
|
||||
const bool plugged_in = battery_is_usb_connected();
|
||||
|
||||
if (plugged_in) {
|
||||
if (charging) {
|
||||
return ConnectionStateChargingPlugged;
|
||||
} else {
|
||||
return ConnectionStateDischargingPlugged;
|
||||
}
|
||||
} else {
|
||||
if (charging) {
|
||||
// Since we can't be charging and disconnected,
|
||||
// just log a warning and pretend we aren't charging.
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "PMIC reported charging while unplugged - ignoring");
|
||||
}
|
||||
return ConnectionStateDischargingUnplugged;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_update_state(void *force_update) {
|
||||
const uint8_t MAX_SAMPLE_SKIPS = 5;
|
||||
bool forced = (bool)force_update;
|
||||
// Large current draws will cause the voltage supplied by the battery to
|
||||
// droop. We try to only sample the battery when there is minimal
|
||||
// activity. We look to see if stop mode is allowed because this is a good
|
||||
// indicator that no peripherals are in use (i.e vibe, backlight, etc)
|
||||
if ((s_last_battery_state.skip_count < MAX_SAMPLE_SKIPS) &&
|
||||
!forced && !stop_mode_is_allowed()) {
|
||||
s_last_battery_state.skip_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_last_battery_state.skip_count == MAX_SAMPLE_SKIPS) {
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_BATTERY_SAMPLE_SKIP_COUNT_EXCEEDED,
|
||||
AnalyticsClient_System);
|
||||
}
|
||||
s_last_battery_state.skip_count = 0;
|
||||
|
||||
// Driver communication
|
||||
|
||||
bool state_changed = false;
|
||||
ConnectionStateID next_state = prv_get_connection_state();
|
||||
if (s_last_battery_state.connection != next_state) {
|
||||
// Do not allow DischargingPlugged -> ChargingPlugged state
|
||||
if ((s_last_battery_state.connection != ConnectionStateDischargingPlugged) ||
|
||||
(next_state != ConnectionStateChargingPlugged)) {
|
||||
prv_transition(&s_last_battery_state, next_state);
|
||||
state_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
s_last_battery_state.voltage = prv_filter_voltage(s_last_battery_state.voltage,
|
||||
battery_get_millivolts());
|
||||
bool charging = (s_last_battery_state.connection == ConnectionStateChargingPlugged);
|
||||
|
||||
// Update Percent & Filtering
|
||||
|
||||
const uint32_t ALWAYS_UPDATE_THRESHOLD = ratio32_from_percent(10);
|
||||
bool likely_stable = prv_is_stable(&s_last_battery_state);
|
||||
|
||||
uint32_t new_charge_percent =
|
||||
battery_curve_sample_ratio32_charge_percent(s_last_battery_state.voltage, charging);
|
||||
#ifndef TARGET_QEMU
|
||||
// If QEMU, allow updates to always occur for ease of testing otherwise
|
||||
// Allow updates iff:
|
||||
// - We are charging
|
||||
// - We are discharging and:
|
||||
// - The readings have stabilized and the battery percent did not go up
|
||||
// - The readings have not yet stablized
|
||||
// TL;DR: Allow updates unless we're stable and discharging but the % went up.
|
||||
if (!charging && likely_stable &&
|
||||
new_charge_percent > s_last_battery_state.percent) {
|
||||
// It's okay to return early since any connection/plugged changes will reset the filter,
|
||||
// so we won't catch those.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
s_last_battery_state.percent = new_charge_percent;
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "mV Raw: %"PRIu16" Ratio: %"PRIu32" Percent: %"PRIu32,
|
||||
s_last_battery_state.voltage, s_last_battery_state.percent,
|
||||
ratio32_to_percent(s_last_battery_state.percent));
|
||||
|
||||
PWR_TRACK_BATT(charging ? "CHARGING" : "DISCHARGING", s_last_battery_state.voltage);
|
||||
|
||||
if (forced || likely_stable || s_last_battery_state.percent <= ALWAYS_UPDATE_THRESHOLD ||
|
||||
charging || state_changed) {
|
||||
battery_state_put_change_event(prv_get_precise_charge_state(&s_last_battery_state));
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_update_callback(void *data) {
|
||||
// Running the battery monitor on the timer task is not a good idea because
|
||||
// we could be sampling right in the middle of a flash erase, etc. Therefore,
|
||||
// dispatch to a lower priority task
|
||||
system_task_add_callback(prv_update_state, data);
|
||||
|
||||
// Reschedule ourselves again so we create a loop
|
||||
prv_schedule_update(BATTERY_SAMPLE_RATE_MS, false);
|
||||
}
|
||||
|
||||
static void prv_schedule_update(uint32_t delay, bool force_update) {
|
||||
bool success = new_timer_start(s_periodic_timer_id, delay, prv_update_callback,
|
||||
(void *)force_update, 0 /*flags*/);
|
||||
PBL_ASSERTN(success);
|
||||
}
|
||||
|
||||
void battery_state_force_update(void) {
|
||||
// Fire off our periodic timer. Note that we rely on the callback to reschedule the timer
|
||||
// for 1 minute intervals rather than create it as a repeating timer. This is because
|
||||
// we occasionally want the callback to get triggered immediately
|
||||
// (in response to the charging cable being plugged in). In these instances, we reschedule it
|
||||
// from the main task.
|
||||
prv_schedule_update(0, true);
|
||||
}
|
||||
|
||||
void battery_state_init(void) {
|
||||
s_periodic_timer_id = new_timer_create();
|
||||
|
||||
s_last_battery_state = (BatteryState) { .connection = ConnectionStateDischargingUnplugged };
|
||||
battery_state_reset_filter();
|
||||
battery_state_force_update();
|
||||
|
||||
s_analytics_previous_mv = s_last_battery_state.voltage;
|
||||
}
|
||||
|
||||
void battery_state_handle_connection_event(bool is_connected) {
|
||||
static const uint32_t RECONNECTION_DELAY_MS = 1000;
|
||||
|
||||
PBL_LOG_VERBOSE("USB Connected:%d", is_connected);
|
||||
|
||||
// Trigger a reset update to the state machine. Delay the update to allow the battery voltage to
|
||||
// settle and to debounce reconnection events
|
||||
prv_schedule_update(RECONNECTION_DELAY_MS, true);
|
||||
}
|
||||
|
||||
PreciseBatteryChargeState prv_get_precise_charge_state(const BatteryState *state) {
|
||||
PreciseBatteryChargeState event_state = {
|
||||
.charge_percent = state->percent,
|
||||
.is_charging = (s_last_battery_state.connection == ConnectionStateChargingPlugged),
|
||||
.is_plugged = (s_last_battery_state.connection != ConnectionStateDischargingUnplugged)
|
||||
};
|
||||
return event_state;
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(BatteryChargeState, sys_battery_get_charge_state, void) {
|
||||
return battery_get_charge_state();
|
||||
}
|
||||
|
||||
BatteryChargeState battery_get_charge_state(void) {
|
||||
bool is_plugged = (s_last_battery_state.connection != ConnectionStateDischargingUnplugged);
|
||||
|
||||
int32_t percent = ratio32_to_percent(s_last_battery_state.percent);
|
||||
|
||||
// subtract low power reserve, so developer will see 0% when we're approaching low power mode
|
||||
int32_t percent_normalized = MAX((percent - BOARD_CONFIG_POWER.low_power_threshold
|
||||
+ percent / (100 / BOARD_CONFIG_POWER.low_power_threshold)), 0);
|
||||
|
||||
// massage rounding factor so that between 100% to 50% charge the SOC reported is biased to a
|
||||
// higher charge percent bin.
|
||||
int32_t rounding_factor = 5 + MAX(((percent - 50) / 10), 0);
|
||||
BatteryChargeState state = {
|
||||
.charge_percent = MIN(10 * ((percent_normalized + rounding_factor) / 10), 100),
|
||||
.is_charging = is_plugged && percent_normalized < 100,
|
||||
.is_plugged = is_plugged,
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
// For unit tests
|
||||
TimerID battery_state_get_periodic_timer_id(void) {
|
||||
return s_periodic_timer_id;
|
||||
}
|
||||
|
||||
uint16_t battery_state_get_voltage(void) {
|
||||
return s_last_battery_state.voltage;
|
||||
}
|
||||
|
||||
|
||||
#include "console/prompt.h"
|
||||
void command_print_battery_status(void) {
|
||||
char buffer[32];
|
||||
PreciseBatteryChargeState state = prv_get_precise_charge_state(&s_last_battery_state);
|
||||
prompt_send_response_fmt(buffer, 32, "%"PRIu16" mV", s_last_battery_state.voltage);
|
||||
prompt_send_response_fmt(buffer, 32,
|
||||
"batt_percent: %"PRIu32"%%", ratio32_to_percent(state.charge_percent));
|
||||
prompt_send_response_fmt(buffer, 32, "plugged: %s", state.is_plugged ? "YES" : "NO");
|
||||
prompt_send_response_fmt(buffer, 32, "charging: %s", state.is_charging ? "YES" : "NO");
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Analytics
|
||||
|
||||
// Note that this is run on a different thread than battery_state!
|
||||
void analytics_external_collect_battery(void) {
|
||||
// This should not be called for an hour after bootup
|
||||
|
||||
int battery_mv = s_last_battery_state.voltage;
|
||||
|
||||
int d_mv = battery_mv - s_analytics_previous_mv;
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_BATTERY_VOLTAGE, battery_mv, AnalyticsClient_System);
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_BATTERY_VOLTAGE_DELTA, d_mv, AnalyticsClient_System);
|
||||
|
||||
int scaling_factor = INT32_MAX / 100; // we want to cover -100 to 100 percent
|
||||
// Note: we assume that the watch was not charging during the hour.
|
||||
int32_t start_percent = battery_curve_lookup_percent_with_scaling_factor(s_analytics_previous_mv,
|
||||
false, scaling_factor);
|
||||
int32_t curr_percent = battery_curve_lookup_percent_with_scaling_factor(battery_mv, false,
|
||||
scaling_factor);
|
||||
int32_t d_percent = curr_percent - start_percent;
|
||||
|
||||
s_analytics_previous_mv = battery_mv;
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_BATTERY_PERCENT_DELTA, d_percent, AnalyticsClient_System);
|
||||
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_BATTERY_PERCENT,
|
||||
ratio32_to_percent(s_last_battery_state.percent),
|
||||
AnalyticsClient_System);
|
||||
}
|
||||
|
||||
static void prv_set_forced_charge_state(bool is_charging) {
|
||||
battery_force_charge_enable(is_charging);
|
||||
|
||||
// Trigger an immediate update to the state machine: may trigger an event
|
||||
battery_state_force_update();
|
||||
}
|
||||
|
||||
void command_battery_charge_option(const char* option) {
|
||||
if (!strcmp("disable", option)) {
|
||||
prv_set_forced_charge_state(false);
|
||||
} else if (!strcmp("enable", option)) {
|
||||
prv_set_forced_charge_state(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Handles all battery-related driver communication, filters out events
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup Battery Battery
|
||||
//! \brief Functions related to getting the battery status
|
||||
//!
|
||||
//! This module contains the functions necessary to find the current charge status.
|
||||
//! @note Battery charge state is a complex topic; our modelling of the charge
|
||||
//! that is exposed by these functions represents a very simplified model based
|
||||
//! mostly on empirically derived charge and discharge voltage curves. As
|
||||
//! such, you should expect that the output will not have a high degree of
|
||||
//! accuracy.
|
||||
//! @{
|
||||
|
||||
//! Structure for retrieval of the battery charge state
|
||||
typedef struct {
|
||||
//! A percentage (0-100) of how full the battery is
|
||||
uint8_t charge_percent;
|
||||
//! True if the battery is currently being charged. False if not.
|
||||
bool is_charging;
|
||||
//! True if the charger cable is connected. False if not.
|
||||
bool is_plugged;
|
||||
} BatteryChargeState;
|
||||
|
||||
//! @internal
|
||||
//! Structure for retrieval of the exact battery charge state
|
||||
typedef struct {
|
||||
//! The battery's percentage as a ratio32
|
||||
uint32_t charge_percent;
|
||||
//! WARNING: This maps to @see battery_charge_controller_thinks_we_are_charging as opposed to
|
||||
//! the user-facing defintion of whether we're charging (100% battery).
|
||||
bool is_charging;
|
||||
bool is_plugged;
|
||||
} PreciseBatteryChargeState;
|
||||
|
||||
//! Function to get the current battery charge state
|
||||
//! @returns a \ref BatteryChargeState struct with the current charge state
|
||||
BatteryChargeState battery_get_charge_state(void);
|
||||
|
||||
//! @}
|
||||
//! @}
|
||||
|
||||
void battery_state_force_update(void);
|
||||
|
||||
void battery_state_init(void);
|
||||
|
||||
void battery_state_handle_connection_event(bool is_connected);
|
||||
|
||||
void battery_state_reset_filter(void);
|
||||
|
||||
// Get the last recorded voltage
|
||||
uint16_t battery_state_get_voltage(void);
|
||||
|
||||
// For unit tests
|
||||
TimerID battery_state_get_periodic_timer_id(void);
|
||||
Reference in New Issue
Block a user