#!/usr/bin/env python3
import unittest
from unittest . mock import patch
from parameterized import parameterized
from cereal import log
import cereal . messaging as messaging
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
TEST_DURATION_S = 50
ALL_PANDA_TYPES = [ ( log . PandaState . PandaType . dos , ) ]
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 " )
def mock_peripheralState ( self , hw_type , car_voltage = 12 ) :
ps = messaging . new_message ( ' peripheralState ' ) . peripheralState
ps . pandaType = hw_type
ps . voltage = car_voltage * 1e3
return ps
# 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_offroad_ignition ( self , hw_type ) :
pm = PowerMonitoring ( )
for _ in range ( 10 ) :
pm . calculate ( self . mock_peripheralState ( hw_type ) , True )
self . assertEqual ( pm . get_power_used ( ) , 0 )
# Test to see that it integrates with discharging battery
@parameterized . expand ( ALL_PANDA_TYPES )
def test_offroad_integration_discharging ( self , hw_type ) :
POWER_DRAW = 4
with pm_patch ( " HARDWARE.get_current_power_draw " , POWER_DRAW ) :
pm = PowerMonitoring ( )
for _ in range ( TEST_DURATION_S + 1 ) :
pm . calculate ( self . mock_peripheralState ( hw_type ) , 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_car_battery_integration_onroad ( self , hw_type ) :
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 ( self . mock_peripheralState ( hw_type ) , 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_car_battery_integration_upper_limit ( self , hw_type ) :
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 ( self . mock_peripheralState ( hw_type ) , 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_car_battery_integration_offroad ( self , hw_type ) :
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 ( self . mock_peripheralState ( hw_type ) , 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_car_battery_integration_lower_limit ( self , hw_type ) :
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 ( self . mock_peripheralState ( hw_type ) , 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
@parameterized . expand ( ALL_PANDA_TYPES )
def test_max_time_offroad ( self , hw_type ) :
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
peripheralState = self . mock_peripheralState ( hw_type )
while ssb < = start_time + MOCKED_MAX_OFFROAD_TIME :
pm . calculate ( peripheralState , 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 ) )
# Test to check policy of stopping charging when the car voltage is too low
@parameterized . expand ( ALL_PANDA_TYPES )
def test_car_voltage ( self , hw_type ) :
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
peripheralState = self . mock_peripheralState ( hw_type , car_voltage = ( VBATT_PAUSE_CHARGING - 1 ) )
for i in range ( TEST_TIME ) :
pm . calculate ( peripheralState , ignition )
if i % 10 == 0 :
self . assertEqual ( pm . should_shutdown ( ignition , True , ssb , True ) , ( pm . car_voltage_mV < VBATT_PAUSE_CHARGING * 1e3 ) )
self . assertTrue ( pm . should_shutdown ( ignition , True , ssb , 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
peripheralState = self . mock_peripheralState ( log . PandaState . PandaType . uno , car_voltage = ( VBATT_PAUSE_CHARGING - 1 ) )
for i in range ( TEST_TIME ) :
pm . calculate ( peripheralState , 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
peripheralState = self . mock_peripheralState ( log . PandaState . PandaType . uno , car_voltage = ( VBATT_PAUSE_CHARGING - 1 ) )
for i in range ( TEST_TIME ) :
pm . calculate ( peripheralState , 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
peripheralState = self . mock_peripheralState ( log . PandaState . PandaType . uno , car_voltage = ( VBATT_PAUSE_CHARGING - 1 ) )
for i in range ( TEST_TIME ) :
pm . calculate ( peripheralState , ignition )
if i % 10 == 0 :
self . assertFalse ( pm . should_shutdown ( ignition , False , ssb , False ) )
self . assertFalse ( pm . should_shutdown ( ignition , False , ssb , False ) )
if __name__ == " __main__ " :
unittest . main ( )