Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

common: add ParameterUpdater for thread-safe parameter updates #34186

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions common/parameter_updater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import threading
import time

from openpilot.common.params import Params


class ParameterUpdater:
def __init__(self, params_to_update: list[str], update_interval: float = 0.1):
self.params = Params()
self.params_to_update = params_to_update
self.param_values: dict[str, str | None] = {}
self.update_interval = update_interval
self.mutex = threading.Lock()
self.stop_event = threading.Event()
self.update_thread: threading.Thread | None = None

# Initial update
self._update()

def get(self, param: str) -> str | None:
with self.mutex:
return self.param_values[param]

def get_bool(self, param: str) -> bool:
return self.get(param) == b'1'

def get_int(self, param: str, def_val: int = 0) -> int:
value = self.get(param)
try:
return int(value) if value is not None else def_val
except (ValueError, TypeError):
return def_val

def start(self) -> None:
if self.update_thread is None:
self.stop_event.clear()
self.update_thread = threading.Thread(target=self._update_periodically, daemon=True)
self.update_thread.start()

def stop(self) -> None:
if self.update_thread:
self.stop_event.set()
self.update_thread.join()
self.update_thread = None

def _update(self) -> None:
new_values = {param: self.params.get(param) for param in self.params_to_update}
with self.mutex:
self.param_values = new_values

def _update_periodically(self) -> None:
while not self.stop_event.is_set():
self._update()
time.sleep(self.update_interval)
13 changes: 13 additions & 0 deletions common/tests/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import uuid

from openpilot.common.params import Params, ParamKeyType, UnknownKeyName
from openpilot.common.parameter_updater import ParameterUpdater

class TestParams:
def setup_method(self):
Expand Down Expand Up @@ -100,6 +101,18 @@ def _delayed_writer():
assert q.get("CarParams") is None
assert q.get("CarParams", True) == b"1"

def test_parameter_updater(self):
parameter_updater = ParameterUpdater(["DongleId", "CarParams", "IsMetric"])
parameter_updater.start()
Params().put("DongleId", "cb38263377b873ee")
Params().put("CarParams", "123")
Params().put_bool("IsMetric", True)
time.sleep(0.2)
assert parameter_updater.get("DongleId") == b'cb38263377b873ee'
assert parameter_updater.get_bool("IsMetric") is True
assert parameter_updater.get_int("CarParams") == 123
parameter_updater.stop()

def test_params_all_keys(self):
keys = Params().all_keys()

Expand Down
22 changes: 6 additions & 16 deletions selfdrive/car/card.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python3
import os
import time
import threading

import cereal.messaging as messaging

from cereal import car, log

from panda import ALTERNATIVE_EXPERIENCE

from openpilot.common.parameter_updater import ParameterUpdater
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog, ForwardingHandler
Expand Down Expand Up @@ -149,8 +149,7 @@ def __init__(self, CI=None, RI=None) -> None:
self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP)

self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.parameter_updater = ParameterUpdater(['IsMetric', 'ExperimentalMode'])

# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
Expand Down Expand Up @@ -181,7 +180,7 @@ def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]:
self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime

# TODO: mirror the carState.cruiseState struct?
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.parameter_updater.get_bool('IsMetric'))
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
CS.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)

Expand Down Expand Up @@ -239,7 +238,7 @@ def step(self):
CS, RD = self.state_update()

if self.sm['carControl'].enabled and not self.CC_prev.enabled:
self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode)
self.v_cruise_helper.initialize_v_cruise(CS, self.parameter_updater.get_bool('ExperimentalMode'))

self.state_publish(CS, RD)

Expand All @@ -250,23 +249,14 @@ def step(self):

self.initialized_prev = initialized

def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
time.sleep(0.1)

def card_thread(self):
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
self.parameter_updater.start()
while True:
self.step()
self.rk.monitor_time()
finally:
e.set()
t.join()
self.parameter_updater.stop()


def main():
Expand Down
34 changes: 8 additions & 26 deletions selfdrive/selfdrived/selfdrived.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/usr/bin/env python3
import os
import time
import threading

import cereal.messaging as messaging

Expand All @@ -10,6 +8,7 @@
from panda import ALTERNATIVE_EXPERIENCE


from openpilot.common.parameter_updater import ParameterUpdater
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL
from openpilot.common.swaglog import cloudlog
Expand Down Expand Up @@ -83,8 +82,7 @@ def __init__(self, CP=None):
ignore_alive=ignore, ignore_avg_freq=ignore+['radarState',],
ignore_valid=ignore, frequency=int(1/DT_CTRL))

# read params
self.is_metric = self.params.get_bool("IsMetric")
self.parameter_updater = ParameterUpdater(['IsMetric', 'ExperimentalMode', 'LongitudinalPersonality'])
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")

car_recognized = self.CP.carName != 'mock'
Expand All @@ -110,8 +108,7 @@ def __init__(self, CP=None):
self.events_prev = []
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.personality = self.read_personality_param()
self.personality = log.LongitudinalPersonality.standard
self.recalibrating_seen = False
self.state_machine = StateMachine()
self.rk = Ratekeeper(100, print_delay_threshold=None)
Expand Down Expand Up @@ -417,7 +414,7 @@ def update_alerts(self, CS):
clear_event_types.add(ET.NO_ENTRY)

pers = LONGITUDINAL_PERSONALITY_MAP[self.personality]
alerts = self.events.create_alerts(self.state_machine.current_alert_types, [self.CP, CS, self.sm, self.is_metric,
alerts = self.events.create_alerts(self.state_machine.current_alert_types, [self.CP, CS, self.sm, self.parameter_updater.get_bool("IsMetric"),
self.state_machine.soft_disable_timer, pers])
self.AM.add_many(self.sm.frame, alerts)
self.AM.process_alerts(self.sm.frame, clear_event_types)
Expand All @@ -431,7 +428,7 @@ def publish_selfdriveState(self, CS):
ss.active = self.active
ss.state = self.state_machine.state
ss.engageable = not self.events.contains(ET.NO_ENTRY)
ss.experimentalMode = self.experimental_mode
ss.experimentalMode = self.parameter_updater.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
ss.personality = self.personality

ss.alertText1 = self.AM.current_alert.alert_text_1
Expand Down Expand Up @@ -462,30 +459,15 @@ def step(self):

self.CS_prev = CS

def read_personality_param(self):
try:
return int(self.params.get('LongitudinalPersonality'))
except (ValueError, TypeError):
return log.LongitudinalPersonality.standard

def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.read_personality_param()
time.sleep(0.1)

def run(self):
e = threading.Event()
t = threading.Thread(target=self.params_thread, args=(e, ))
try:
t.start()
self.parameter_updater.start()
while True:
self.personality = self.parameter_updater.get_int('LongitudinalPersonality', log.LongitudinalPersonality.standard)
self.step()
self.rk.monitor_time()
finally:
e.set()
t.join()
self.parameter_updater.stop()


def main():
Expand Down
Loading