mirror of
https://github.com/google/pebble.git
synced 2025-12-06 06:02:24 -05:00
Import of the watch repository from Pebble
This commit is contained in:
528
tests/fw/test_battery_monitor.c
Normal file
528
tests/fw/test_battery_monitor.c
Normal file
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
* 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 "services/common/battery/battery_state.h"
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
// Stubs
|
||||
////////////////////////////////////
|
||||
#include "stubs_analytics.h"
|
||||
#include "stubs_logging.h"
|
||||
#include "stubs_passert.h"
|
||||
#include "stubs_prompt.h"
|
||||
#include "stubs_serial.h"
|
||||
#include "fake_new_timer.h"
|
||||
#include "fake_battery.h"
|
||||
#include "fake_system_task.h"
|
||||
#include "fake_rtc.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/reboot_reason.h"
|
||||
|
||||
static bool s_entered_standby;
|
||||
static bool s_in_low_power;
|
||||
static bool s_error_window_shown;
|
||||
static bool s_warning_window_shown;
|
||||
static bool s_stop_mode_allowed;
|
||||
static PebbleEvent s_last_event_put;
|
||||
|
||||
bool battery_is_usb_connected_raw(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void low_power_standby(void) {
|
||||
s_entered_standby = true;
|
||||
}
|
||||
|
||||
void low_power_exit(void) {
|
||||
s_in_low_power = false;
|
||||
}
|
||||
|
||||
void low_power_enter(void) {
|
||||
s_in_low_power = true;
|
||||
}
|
||||
|
||||
bool low_power_is_active(void) {
|
||||
return s_in_low_power;
|
||||
}
|
||||
|
||||
bool firmware_update_is_in_progress(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void battery_force_charge_enable(bool is_charging) { }
|
||||
|
||||
bool stop_mode_is_allowed(void) {
|
||||
return s_stop_mode_allowed;
|
||||
}
|
||||
|
||||
static void periodic_timer_trigger(int count) {
|
||||
TimerID timer_id = battery_state_get_periodic_timer_id();
|
||||
cl_assert(timer_id != TIMER_INVALID_ID);
|
||||
cl_assert(stub_new_timer_is_scheduled(timer_id));
|
||||
for (int i=0; i<count; ++i) {
|
||||
stub_new_timer_fire(timer_id);
|
||||
fake_system_task_callbacks_invoke_pending();
|
||||
}
|
||||
}
|
||||
|
||||
static void standby_timer_trigger(int count) {
|
||||
TimerID timer_id = battery_monitor_get_standby_timer_id();
|
||||
|
||||
cl_assert(timer_id != TIMER_INVALID_ID);
|
||||
cl_assert(stub_new_timer_is_scheduled(timer_id));
|
||||
for (int i = 0; i<count; ++i) {
|
||||
stub_new_timer_fire(timer_id);
|
||||
fake_system_task_callbacks_invoke_pending();
|
||||
}
|
||||
}
|
||||
|
||||
static bool standby_timer_is_scheduled() {
|
||||
TimerID timer_id = battery_monitor_get_standby_timer_id();
|
||||
return timer_id != TIMER_INVALID_ID && stub_new_timer_is_scheduled(timer_id);
|
||||
}
|
||||
|
||||
static uint32_t standby_timer_get_timeout() {
|
||||
TimerID timer_id = battery_monitor_get_standby_timer_id();
|
||||
|
||||
cl_assert(timer_id != TIMER_INVALID_ID);
|
||||
cl_assert(stub_new_timer_is_scheduled(timer_id));
|
||||
return stub_new_timer_timeout(timer_id);
|
||||
}
|
||||
|
||||
void enter_standby(RebootReasonCode reason) {
|
||||
s_entered_standby = true;
|
||||
}
|
||||
|
||||
void event_put(PebbleEvent* event) {
|
||||
s_last_event_put = *event;
|
||||
|
||||
if (event->type == PEBBLE_BATTERY_STATE_CHANGE_EVENT) {
|
||||
battery_monitor_handle_state_change_event(event->battery_state.new_state);
|
||||
} else if (event->type == PEBBLE_BATTERY_CONNECTION_EVENT) {
|
||||
battery_state_handle_connection_event(event->battery_connection.is_connected);
|
||||
periodic_timer_trigger(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup
|
||||
////////////////////////////////////
|
||||
void test_battery_monitor__initialize(void) {
|
||||
//g_pbl_log_enabled = true;
|
||||
//g_pbl_log_level = 255;
|
||||
|
||||
s_entered_standby = false;
|
||||
s_error_window_shown = false;
|
||||
s_warning_window_shown = false;
|
||||
s_in_low_power = false;
|
||||
s_stop_mode_allowed = true;
|
||||
fake_rtc_init(0, 0);
|
||||
fake_rtc_auto_increment_ticks(0);
|
||||
}
|
||||
|
||||
void test_battery_monitor__cleanup(void) {
|
||||
}
|
||||
|
||||
// Tests
|
||||
////////////////////////////////////
|
||||
|
||||
int32_t battery_curve_lookup_percent_with_scaling_factor(
|
||||
int battery_mv, bool is_charging, uint32_t scaling_factor);
|
||||
|
||||
void test_battery_monitor__scaled_reading(void) {
|
||||
int32_t scaling_factor = INT32_MAX / 100;
|
||||
int32_t prev_reading = 0;
|
||||
|
||||
// run through a wide range of battery readings. Confirm as the mv increases,
|
||||
// the percentage reported increases. Use the largest scaling factor to check
|
||||
// for integer overflows
|
||||
for (int mv = 3000; mv < 5000; mv++) {
|
||||
|
||||
int32_t res = battery_curve_lookup_percent_with_scaling_factor(
|
||||
mv, false, scaling_factor);
|
||||
|
||||
cl_assert(prev_reading <= res);
|
||||
prev_reading = res;
|
||||
}
|
||||
|
||||
// make sure that when we compute the largest possible (100% - 0%) and lowest possible
|
||||
// (0% - 100%) battery delta that we don't overflow the computation
|
||||
int32_t start_percent = battery_curve_lookup_percent_with_scaling_factor(
|
||||
2000, false, scaling_factor);
|
||||
|
||||
int32_t end_percent = battery_curve_lookup_percent_with_scaling_factor(
|
||||
5000, false, scaling_factor);
|
||||
|
||||
int32_t delta_percent = end_percent - start_percent;
|
||||
cl_assert(delta_percent > (INT32_MAX - 100));
|
||||
|
||||
delta_percent = start_percent - end_percent;
|
||||
cl_assert(delta_percent < (INT32_MIN + 100));
|
||||
}
|
||||
|
||||
// Check that the percentage reported is somewhat protected from transient voltage changes
|
||||
void test_battery_monitor__charge_fluctuate_voltage(void) {
|
||||
int high_percent = 70;
|
||||
int low_percent = 20;
|
||||
int high_mv = battery_curve_lookup_voltage_by_percent(high_percent, true);
|
||||
int low_mv = battery_curve_lookup_voltage_by_percent(low_percent, true);
|
||||
|
||||
fake_battery_init(high_mv, true, true);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
// For the first sample, it will be identical
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, high_percent);
|
||||
|
||||
// ...and should stay that way
|
||||
periodic_timer_trigger(10);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, high_percent);
|
||||
|
||||
// Then, when the voltage drops, the percentage should begin to decline - but should not reach the low value yet
|
||||
fake_battery_set_millivolts(low_mv);
|
||||
periodic_timer_trigger(1);
|
||||
int delta = high_percent - battery_get_charge_state().charge_percent;
|
||||
cl_assert(delta >= 0);
|
||||
cl_assert(delta < high_percent - low_percent);
|
||||
|
||||
// But, it should approach that value over time
|
||||
int last_delta = delta;
|
||||
while(battery_get_charge_state().charge_percent > low_percent) {
|
||||
periodic_timer_trigger(1);
|
||||
delta = high_percent - battery_get_charge_state().charge_percent;
|
||||
cl_assert(delta >= last_delta);
|
||||
last_delta = delta;
|
||||
}
|
||||
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, low_percent);
|
||||
}
|
||||
|
||||
void test_battery_monitor__connection_reset(void) {
|
||||
// Test for PBL-19951: Reset charge percent on reconnection events
|
||||
int percent = 10;
|
||||
int charge_mv = battery_curve_lookup_voltage_by_percent(percent, true);
|
||||
int discharge_mv = battery_curve_lookup_voltage_by_percent(percent, false);
|
||||
|
||||
fake_battery_init(discharge_mv, false, false);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, percent);
|
||||
|
||||
fake_battery_set_charging(true);
|
||||
fake_battery_set_millivolts(charge_mv);
|
||||
fake_battery_set_connected(true);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, percent);
|
||||
|
||||
fake_battery_set_charging(false);
|
||||
fake_battery_set_millivolts(discharge_mv);
|
||||
fake_battery_set_connected(false);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, percent);
|
||||
}
|
||||
|
||||
void test_battery_monitor__curve_adjustment_when_charge_complete(void) {
|
||||
int charge_mv = battery_curve_lookup_voltage_by_percent(0, true);
|
||||
int full_mv = battery_curve_lookup_voltage_by_percent(100, false);
|
||||
int charge_terminate_mv = battery_curve_lookup_voltage_by_percent(95, false);
|
||||
|
||||
fake_battery_init(charge_mv, true, true);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
|
||||
fake_battery_set_millivolts(charge_terminate_mv);
|
||||
fake_battery_set_charging(false);
|
||||
periodic_timer_trigger(1);
|
||||
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 100);
|
||||
|
||||
fake_battery_set_millivolts(full_mv);
|
||||
periodic_timer_trigger(1);
|
||||
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 100);
|
||||
}
|
||||
|
||||
void test_battery_monitor__curve_doesnt_shift_too_far(void) {
|
||||
int charge_mv = battery_curve_lookup_voltage_by_percent(0, true);
|
||||
int charge_terminate_mv = battery_curve_lookup_voltage_by_percent(80, false);
|
||||
|
||||
fake_battery_init(charge_mv, true, true);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
|
||||
fake_battery_set_millivolts(charge_terminate_mv);
|
||||
fake_battery_set_charging(false);
|
||||
periodic_timer_trigger(1);
|
||||
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 80);
|
||||
}
|
||||
|
||||
/*
|
||||
good -> lpm
|
||||
lpm -> good
|
||||
|
||||
good -> critical
|
||||
critical -> lpm
|
||||
|
||||
lpm -> critical
|
||||
critical -> good
|
||||
*/
|
||||
typedef enum {
|
||||
PowerStateGood,
|
||||
PowerStateLowPower,
|
||||
PowerStateCritical,
|
||||
PowerStateStandby
|
||||
} PowerStateID;
|
||||
extern PowerStateID s_power_state;
|
||||
|
||||
void test_battery_monitor__transitions(void) {
|
||||
int good_mv = battery_curve_lookup_voltage_by_percent(100, false);
|
||||
int low_mv = battery_curve_lookup_voltage_by_percent(3, false);
|
||||
int critical_mv = battery_curve_lookup_voltage_by_percent(0, false);
|
||||
|
||||
fake_battery_init(good_mv, false, false);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(!s_in_low_power && !battery_monitor_critical_lockout());
|
||||
cl_assert_equal_i(s_power_state, PowerStateGood);
|
||||
|
||||
// good -> lpm
|
||||
fake_battery_set_millivolts(low_mv);
|
||||
periodic_timer_trigger(10);
|
||||
cl_assert(s_in_low_power);
|
||||
cl_assert_equal_i(s_power_state, PowerStateLowPower);
|
||||
|
||||
// lpm -> good
|
||||
fake_battery_set_charging(true);
|
||||
fake_battery_set_connected(true);
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(!s_in_low_power);
|
||||
cl_assert_equal_i(s_power_state, PowerStateGood);
|
||||
|
||||
// good -> critical
|
||||
fake_battery_set_millivolts(critical_mv);
|
||||
fake_battery_set_charging(false);
|
||||
fake_battery_set_connected(false);
|
||||
periodic_timer_trigger(20);
|
||||
cl_assert(battery_monitor_critical_lockout());
|
||||
cl_assert_equal_i(s_power_state, PowerStateCritical);
|
||||
|
||||
// critical -> lpm (only possible if unstable)
|
||||
fake_battery_set_millivolts(low_mv);
|
||||
battery_state_force_update();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(s_in_low_power);
|
||||
cl_assert_equal_i(s_power_state, PowerStateLowPower);
|
||||
|
||||
// lpm -> critical
|
||||
fake_battery_set_millivolts(critical_mv);
|
||||
periodic_timer_trigger(20);
|
||||
cl_assert(battery_monitor_critical_lockout());
|
||||
cl_assert(s_in_low_power);
|
||||
cl_assert_equal_i(s_power_state, PowerStateCritical);
|
||||
|
||||
// critical -> good
|
||||
fake_battery_set_charging(true);
|
||||
fake_battery_set_connected(true);
|
||||
fake_battery_set_millivolts(good_mv);
|
||||
periodic_timer_trigger(20);
|
||||
cl_assert(!battery_monitor_critical_lockout());
|
||||
cl_assert(!s_in_low_power);
|
||||
cl_assert_equal_i(s_power_state, PowerStateGood);
|
||||
}
|
||||
|
||||
void test_battery_monitor__low_first_run(void) {
|
||||
int low_mv = battery_curve_lookup_voltage_by_percent(3, false);
|
||||
|
||||
fake_battery_init(low_mv, false, false);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(battery_monitor_critical_lockout());
|
||||
|
||||
cl_assert_equal_i(standby_timer_get_timeout(), 2000);
|
||||
standby_timer_trigger(1);
|
||||
cl_assert(s_entered_standby);
|
||||
}
|
||||
|
||||
void test_battery_monitor__critical(void) {
|
||||
int good_mv = battery_curve_lookup_voltage_by_percent(10, false);
|
||||
int critical_mv = battery_curve_lookup_voltage_by_percent(0, false);
|
||||
|
||||
fake_battery_init(good_mv, false, false);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(!battery_monitor_critical_lockout());
|
||||
|
||||
fake_battery_set_millivolts(critical_mv);
|
||||
periodic_timer_trigger(25);
|
||||
cl_assert(battery_monitor_critical_lockout());
|
||||
|
||||
cl_assert_equal_i(standby_timer_get_timeout(), 30000);
|
||||
standby_timer_trigger(1);
|
||||
cl_assert(s_entered_standby);
|
||||
}
|
||||
|
||||
void test_battery_monitor__critical_plugged_in(void) {
|
||||
int good_mv = battery_curve_lookup_voltage_by_percent(10, false);
|
||||
int critical_mv = battery_curve_lookup_voltage_by_percent(0, false);
|
||||
|
||||
fake_battery_init(good_mv, false, false);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(!battery_monitor_critical_lockout());
|
||||
|
||||
fake_battery_set_millivolts(critical_mv);
|
||||
periodic_timer_trigger(25);
|
||||
cl_assert(battery_monitor_critical_lockout());
|
||||
|
||||
cl_assert_equal_i(standby_timer_get_timeout(), 30000);
|
||||
fake_battery_set_charging(true);
|
||||
fake_battery_set_connected(true);
|
||||
periodic_timer_trigger(1);
|
||||
standby_timer_trigger(1);
|
||||
cl_assert(!s_entered_standby);
|
||||
}
|
||||
|
||||
void test_battery_monitor__increase_discharging(void) {
|
||||
int low_mv = battery_curve_lookup_voltage_by_percent(50, false);
|
||||
int high_mv = battery_curve_lookup_voltage_by_percent(100, false);
|
||||
int lower_mv = battery_curve_lookup_voltage_by_percent(20, false);
|
||||
|
||||
fake_battery_init(low_mv, false, false);
|
||||
fake_rtc_auto_increment_ticks(50000);
|
||||
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(5);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 50);
|
||||
|
||||
// Should be stable by now
|
||||
// Shouldn't update percent (actually, shouldn't even send events.)
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Shouldn't be any updates");
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼");
|
||||
fake_battery_set_millivolts(high_mv);
|
||||
periodic_timer_trigger(20);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 50);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲");
|
||||
|
||||
// Should still update if it goes lower
|
||||
fake_battery_set_millivolts(lower_mv);
|
||||
periodic_timer_trigger(20);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 20);
|
||||
}
|
||||
|
||||
void test_battery_monitor__stop_mode_disabled(void) {
|
||||
int start_mv = battery_curve_lookup_voltage_by_percent(50, false);
|
||||
int end_mv = battery_curve_lookup_voltage_by_percent(20, false);
|
||||
|
||||
fake_battery_init(start_mv, false, false);
|
||||
|
||||
// Start off with a nice battery level
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 50);
|
||||
|
||||
// Pretend vibe activated or something like that.
|
||||
// - The reported mV goes down and stop mode is disabled
|
||||
// It should skip 5 times (MAX_SAMPLE_SKIPS) before updating.
|
||||
fake_battery_set_millivolts(end_mv);
|
||||
s_stop_mode_allowed = false;
|
||||
periodic_timer_trigger(5);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 50);
|
||||
|
||||
// After 5 skips, we should update.
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(battery_get_charge_state().charge_percent < 50);
|
||||
}
|
||||
|
||||
void test_battery_monitor__connection_states(void) {
|
||||
int charge_mv = battery_curve_lookup_voltage_by_percent(60, true);
|
||||
int okay_mv = battery_curve_lookup_voltage_by_percent(5, false);
|
||||
int discharge_mv = battery_curve_lookup_voltage_by_percent(3, false);
|
||||
|
||||
// Begin in LPM, unplugged and discharging.
|
||||
fake_battery_init(okay_mv, false, false);
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
fake_battery_set_millivolts(discharge_mv);
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(s_in_low_power);
|
||||
|
||||
// If we somehow begin charging, ignore it.
|
||||
fake_battery_set_charging(true);
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(s_in_low_power);
|
||||
|
||||
// If we're charging and connected, reset the filter
|
||||
fake_battery_set_millivolts(charge_mv);
|
||||
fake_battery_set_connected(true);
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert(!s_in_low_power && battery_get_charge_state().charge_percent == 60);
|
||||
|
||||
// Discharging but connected - The charge should update so 60% is 100%
|
||||
fake_battery_set_charging(false);
|
||||
periodic_timer_trigger(1);
|
||||
cl_assert_equal_i(battery_get_charge_state().charge_percent, 100);
|
||||
}
|
||||
|
||||
void test_battery_monitor__battery_get_charge_state(void) {
|
||||
// range through all discrete percentages and verify that battery_get_charge_state()
|
||||
// returns sane values
|
||||
BatteryChargeState result;
|
||||
uint8_t last_charge_percent = 0;
|
||||
uint8_t last_discharge_percent = 100;
|
||||
for (uint32_t charge_percent = 0; charge_percent <= 100; ++charge_percent) {
|
||||
int charge_mv = battery_curve_lookup_voltage_by_percent(charge_percent, true);
|
||||
int discharge_mv = battery_curve_lookup_voltage_by_percent(100 - charge_percent, false);
|
||||
//
|
||||
// test as if the battery is plugged and charging
|
||||
//
|
||||
bool charging = charge_percent < 100;
|
||||
fake_battery_init(charge_mv, true, charging);
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
result = battery_get_charge_state();
|
||||
// due to fudge factors we merely check that the percentage is in range and
|
||||
// that it is monotonically increasing
|
||||
cl_assert((result.charge_percent >= 0) && (result.charge_percent <= 100));
|
||||
cl_assert(result.charge_percent >= last_charge_percent);
|
||||
cl_assert(result.is_charging == charging);
|
||||
cl_assert(result.is_plugged);
|
||||
last_charge_percent = result.charge_percent;
|
||||
//
|
||||
// test as if the battery is unplugged and discharging
|
||||
//
|
||||
fake_battery_init(discharge_mv, false, false);
|
||||
battery_monitor_init();
|
||||
periodic_timer_trigger(1);
|
||||
result = battery_get_charge_state();
|
||||
// due to fudge factors we merely check that the percentage is in range and
|
||||
// that it is monotonically decreasing
|
||||
cl_assert((result.charge_percent >= 0) && (result.charge_percent <= 100));
|
||||
cl_assert(result.charge_percent <= last_discharge_percent);
|
||||
cl_assert(!result.is_charging);
|
||||
cl_assert(!result.is_plugged);
|
||||
last_discharge_percent = result.charge_percent;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user