mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-03-02 19:33:56 +08:00
* Reduce Min Idle Battery Voltage and Idle Time Updated minimum offroad battery voltage to reduce wear on car batteries and updated max offroad time to reflect the increased power consumption at idle based on EPHOT hardware power draw (this may have changed). Most vehicles make use of lead acid batteries which during normal use shouldn't ever decrease below ~12V (sources give a range of 11.8-12V). Increasing this limit will prevent premature battery failure by preventing sulfation of the cells. * Voltage cutoff to 11.8V, max offroad to 30h Changed lower voltage limit to 11.8V, removed power usage comment entirely, reverted change to max offroad time to 30 hours * Added Offroad Shutdown conditional Added IsOffroad Shutdown conditional statement to prevent shutting down due to voltage floor. * LPF Gain: Tau = 45s Tau modified to 45s to result in a LPF gain of ~0.011. Assuming a dT of 0.5s: (0.5/45)/((0.5/45)+1) * Revert IsOffroad check Removed due to unnecessary redundancy.
130 lines
5.5 KiB
Python
130 lines
5.5 KiB
Python
import threading
|
|
from typing import Optional
|
|
|
|
from common.params import Params, put_nonblocking
|
|
from common.realtime import sec_since_boot
|
|
from system.hardware import HARDWARE
|
|
from system.swaglog import cloudlog
|
|
from selfdrive.statsd import statlog
|
|
|
|
CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1))
|
|
|
|
# While driving, a battery charges completely in about 30-60 minutes
|
|
CAR_BATTERY_CAPACITY_uWh = 30e6
|
|
CAR_CHARGING_RATE_W = 45
|
|
|
|
VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage
|
|
VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss
|
|
MAX_TIME_OFFROAD_S = 30*3600
|
|
MIN_ON_TIME_S = 3600
|
|
VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60
|
|
|
|
class PowerMonitoring:
|
|
def __init__(self):
|
|
self.params = Params()
|
|
self.last_measurement_time = None # Used for integration delta
|
|
self.last_save_time = 0 # Used for saving current value in a param
|
|
self.power_used_uWh = 0 # Integrated power usage in uWh since going into offroad
|
|
self.next_pulsed_measurement_time = None
|
|
self.car_voltage_mV = 12e3 # Low-passed version of peripheralState voltage
|
|
self.car_voltage_instant_mV = 12e3 # Last value of peripheralState voltage
|
|
self.integration_lock = threading.Lock()
|
|
|
|
car_battery_capacity_uWh = self.params.get("CarBatteryCapacity")
|
|
if car_battery_capacity_uWh is None:
|
|
car_battery_capacity_uWh = 0
|
|
|
|
# Reset capacity if it's low
|
|
self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh))
|
|
|
|
# Calculation tick
|
|
def calculate(self, voltage: Optional[int], ignition: bool):
|
|
try:
|
|
now = sec_since_boot()
|
|
|
|
# If peripheralState is None, we're probably not in a car, so we don't care
|
|
if voltage is None:
|
|
with self.integration_lock:
|
|
self.last_measurement_time = None
|
|
self.next_pulsed_measurement_time = None
|
|
self.power_used_uWh = 0
|
|
return
|
|
|
|
# Low-pass battery voltage
|
|
self.car_voltage_instant_mV = voltage
|
|
self.car_voltage_mV = ((voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K)))
|
|
statlog.gauge("car_voltage", self.car_voltage_mV / 1e3)
|
|
|
|
# Cap the car battery power and save it in a param every 10-ish seconds
|
|
self.car_battery_capacity_uWh = max(self.car_battery_capacity_uWh, 0)
|
|
self.car_battery_capacity_uWh = min(self.car_battery_capacity_uWh, CAR_BATTERY_CAPACITY_uWh)
|
|
if now - self.last_save_time >= 10:
|
|
put_nonblocking("CarBatteryCapacity", str(int(self.car_battery_capacity_uWh)))
|
|
self.last_save_time = now
|
|
|
|
# First measurement, set integration time
|
|
with self.integration_lock:
|
|
if self.last_measurement_time is None:
|
|
self.last_measurement_time = now
|
|
return
|
|
|
|
if ignition:
|
|
# If there is ignition, we integrate the charging rate of the car
|
|
with self.integration_lock:
|
|
self.power_used_uWh = 0
|
|
integration_time_h = (now - self.last_measurement_time) / 3600
|
|
if integration_time_h < 0:
|
|
raise ValueError(f"Negative integration time: {integration_time_h}h")
|
|
self.car_battery_capacity_uWh += (CAR_CHARGING_RATE_W * 1e6 * integration_time_h)
|
|
self.last_measurement_time = now
|
|
else:
|
|
# Get current power draw somehow
|
|
current_power = HARDWARE.get_current_power_draw()
|
|
|
|
# Do the integration
|
|
self._perform_integration(now, current_power)
|
|
except Exception:
|
|
cloudlog.exception("Power monitoring calculation failed")
|
|
|
|
def _perform_integration(self, t: float, current_power: float) -> None:
|
|
with self.integration_lock:
|
|
try:
|
|
if self.last_measurement_time:
|
|
integration_time_h = (t - self.last_measurement_time) / 3600
|
|
power_used = (current_power * 1000000) * integration_time_h
|
|
if power_used < 0:
|
|
raise ValueError(f"Negative power used! Integration time: {integration_time_h} h Current Power: {power_used} uWh")
|
|
self.power_used_uWh += power_used
|
|
self.car_battery_capacity_uWh -= power_used
|
|
self.last_measurement_time = t
|
|
except Exception:
|
|
cloudlog.exception("Integration failed")
|
|
|
|
# Get the power usage
|
|
def get_power_used(self) -> int:
|
|
return int(self.power_used_uWh)
|
|
|
|
def get_car_battery_capacity(self) -> int:
|
|
return int(self.car_battery_capacity_uWh)
|
|
|
|
# See if we need to shutdown
|
|
def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: Optional[float], started_seen: bool):
|
|
if offroad_timestamp is None:
|
|
return False
|
|
|
|
now = sec_since_boot()
|
|
should_shutdown = False
|
|
offroad_time = (now - offroad_timestamp)
|
|
low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and
|
|
self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and
|
|
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
|
|
should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S
|
|
should_shutdown |= low_voltage_shutdown
|
|
should_shutdown |= (self.car_battery_capacity_uWh <= 0)
|
|
should_shutdown &= not ignition
|
|
should_shutdown &= (not self.params.get_bool("DisablePowerDown"))
|
|
should_shutdown &= in_car
|
|
should_shutdown |= self.params.get_bool("ForcePowerDown")
|
|
should_shutdown &= started_seen or (now > MIN_ON_TIME_S)
|
|
return should_shutdown
|