Small thermald cleanup
parent
2338ff9790
commit
65ad31a7e4
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue