diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index c96cf6953b..e03ef69855 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -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) - - - - diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 40d546def2..8870ffcc09 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -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()