Small thermald cleanup

old-commit-hash: 65ad31a7e4
commatwo_master
Willem Melching 5 years ago
parent a59b0f06d2
commit 6c6df5486a
  1. 48
      selfdrive/thermald/power_monitoring.py
  2. 29
      selfdrive/thermald/thermald.py

@ -1,38 +1,48 @@
import time
import datetime import datetime
import threading
import random import random
import threading
import time
from statistics import mean from statistics import mean
from cereal import log from cereal import log
from selfdrive.swaglog import cloudlog
PANDA_OUTPUT_VOLTAGE = 5.28 PANDA_OUTPUT_VOLTAGE = 5.28
# Parameters # Parameters
def get_battery_capacity(): def get_battery_capacity():
return _read_param("/sys/class/power_supply/battery/capacity", int) return _read_param("/sys/class/power_supply/battery/capacity", int)
def get_battery_status(): 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 # 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(), '') return _read_param("/sys/class/power_supply/battery/status", lambda x: x.strip(), '')
def get_battery_current(): def get_battery_current():
return _read_param("/sys/class/power_supply/battery/current_now", int) return _read_param("/sys/class/power_supply/battery/current_now", int)
def get_battery_voltage(): def get_battery_voltage():
return _read_param("/sys/class/power_supply/battery/voltage_now", int) return _read_param("/sys/class/power_supply/battery/voltage_now", int)
def get_usb_present(): def get_usb_present():
return _read_param("/sys/class/power_supply/usb/present", lambda x: bool(int(x)), False) return _read_param("/sys/class/power_supply/usb/present", lambda x: bool(int(x)), False)
def get_battery_charging(): def get_battery_charging():
# This does correspond with actually charging # This does correspond with actually charging
return _read_param("/sys/class/power_supply/battery/charge_type", lambda x: x.strip() != "N/A", False) return _read_param("/sys/class/power_supply/battery/charge_type", lambda x: x.strip() != "N/A", False)
def set_battery_charging(on): def set_battery_charging(on):
with open('/sys/class/power_supply/battery/charging_enabled', 'w') as f: with open('/sys/class/power_supply/battery/charging_enabled', 'w') as f:
f.write(f"{1 if on else 0}\n") f.write(f"{1 if on else 0}\n")
# Helpers # Helpers
def _read_param(path, parser, default=0): def _read_param(path, parser, default=0):
try: try:
@ -41,10 +51,12 @@ def _read_param(path, parser, default=0):
except Exception: except Exception:
return default return default
def panda_current_to_actual_current(panda_current): def panda_current_to_actual_current(panda_current):
# From white/grey panda schematic # From white/grey panda schematic
return (3.3 - (panda_current * 3.3 / 4096)) / 8.25 return (3.3 - (panda_current * 3.3 / 4096)) / 8.25
class PowerMonitoring: class PowerMonitoring:
def __init__(self): def __init__(self):
self.last_measurement_time = None # Used for integration delta self.last_measurement_time = None # Used for integration delta
@ -63,13 +75,13 @@ class PowerMonitoring:
# Only integrate when there is no ignition # 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 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.last_measurement_time = None
self.power_used_uWh = 0 self.power_used_uWh = 0
return return
# First measurement, set integration time # First measurement, set integration time
if self.last_measurement_time == None: if self.last_measurement_time is None:
self.last_measurement_time = now self.last_measurement_time = now
return return
@ -78,13 +90,13 @@ class PowerMonitoring:
if get_battery_status() == 'Discharging': if get_battery_status() == 'Discharging':
# If the battery is discharging, we can use this measurement # 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 # 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): 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 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 # 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% # This seems to be accurate to about 5%
current_power = (PANDA_OUTPUT_VOLTAGE * panda_current_to_actual_current(health.health.current)) 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??? # TODO: Figure out why this is off by a factor of 3/4???
FUDGE_FACTOR = 1.33 FUDGE_FACTOR = 1.33
@ -93,7 +105,7 @@ class PowerMonitoring:
try: try:
set_battery_charging(False) set_battery_charging(False)
time.sleep(5) time.sleep(5)
# Measure for a few sec to get a good average # Measure for a few sec to get a good average
voltages = [] voltages = []
currents = [] currents = []
@ -101,20 +113,20 @@ class PowerMonitoring:
voltages.append(get_battery_voltage()) voltages.append(get_battery_voltage())
currents.append(get_battery_current()) currents.append(get_battery_current())
time.sleep(1) 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) self._perform_integration(now, current_power * FUDGE_FACTOR)
# Enable charging again # Enable charging again
set_battery_charging(True) set_battery_charging(True)
except Exception as e: except Exception:
print("Pulsed power measurement failed:", str(e)) cloudlog.exception("Pulsed power measurement failed")
# Start pulsed measurement and return # Start pulsed measurement and return
threading.Thread(target=perform_pulse_measurement, args=(now,)).start() threading.Thread(target=perform_pulse_measurement, args=(now,)).start()
self.next_pulsed_measurement_time = None self.next_pulsed_measurement_time = None
return 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 # 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 # 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 # 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 # Do the integration
self._perform_integration(now, current_power) self._perform_integration(now, current_power)
except Exception as e: except Exception:
print("Power monitoring calculation failed:", str(e)) cloudlog.exception("Power monitoring calculation failed:")
def _perform_integration(self, t, current_power): def _perform_integration(self, t, current_power):
self.integration_lock.acquire() self.integration_lock.acquire()
@ -139,7 +151,3 @@ class PowerMonitoring:
# Get the power usage # Get the power usage
def get_power_used(self): def get_power_used(self):
return int(self.power_used_uWh) return int(self.power_used_uWh)

@ -28,10 +28,14 @@ CURRENT_TAU = 15. # 15s time constant
DAYS_NO_CONNECTIVITY_MAX = 7 # do not allow to engage after a week without internet DAYS_NO_CONNECTIVITY_MAX = 7 # do not allow to engage after a week without internet
DAYS_NO_CONNECTIVITY_PROMPT = 4 # send an offroad prompt after 4 days with no internet DAYS_NO_CONNECTIVITY_PROMPT = 4 # send an offroad prompt after 4 days with no internet
LEON = False
last_eon_fan_val = None
with open(BASEDIR + "/selfdrive/controls/lib/alerts_offroad.json") as json_file: with open(BASEDIR + "/selfdrive/controls/lib/alerts_offroad.json") as json_file:
OFFROAD_ALERTS = json.load(json_file) OFFROAD_ALERTS = json.load(json_file)
def read_tz(x, clip=True): def read_tz(x, clip=True):
if not ANDROID: if not ANDROID:
# we don't monitor thermal on PC # we don't monitor thermal on PC
@ -46,6 +50,7 @@ def read_tz(x, clip=True):
return ret return ret
def read_thermal(): def read_thermal():
dat = messaging.new_message('thermal') dat = messaging.new_message('thermal')
dat.thermal.cpu0 = read_tz(5) dat.thermal.cpu0 = read_tz(5)
@ -58,7 +63,7 @@ def read_thermal():
dat.thermal.pa0 = read_tz(25) dat.thermal.pa0 = read_tz(25)
return dat return dat
LEON = False
def setup_eon_fan(): def setup_eon_fan():
global LEON global LEON
@ -72,11 +77,10 @@ def setup_eon_fan():
bus.write_byte_data(0x21, 0x04, 0x4) # manual override source bus.write_byte_data(0x21, 0x04, 0x4) # manual override source
except IOError: except IOError:
print("LEON detected") print("LEON detected")
#os.system("echo 1 > /sys/devices/soc/6a00000.ssusb/power_supply/usb/usb_otg")
LEON = True LEON = True
bus.close() bus.close()
last_eon_fan_val = None
def set_eon_fan(val): def set_eon_fan(val):
global LEON, last_eon_fan_val global LEON, last_eon_fan_val
@ -102,6 +106,7 @@ def set_eon_fan(val):
bus.close() bus.close()
last_eon_fan_val = val last_eon_fan_val = val
# temp thresholds to control fan speed - high hysteresis # temp thresholds to control fan speed - high hysteresis
_TEMP_THRS_H = [50., 65., 80., 10000] _TEMP_THRS_H = [50., 65., 80., 10000]
# temp thresholds to control fan speed - low hysteresis # temp thresholds to control fan speed - low hysteresis
@ -127,7 +132,7 @@ def handle_fan_eon(max_cpu_temp, bat_temp, fan_speed, ignition):
# no max fan speed unless battery is hot # no max fan speed unless battery is hot
fan_speed = min(fan_speed, _FAN_SPEEDS[-2]) fan_speed = min(fan_speed, _FAN_SPEEDS[-2])
set_eon_fan(fan_speed//16384) set_eon_fan(fan_speed // 16384)
return fan_speed return fan_speed
@ -140,6 +145,7 @@ def handle_fan_uno(max_cpu_temp, bat_temp, fan_speed, ignition):
return new_speed return new_speed
def thermald_thread(): def thermald_thread():
# prevent LEECO from undervoltage # prevent LEECO from undervoltage
BATT_PERC_OFF = 10 if LEON else 3 BATT_PERC_OFF = 10 if LEON else 3
@ -227,7 +233,7 @@ def thermald_thread():
max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1, max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1,
msg.thermal.cpu2, msg.thermal.cpu3) / 10.0 msg.thermal.cpu2, msg.thermal.cpu3) / 10.0
max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.) max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.)
bat_temp = msg.thermal.bat/1000. bat_temp = msg.thermal.bat / 1000.
fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition) fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition)
msg.thermal.fanSpeed = fan_speed msg.thermal.fanSpeed = fan_speed
@ -236,7 +242,7 @@ def thermald_thread():
if max_cpu_temp > 107. or bat_temp >= 63.: if max_cpu_temp > 107. or bat_temp >= 63.:
# onroad not allowed # onroad not allowed
thermal_status = ThermalStatus.danger thermal_status = ThermalStatus.danger
elif max_comp_temp > 92.5 or bat_temp > 60.: # CPU throttling starts around ~90C elif max_comp_temp > 92.5 or bat_temp > 60.: # CPU throttling starts around ~90C
# hysteresis between onroad not allowed and engage not allowed # hysteresis between onroad not allowed and engage not allowed
thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger)
elif max_cpu_temp > 87.5: elif max_cpu_temp > 87.5:
@ -382,15 +388,13 @@ def thermald_thread():
fw_version_match_prev = fw_version_match fw_version_match_prev = fw_version_match
should_start_prev = should_start should_start_prev = should_start
#print(msg)
# report to server once per minute # report to server once per minute
if (count % int(60. / DT_TRML)) == 0: if (count % int(60. / DT_TRML)) == 0:
cloudlog.event("STATUS_PACKET", cloudlog.event("STATUS_PACKET",
count=count, count=count,
health=(health.to_dict() if health else None), health=(health.to_dict() if health else None),
location=(location.to_dict() if location else None), location=(location.to_dict() if location else None),
thermal=msg.to_dict()) thermal=msg.to_dict())
count += 1 count += 1
@ -398,5 +402,6 @@ def thermald_thread():
def main(): def main():
thermald_thread() thermald_thread()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

Loading…
Cancel
Save