|
|
|
@ -1,38 +1,48 @@ |
|
|
|
|
import time |
|
|
|
|
import datetime |
|
|
|
|
import threading |
|
|
|
|
import random |
|
|
|
|
import threading |
|
|
|
|
import time |
|
|
|
|
from statistics import mean |
|
|
|
|
|
|
|
|
|
from cereal import log |
|
|
|
|
from selfdrive.swaglog import cloudlog |
|
|
|
|
|
|
|
|
|
PANDA_OUTPUT_VOLTAGE = 5.28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Parameters |
|
|
|
|
def get_battery_capacity(): |
|
|
|
|
return _read_param("/sys/class/power_supply/battery/capacity", int) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_battery_status(): |
|
|
|
|
# This does not correspond with actual charging or not. |
|
|
|
|
# This does not correspond with actual charging or not. |
|
|
|
|
# If a USB cable is plugged in, it responds with 'Charging', even when charging is disabled |
|
|
|
|
return _read_param("/sys/class/power_supply/battery/status", lambda x: x.strip(), '') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_battery_current(): |
|
|
|
|
return _read_param("/sys/class/power_supply/battery/current_now", int) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_battery_voltage(): |
|
|
|
|
return _read_param("/sys/class/power_supply/battery/voltage_now", int) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_usb_present(): |
|
|
|
|
return _read_param("/sys/class/power_supply/usb/present", lambda x: bool(int(x)), False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_battery_charging(): |
|
|
|
|
# This does correspond with actually charging |
|
|
|
|
return _read_param("/sys/class/power_supply/battery/charge_type", lambda x: x.strip() != "N/A", False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_battery_charging(on): |
|
|
|
|
with open('/sys/class/power_supply/battery/charging_enabled', 'w') as f: |
|
|
|
|
f.write(f"{1 if on else 0}\n") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Helpers |
|
|
|
|
def _read_param(path, parser, default=0): |
|
|
|
|
try: |
|
|
|
@ -41,10 +51,12 @@ def _read_param(path, parser, default=0): |
|
|
|
|
except Exception: |
|
|
|
|
return default |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def panda_current_to_actual_current(panda_current): |
|
|
|
|
# From white/grey panda schematic |
|
|
|
|
return (3.3 - (panda_current * 3.3 / 4096)) / 8.25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PowerMonitoring: |
|
|
|
|
def __init__(self): |
|
|
|
|
self.last_measurement_time = None # Used for integration delta |
|
|
|
@ -63,13 +75,13 @@ class PowerMonitoring: |
|
|
|
|
|
|
|
|
|
# Only integrate when there is no ignition |
|
|
|
|
# If health is None, we're probably not in a car, so we don't care |
|
|
|
|
if health == None or (health.health.ignitionLine or health.health.ignitionCan): |
|
|
|
|
if health is None or (health.health.ignitionLine or health.health.ignitionCan): |
|
|
|
|
self.last_measurement_time = None |
|
|
|
|
self.power_used_uWh = 0 |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
# First measurement, set integration time |
|
|
|
|
if self.last_measurement_time == None: |
|
|
|
|
if self.last_measurement_time is None: |
|
|
|
|
self.last_measurement_time = now |
|
|
|
|
return |
|
|
|
|
|
|
|
|
@ -78,13 +90,13 @@ class PowerMonitoring: |
|
|
|
|
if get_battery_status() == 'Discharging': |
|
|
|
|
# If the battery is discharging, we can use this measurement |
|
|
|
|
# On C2: this is low by about 10-15%, probably mostly due to UNO draw not being factored in |
|
|
|
|
current_power = ((get_battery_voltage() / 1000000) * (get_battery_current() / 1000000)) |
|
|
|
|
current_power = ((get_battery_voltage() / 1000000) * (get_battery_current() / 1000000)) |
|
|
|
|
elif (health.health.hwType in [log.HealthData.HwType.whitePanda, log.HealthData.HwType.greyPanda]) and (health.health.current > 1): |
|
|
|
|
# If white/grey panda, use the integrated current measurements if the measurement is not 0 |
|
|
|
|
# If the measurement is 0, the current is 400mA or greater, and out of the measurement range of the panda |
|
|
|
|
# This seems to be accurate to about 5% |
|
|
|
|
current_power = (PANDA_OUTPUT_VOLTAGE * panda_current_to_actual_current(health.health.current)) |
|
|
|
|
elif (self.next_pulsed_measurement_time != None) and (self.next_pulsed_measurement_time <= now): |
|
|
|
|
elif (self.next_pulsed_measurement_time is not None) and (self.next_pulsed_measurement_time <= now): |
|
|
|
|
# TODO: Figure out why this is off by a factor of 3/4??? |
|
|
|
|
FUDGE_FACTOR = 1.33 |
|
|
|
|
|
|
|
|
@ -93,7 +105,7 @@ class PowerMonitoring: |
|
|
|
|
try: |
|
|
|
|
set_battery_charging(False) |
|
|
|
|
time.sleep(5) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Measure for a few sec to get a good average |
|
|
|
|
voltages = [] |
|
|
|
|
currents = [] |
|
|
|
@ -101,20 +113,20 @@ class PowerMonitoring: |
|
|
|
|
voltages.append(get_battery_voltage()) |
|
|
|
|
currents.append(get_battery_current()) |
|
|
|
|
time.sleep(1) |
|
|
|
|
current_power = ((mean(voltages) / 1000000) * (mean(currents) / 1000000)) |
|
|
|
|
current_power = ((mean(voltages) / 1000000) * (mean(currents) / 1000000)) |
|
|
|
|
self._perform_integration(now, current_power * FUDGE_FACTOR) |
|
|
|
|
|
|
|
|
|
# Enable charging again |
|
|
|
|
set_battery_charging(True) |
|
|
|
|
except Exception as e: |
|
|
|
|
print("Pulsed power measurement failed:", str(e)) |
|
|
|
|
|
|
|
|
|
except Exception: |
|
|
|
|
cloudlog.exception("Pulsed power measurement failed") |
|
|
|
|
|
|
|
|
|
# Start pulsed measurement and return |
|
|
|
|
threading.Thread(target=perform_pulse_measurement, args=(now,)).start() |
|
|
|
|
self.next_pulsed_measurement_time = None |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
elif self.next_pulsed_measurement_time == None: |
|
|
|
|
|
|
|
|
|
elif self.next_pulsed_measurement_time is None: |
|
|
|
|
# On a charging EON with black panda, or drawing more than 400mA out of a white/grey one |
|
|
|
|
# Only way to get the power draw is to turn off charging for a few sec and check what the discharging rate is |
|
|
|
|
# We shouldn't do this very often, so make sure it has been some long-ish random time interval |
|
|
|
@ -126,8 +138,8 @@ class PowerMonitoring: |
|
|
|
|
|
|
|
|
|
# Do the integration |
|
|
|
|
self._perform_integration(now, current_power) |
|
|
|
|
except Exception as e: |
|
|
|
|
print("Power monitoring calculation failed:", str(e)) |
|
|
|
|
except Exception: |
|
|
|
|
cloudlog.exception("Power monitoring calculation failed:") |
|
|
|
|
|
|
|
|
|
def _perform_integration(self, t, current_power): |
|
|
|
|
self.integration_lock.acquire() |
|
|
|
@ -139,7 +151,3 @@ class PowerMonitoring: |
|
|
|
|
# Get the power usage |
|
|
|
|
def get_power_used(self): |
|
|
|
|
return int(self.power_used_uWh) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|