You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
8.7 KiB
203 lines
8.7 KiB
#!/usr/bin/env python3
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from common.params import Params
|
|
params = Params()
|
|
|
|
# Create fake time
|
|
ssb = 0
|
|
def mock_sec_since_boot():
|
|
global ssb
|
|
ssb += 1
|
|
return ssb
|
|
|
|
with patch("common.realtime.sec_since_boot", new=mock_sec_since_boot):
|
|
with patch("common.params.put_nonblocking", new=params.put):
|
|
from selfdrive.thermald.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \
|
|
CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S
|
|
|
|
TEST_DURATION_S = 50
|
|
GOOD_VOLTAGE = 12 * 1e3
|
|
VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3
|
|
|
|
def pm_patch(name, value, constant=False):
|
|
if constant:
|
|
return patch(f"selfdrive.thermald.power_monitoring.{name}", value)
|
|
return patch(f"selfdrive.thermald.power_monitoring.{name}", return_value=value)
|
|
|
|
class TestPowerMonitoring(unittest.TestCase):
|
|
def setUp(self):
|
|
# Clear stored capacity before each test
|
|
params.remove("CarBatteryCapacity")
|
|
params.remove("DisablePowerDown")
|
|
|
|
# Test to see that it doesn't do anything when pandaState is None
|
|
def test_pandaState_present(self):
|
|
pm = PowerMonitoring()
|
|
for _ in range(10):
|
|
pm.calculate(None, None)
|
|
self.assertEqual(pm.get_power_used(), 0)
|
|
self.assertEqual(pm.get_car_battery_capacity(), (CAR_BATTERY_CAPACITY_uWh / 10))
|
|
|
|
# Test to see that it doesn't integrate offroad when ignition is True
|
|
def test_offroad_ignition(self):
|
|
pm = PowerMonitoring()
|
|
for _ in range(10):
|
|
pm.calculate(GOOD_VOLTAGE, True)
|
|
self.assertEqual(pm.get_power_used(), 0)
|
|
|
|
# Test to see that it integrates with discharging battery
|
|
def test_offroad_integration_discharging(self):
|
|
POWER_DRAW = 4
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
for _ in range(TEST_DURATION_S + 1):
|
|
pm.calculate(GOOD_VOLTAGE, False)
|
|
expected_power_usage = ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6)
|
|
self.assertLess(abs(pm.get_power_used() - expected_power_usage), 10)
|
|
|
|
# Test to check positive integration of car_battery_capacity
|
|
def test_car_battery_integration_onroad(self):
|
|
POWER_DRAW = 4
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = 0
|
|
for _ in range(TEST_DURATION_S + 1):
|
|
pm.calculate(GOOD_VOLTAGE, True)
|
|
expected_capacity = ((TEST_DURATION_S/3600) * CAR_CHARGING_RATE_W * 1e6)
|
|
self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10)
|
|
|
|
# Test to check positive integration upper limit
|
|
def test_car_battery_integration_upper_limit(self):
|
|
POWER_DRAW = 4
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - 1000
|
|
for _ in range(TEST_DURATION_S + 1):
|
|
pm.calculate(GOOD_VOLTAGE, True)
|
|
estimated_capacity = CAR_BATTERY_CAPACITY_uWh + (CAR_CHARGING_RATE_W / 3600 * 1e6)
|
|
self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10)
|
|
|
|
# Test to check negative integration of car_battery_capacity
|
|
def test_car_battery_integration_offroad(self):
|
|
POWER_DRAW = 4
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
for _ in range(TEST_DURATION_S + 1):
|
|
pm.calculate(GOOD_VOLTAGE, False)
|
|
expected_capacity = CAR_BATTERY_CAPACITY_uWh - ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6)
|
|
self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10)
|
|
|
|
# Test to check negative integration lower limit
|
|
def test_car_battery_integration_lower_limit(self):
|
|
POWER_DRAW = 4
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = 1000
|
|
for _ in range(TEST_DURATION_S + 1):
|
|
pm.calculate(GOOD_VOLTAGE, False)
|
|
estimated_capacity = 0 - ((1/3600) * POWER_DRAW * 1e6)
|
|
self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10)
|
|
|
|
# Test to check policy of stopping charging after MAX_TIME_OFFROAD_S
|
|
def test_max_time_offroad(self):
|
|
MOCKED_MAX_OFFROAD_TIME = 3600
|
|
POWER_DRAW = 0 # To stop shutting down for other reasons
|
|
with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
start_time = ssb
|
|
ignition = False
|
|
while ssb <= start_time + MOCKED_MAX_OFFROAD_TIME:
|
|
pm.calculate(GOOD_VOLTAGE, ignition)
|
|
if (ssb - start_time) % 1000 == 0 and ssb < start_time + MOCKED_MAX_OFFROAD_TIME:
|
|
self.assertFalse(pm.should_shutdown(ignition, True, start_time, False))
|
|
self.assertTrue(pm.should_shutdown(ignition, True, start_time, False))
|
|
|
|
def test_car_voltage(self):
|
|
POWER_DRAW = 0 # To stop shutting down for other reasons
|
|
TEST_TIME = 350
|
|
VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50
|
|
with pm_patch("VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S", VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S, constant=True), \
|
|
pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
ignition = False
|
|
start_time = ssb
|
|
for i in range(TEST_TIME):
|
|
pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition)
|
|
if i % 10 == 0:
|
|
self.assertEqual(pm.should_shutdown(ignition, True, start_time, True),
|
|
(pm.car_voltage_mV < VBATT_PAUSE_CHARGING * 1e3 and
|
|
(ssb - start_time) > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S and
|
|
(ssb - start_time) > DELAY_SHUTDOWN_TIME_S))
|
|
self.assertTrue(pm.should_shutdown(ignition, True, start_time, True))
|
|
|
|
# Test to check policy of not stopping charging when DisablePowerDown is set
|
|
def test_disable_power_down(self):
|
|
POWER_DRAW = 0 # To stop shutting down for other reasons
|
|
TEST_TIME = 100
|
|
params.put_bool("DisablePowerDown", True)
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
ignition = False
|
|
for i in range(TEST_TIME):
|
|
pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition)
|
|
if i % 10 == 0:
|
|
self.assertFalse(pm.should_shutdown(ignition, True, ssb, False))
|
|
self.assertFalse(pm.should_shutdown(ignition, True, ssb, False))
|
|
|
|
# Test to check policy of not stopping charging when ignition
|
|
def test_ignition(self):
|
|
POWER_DRAW = 0 # To stop shutting down for other reasons
|
|
TEST_TIME = 100
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
ignition = True
|
|
for i in range(TEST_TIME):
|
|
pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition)
|
|
if i % 10 == 0:
|
|
self.assertFalse(pm.should_shutdown(ignition, True, ssb, False))
|
|
self.assertFalse(pm.should_shutdown(ignition, True, ssb, False))
|
|
|
|
# Test to check policy of not stopping charging when harness is not connected
|
|
def test_harness_connection(self):
|
|
POWER_DRAW = 0 # To stop shutting down for other reasons
|
|
TEST_TIME = 100
|
|
with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
|
|
|
ignition = False
|
|
for i in range(TEST_TIME):
|
|
pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition)
|
|
if i % 10 == 0:
|
|
self.assertFalse(pm.should_shutdown(ignition, False, ssb, False))
|
|
self.assertFalse(pm.should_shutdown(ignition, False, ssb, False))
|
|
|
|
def test_delay_shutdown_time(self):
|
|
pm = PowerMonitoring()
|
|
pm.car_battery_capacity_uWh = 0
|
|
ignition = False
|
|
in_car = True
|
|
offroad_timestamp = ssb
|
|
started_seen = True
|
|
pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition)
|
|
|
|
while ssb < offroad_timestamp + DELAY_SHUTDOWN_TIME_S:
|
|
self.assertFalse(pm.should_shutdown(ignition, in_car,
|
|
offroad_timestamp,
|
|
started_seen),
|
|
f"Should not shutdown before {DELAY_SHUTDOWN_TIME_S} seconds offroad time")
|
|
self.assertTrue(pm.should_shutdown(ignition, in_car,
|
|
offroad_timestamp,
|
|
started_seen),
|
|
f"Should shutdown after {DELAY_SHUTDOWN_TIME_S} seconds offroad time")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|