|
|
|
@ -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. |
|
|
|
|
# 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 |
|
|
|
|
|
|
|
|
@ -84,7 +96,7 @@ class PowerMonitoring: |
|
|
|
|
# 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 |
|
|
|
|
|
|
|
|
@ -106,15 +118,15 @@ class PowerMonitoring: |
|
|
|
|
|
|
|
|
|
# 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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|