mirror of
https://github.com/MoreTore/openpilot.git
synced 2026-07-26 12:22:04 +08:00
181 lines
4.7 KiB
Python
181 lines
4.7 KiB
Python
"""Utilities for reading real time clocks and keeping soft real time constraints."""
|
|
import gc
|
|
import os
|
|
import time
|
|
from collections import deque
|
|
|
|
from setproctitle import getproctitle
|
|
|
|
from openpilot.system.hardware import PC
|
|
|
|
|
|
# time step for each process
|
|
DT_CTRL = 0.01 # controlsd
|
|
DT_MDL = 0.05 # model
|
|
DT_HW = 0.5 # hardwared and manager
|
|
DT_DMON = 0.05 # driver monitoring
|
|
|
|
|
|
class Priority:
|
|
# CORE 2
|
|
# - modeld = 55
|
|
# - camerad = 54
|
|
CTRL_LOW = 51 # plannerd & radard
|
|
|
|
# CORE 3
|
|
# - pandad = 55
|
|
CTRL_HIGH = 53
|
|
|
|
|
|
def set_realtime_priority(level: int) -> None:
|
|
if not PC:
|
|
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level))
|
|
|
|
|
|
def set_core_affinity(cores: list[int]) -> None:
|
|
if not PC:
|
|
os.sched_setaffinity(0, cores)
|
|
|
|
|
|
def config_realtime_process(cores: int | list[int], priority: int) -> None:
|
|
gc.disable()
|
|
set_realtime_priority(priority)
|
|
c = cores if isinstance(cores, list) else [cores, ]
|
|
set_core_affinity(c)
|
|
|
|
|
|
class Ratekeeper:
|
|
def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None:
|
|
"""Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative."""
|
|
self._interval = 1. / rate
|
|
self._next_frame_time = time.monotonic() + self._interval
|
|
self._print_delay_threshold = print_delay_threshold
|
|
self._frame = 0
|
|
self._remaining = 0.0
|
|
self._process_name = getproctitle()
|
|
self._dts = deque([self._interval], maxlen=100)
|
|
self._last_monitor_time = time.monotonic()
|
|
|
|
@property
|
|
def frame(self) -> int:
|
|
return self._frame
|
|
|
|
@property
|
|
def remaining(self) -> float:
|
|
return self._remaining
|
|
|
|
@property
|
|
def lagging(self) -> bool:
|
|
avg_dt = sum(self._dts) / len(self._dts)
|
|
expected_dt = self._interval * (1 / 0.9)
|
|
return avg_dt > expected_dt
|
|
|
|
# Maintain loop rate by calling this at the end of each loop
|
|
def keep_time(self) -> bool:
|
|
lagged = self.monitor_time()
|
|
if self._remaining > 0:
|
|
time.sleep(self._remaining)
|
|
return lagged
|
|
|
|
# Monitors the cumulative lag, but does not enforce a rate
|
|
def monitor_time(self) -> bool:
|
|
prev = self._last_monitor_time
|
|
self._last_monitor_time = time.monotonic()
|
|
self._dts.append(self._last_monitor_time - prev)
|
|
|
|
lagged = False
|
|
remaining = self._next_frame_time - time.monotonic()
|
|
self._next_frame_time += self._interval
|
|
if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold:
|
|
print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms")
|
|
lagged = True
|
|
self._frame += 1
|
|
self._remaining = remaining
|
|
return lagged
|
|
|
|
|
|
class DurationTimer:
|
|
def __init__(self, duration=0, step=DT_CTRL) -> None:
|
|
self.step = step
|
|
self.duration = duration
|
|
self.was_reset = False
|
|
self.timer = 0
|
|
self.min = float("-inf") # type: float
|
|
self.max = float("inf") # type: float
|
|
|
|
def tick_obj(self) -> None:
|
|
self.timer += self.step
|
|
# reset on overflow
|
|
self.timer = 0 if (self.timer == (self.max or self.min)) else self.timer
|
|
|
|
def reset(self) -> None:
|
|
"""Resets this objects timer"""
|
|
self.timer = 0
|
|
self.was_reset = True
|
|
|
|
def active(self) -> bool:
|
|
"""Returns true if time since last reset is less than duration"""
|
|
return bool(round(self.timer,2) < self.duration)
|
|
|
|
def adjust(self, duration) -> None:
|
|
"""Adjusts the duration of the timer"""
|
|
self.duration = duration
|
|
|
|
def once_after_reset(self) -> bool:
|
|
"""Returns true only one time after calling reset()"""
|
|
ret = self.was_reset
|
|
self.was_reset = False
|
|
return ret
|
|
|
|
@staticmethod
|
|
def interval_obj(rate, frame) -> bool:
|
|
if frame % rate == 0: # Highlighting shows "frame" in white
|
|
return True
|
|
return False
|
|
|
|
class ModelTimer(DurationTimer):
|
|
frame: int = 0
|
|
objects: list = []
|
|
def __init__(self, duration=0) -> None:
|
|
self.step = DT_MDL
|
|
super().__init__(duration, self.step)
|
|
self.__class__.objects.append(self)
|
|
|
|
@classmethod
|
|
def tick(cls) -> None:
|
|
cls.frame += 1
|
|
for obj in cls.objects:
|
|
ModelTimer.tick_obj(obj)
|
|
|
|
@classmethod
|
|
def reset_all(cls) -> None:
|
|
for obj in cls.objects:
|
|
obj.reset()
|
|
|
|
@classmethod
|
|
def interval(cls, rate) -> bool:
|
|
return ModelTimer.interval_obj(rate, cls.frame)
|
|
|
|
class ControlsTimer(DurationTimer):
|
|
frame = 0
|
|
objects = [] # type: list[DurationTimer]
|
|
def __init__(self, duration=0) -> None:
|
|
self.step = DT_CTRL
|
|
super().__init__(duration=duration, step=self.step)
|
|
self.__class__.objects.append(self)
|
|
|
|
@classmethod
|
|
def tick(cls) -> None:
|
|
cls.frame += 1
|
|
for obj in cls.objects:
|
|
ControlsTimer.tick_obj(obj)
|
|
|
|
@classmethod
|
|
def reset_all(cls) -> None:
|
|
for obj in cls.objects:
|
|
obj.reset()
|
|
|
|
@classmethod
|
|
def interval(cls, rate) -> bool:
|
|
return ControlsTimer.interval_obj(rate, cls.frame)
|