#!/usr/bin/env python3
import pytest
from openpilot . common . params import Params
from openpilot . selfdrive . thermald . power_monitoring import PowerMonitoring , CAR_BATTERY_CAPACITY_uWh , \
CAR_CHARGING_RATE_W , VBATT_PAUSE_CHARGING , DELAY_SHUTDOWN_TIME_S
# Create fake time
ssb = 0.
def mock_time_monotonic ( ) :
global ssb
ssb + = 1.
return ssb
TEST_DURATION_S = 50
GOOD_VOLTAGE = 12 * 1e3
VOLTAGE_BELOW_PAUSE_CHARGING = ( VBATT_PAUSE_CHARGING - 1 ) * 1e3
def pm_patch ( mocker , name , value , constant = False ) :
if constant :
mocker . patch ( f " openpilot.selfdrive.thermald.power_monitoring. { name } " , value )
else :
mocker . patch ( f " openpilot.selfdrive.thermald.power_monitoring. { name } " , return_value = value )
@pytest . fixture ( autouse = True )
def mock_time ( mocker ) :
mocker . patch ( " time.monotonic " , mock_time_monotonic )
class TestPowerMonitoring :
def setup_method ( self ) :
self . params = Params ( )
# Test to see that it doesn't do anything when pandaState is None
def test_panda_state_present ( self ) :
pm = PowerMonitoring ( )
for _ in range ( 10 ) :
pm . calculate ( None , None )
assert pm . get_power_used ( ) == 0
assert 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 )
assert pm . get_power_used ( ) == 0
# Test to see that it integrates with discharging battery
def test_offroad_integration_discharging ( self , mocker ) :
POWER_DRAW = 4
pm_patch ( mocker , " 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 )
assert 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 , mocker ) :
POWER_DRAW = 4
pm_patch ( mocker , " 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 )
assert abs ( pm . get_car_battery_capacity ( ) - expected_capacity ) < 10
# Test to check positive integration upper limit
def test_car_battery_integration_upper_limit ( self , mocker ) :
POWER_DRAW = 4
pm_patch ( mocker , " 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 )
assert 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 , mocker ) :
POWER_DRAW = 4
pm_patch ( mocker , " 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 )
assert abs ( pm . get_car_battery_capacity ( ) - expected_capacity ) < 10
# Test to check negative integration lower limit
def test_car_battery_integration_lower_limit ( self , mocker ) :
POWER_DRAW = 4
pm_patch ( mocker , " 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 )
assert 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 , mocker ) :
MOCKED_MAX_OFFROAD_TIME = 3600
POWER_DRAW = 0 # To stop shutting down for other reasons
pm_patch ( mocker , " MAX_TIME_OFFROAD_S " , MOCKED_MAX_OFFROAD_TIME , constant = True )
pm_patch ( mocker , " 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 :
assert not pm . should_shutdown ( ignition , True , start_time , False )
assert pm . should_shutdown ( ignition , True , start_time , False )
def test_car_voltage ( self , mocker ) :
POWER_DRAW = 0 # To stop shutting down for other reasons
TEST_TIME = 350
VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50
pm_patch ( mocker , " VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S " , VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S , constant = True )
pm_patch ( mocker , " 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 :
assert 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 )
assert 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 , mocker ) :
POWER_DRAW = 0 # To stop shutting down for other reasons
TEST_TIME = 100
self . params . put_bool ( " DisablePowerDown " , True )
pm_patch ( mocker , " 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 :
assert not pm . should_shutdown ( ignition , True , ssb , False )
assert not pm . should_shutdown ( ignition , True , ssb , False )
# Test to check policy of not stopping charging when ignition
def test_ignition ( self , mocker ) :
POWER_DRAW = 0 # To stop shutting down for other reasons
TEST_TIME = 100
pm_patch ( mocker , " 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 :
assert not pm . should_shutdown ( ignition , True , ssb , False )
assert not 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 , mocker ) :
POWER_DRAW = 0 # To stop shutting down for other reasons
TEST_TIME = 100
pm_patch ( mocker , " 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 :
assert not pm . should_shutdown ( ignition , False , ssb , False )
assert not 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 :
assert not pm . should_shutdown ( ignition , in_car ,
offroad_timestamp ,
started_seen ) , \
f " Should not shutdown before { DELAY_SHUTDOWN_TIME_S } seconds offroad time "
assert pm . should_shutdown ( ignition , in_car ,
offroad_timestamp ,
started_seen ) , \
f " Should shutdown after { DELAY_SHUTDOWN_TIME_S } seconds offroad time "