Small thermald cleanup

albatross
Willem Melching 2020-03-12 11:55:33 -07:00
parent 2338ff9790
commit 65ad31a7e4
2 changed files with 45 additions and 32 deletions

View File

@ -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)

View File

@ -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_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:
OFFROAD_ALERTS = json.load(json_file)
def read_tz(x, clip=True):
if not ANDROID:
# we don't monitor thermal on PC
@ -46,6 +50,7 @@ def read_tz(x, clip=True):
return ret
def read_thermal():
dat = messaging.new_message('thermal')
dat.thermal.cpu0 = read_tz(5)
@ -58,7 +63,7 @@ def read_thermal():
dat.thermal.pa0 = read_tz(25)
return dat
LEON = False
def setup_eon_fan():
global LEON
@ -72,11 +77,10 @@ def setup_eon_fan():
bus.write_byte_data(0x21, 0x04, 0x4) # manual override source
except IOError:
print("LEON detected")
#os.system("echo 1 > /sys/devices/soc/6a00000.ssusb/power_supply/usb/usb_otg")
LEON = True
bus.close()
last_eon_fan_val = None
def set_eon_fan(val):
global LEON, last_eon_fan_val
@ -102,6 +106,7 @@ def set_eon_fan(val):
bus.close()
last_eon_fan_val = val
# temp thresholds to control fan speed - high hysteresis
_TEMP_THRS_H = [50., 65., 80., 10000]
# 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
fan_speed = min(fan_speed, _FAN_SPEEDS[-2])
set_eon_fan(fan_speed//16384)
set_eon_fan(fan_speed // 16384)
return fan_speed
@ -140,6 +145,7 @@ def handle_fan_uno(max_cpu_temp, bat_temp, fan_speed, ignition):
return new_speed
def thermald_thread():
# prevent LEECO from undervoltage
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,
msg.thermal.cpu2, msg.thermal.cpu3) / 10.0
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)
msg.thermal.fanSpeed = fan_speed
@ -236,7 +242,7 @@ def thermald_thread():
if max_cpu_temp > 107. or bat_temp >= 63.:
# onroad not allowed
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
thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger)
elif max_cpu_temp > 87.5:
@ -382,15 +388,13 @@ def thermald_thread():
fw_version_match_prev = fw_version_match
should_start_prev = should_start
#print(msg)
# report to server once per minute
if (count % int(60. / DT_TRML)) == 0:
cloudlog.event("STATUS_PACKET",
count=count,
health=(health.to_dict() if health else None),
location=(location.to_dict() if location else None),
thermal=msg.to_dict())
count=count,
health=(health.to_dict() if health else None),
location=(location.to_dict() if location else None),
thermal=msg.to_dict())
count += 1
@ -398,5 +402,6 @@ def thermald_thread():
def main():
thermald_thread()
if __name__ == "__main__":
main()