Skip to content

Commit

Permalink
improve coach startup (#320)
Browse files Browse the repository at this point in the history
  • Loading branch information
durandom authored Jun 20, 2023
1 parent 9bef582 commit 29fbea7
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 84 deletions.
104 changes: 69 additions & 35 deletions components/paddock/telemetry/pitcrew/coach.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
class Coach(LoggingMixin):
def __init__(self, history: History, db_coach: DbCoach, debug=False):
self.history = history
self.previous_history_error = None
self.db_coach = db_coach
self.messages = []
self.previous_distance = 10_000_000
Expand All @@ -30,6 +29,9 @@ def __init__(self, history: History, db_coach: DbCoach, debug=False):
self.session_id = "NO_SESSION"
self.track_walk = False
self.distance = 0
self._new_session_starting = False
self._next_messages = []
self._error = None

def filter_from_topic(self, topic):
frags = topic.split("/")
Expand All @@ -47,47 +49,79 @@ def filter_from_topic(self, topic):
}
return filter

def set_filter(self, filter):
def new_session(self, topic):
self._new_session_starting = True
self.topic = topic
self.log_debug("new session %s", topic)
filter = self.filter_from_topic(topic)
self.history.set_filter(filter)
self.session_id = filter.get("SessionId", "NO_SESSION")
self.messages = []
self._next_messages = []
self.responses = {}
self.db_coach.refresh_from_db()
self.track_walk = self.db_coach.track_walk

def ready(self):
if self._new_session_starting:
if not self.history.is_ready():
if self.history.is_initializing():
return False
error = self.history.get_and_reset_error()
if error:
self.db_coach.error = error
self.db_coach.save()
self._error = error
return False

self.init_messages()

startup_message = "start coaching "
try:
lap_time = self.history.fast_lap.laps.first().time_human()
startup_message += f"for a lap time of {lap_time}"
except Exception:
pass

if self.track_walk:
startup_message += " doing a track walk"

self.say_next(startup_message)
self.db_coach.status = startup_message
self.db_coach.fast_lap = self.history.fast_lap
self.db_coach.error = ""
self.db_coach.save()

self.track_length = self.history.track_length

self._new_session_starting = False

return True

def get_and_reset_error(self):
if self._error:
error = self._error
self._error = None
return error

def say_next(self, msg):
self._next_messages.append(msg)

def notify(self, topic, telemetry, now=None):
now = now or django.utils.timezone.now()
if self.topic != topic:
self.topic = topic
self.log_debug("new session %s", topic)
self.set_filter(self.filter_from_topic(topic))
self.startup_message = ""

if not self.history.ready:
if self.history.error:
self.db_coach.error = self.history.error
self.db_coach.save()
# clear history error
error_msg = self.history.error
self.history.error = None
return (self.response_topic, error_msg)
self.new_session(topic)

if not self.ready():
error = self.get_and_reset_error()
if error:
return (self.response_topic, error)
return None

if self.history.ready and self.history.startup_message:
self.track_length = self.history.track.length
if self.startup_message != self.history.startup_message:
self.startup_message = self.history.startup_message
self.history.startup_message = ""
self.db_coach.refresh_from_db()
self.track_walk = self.db_coach.track_walk
if self.track_walk:
self.startup_message += " doing a track walk"
self.db_coach.status = self.startup_message
self.db_coach.fast_lap = self.history.fast_lap
self.db_coach.error = ""
self.db_coach.save()
return (self.response_topic, self.startup_message)

if not self.messages:
self.init_messages()
self.responses = {}
if self._next_messages:
messages = self._next_messages
self._next_messages = []
return (self.response_topic, messages)

self.distance = int(telemetry["DistanceRoundTrack"])
if self.distance == self.previous_distance:
Expand Down Expand Up @@ -135,7 +169,7 @@ def collect_responses(self, distance, telemetry):

# FIXME: +100 should be speed dependent
#
future_distance = (distance + 20) % self.history.track_length
future_distance = (distance + 20) % self.track_length
responses = self.get_responses(telemetry, future_distance)
for response in responses:
distance = response["distance"]
Expand All @@ -150,7 +184,7 @@ def collect_responses(self, distance, telemetry):
# so we send the message less than 10 seconds before its due
# if we send messages too late, the driver will have passed the distance
# find the distance where
future_distance = (distance + 10) % self.history.track_length
future_distance = (distance + 10) % self.track_length
responses = self.responses.pop(future_distance, None)
if responses:
if len(responses) > 1:
Expand Down
82 changes: 35 additions & 47 deletions components/paddock/telemetry/pitcrew/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@

class History(LoggingMixin):
def __init__(self):
self.pickle = False
self.do_init = False
self._do_init = False
self.segments = []
self.previous_update_meters = 0
self.ready = False
self.error = None
self._ready = False
self._error = None
self.do_run = True
self.driver = None
self.track_length = 0
Expand All @@ -25,7 +24,6 @@ def __init__(self):
self.fast_lap = None
self.process_segments = []
self.threaded = False
self.startup_message = ""
self.session_id = "NO_SESSION"

def disconnect(self):
Expand All @@ -35,22 +33,27 @@ def run(self):
self.threaded = True
while self.do_run:
time.sleep(0.1)
self.do_work()
if self.do_init:
if self.init():
self.do_init = False
if self._ready:
self.do_work()
if self._do_init:
self.init()
self._do_init = False

def set_filter(self, filter):
self._ready = False
self.filter = filter
self.session_id = filter.get("SessionId", "NO_SESSION")
# set to false to avoid race condition
# this method is called from the coach thread
self.ready = False
self.do_init = True
self._do_init = True

def is_initializing(self):
return self._do_init

def is_ready(self):
return self._ready

def init(self):
self.ready = False
self.error = None
self._ready = False
self._error = None
self.process_segments = []
self.telemetry = []

Expand All @@ -63,39 +66,24 @@ def init(self):
except Exception as e:
error = f"Error init {self.filter['Driver']} / {self.filter['GameName']}"
error += f" / {self.filter['CarModel']}/ {self.filter['TrackCode']} - {e}"
self.error = error
self._error = error
self.log_error(error)
return False

success = self.init_segments()
if not success:
return False

self.init_driver()

# self.telemetry_fields = [
# "DistanceRoundTrack",
# "Gear",
# "SpeedMs",
# "Throttle",
# "Brake",
# "CurrentLapTime",
# ]
# self.telemetry = {"_time": []}
# for field in self.telemetry_fields:
# self.telemetry[field] = []

self.ready = True
return True
if self.init_segments():
if self.init_driver():
self._ready = True
return True

def init_segments(self) -> bool:
"""Load the segments from DB."""
fast_lap = FastLap.objects.filter(track=self.track, car=self.car, game=self.game).first()
if not fast_lap:
self.error = f"no data found for game {self.filter['GameName']}"
self.error += f" on track {self.filter['TrackCode']}"
self.error += f" in car {self.filter['CarModel']}"
self.log_error(self.error)
error = f"no data found for game {self.filter['GameName']}"
error += f" on track {self.filter['TrackCode']}"
error += f" in car {self.filter['CarModel']}"
self._error = error
self.log_error(error)
return False

self.log_debug("loading segments for %s %s - %s", self.game, self.track, self.car)
Expand All @@ -121,15 +109,14 @@ def init_segments(self) -> bool:
self.fast_lap = fast_lap

# self.log_debug("loaded %s segments", len(self.segments))

self.startup_message = "start coaching "
try:
lap_time = self.fast_lap.laps.first().time_human()
self.startup_message += f"for a lap time of {lap_time}"
except Exception:
pass
return True

def get_and_reset_error(self):
if self._error:
error = self._error
self._error = None
return error

def features(self, segment, mark="brake"):
# search through segements
for item in self.track_info:
Expand All @@ -153,6 +140,7 @@ def sort_segments(self, distance=0):
def init_driver(self):
self.driver_segments = {}
self.driver_data = {}
return True
# fast_lap, created = FastLap.objects.get_or_create(
# car=self.car, track=self.track, game=self.game, driver=self.driver
# )
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[('/coach/durandom', 'start coaching for a lap time of 1 minute 37.01 seconds '),
[('/coach/durandom', ['start coaching for a lap time of 1 minute 37.01 seconds ']),
('/coach/durandom', ['{"distance": 447, "message": "80 percent", "priority": 8}']),
('/coach/durandom', ['{"distance": 1077, "message": "40 percent", "priority": 8}']),
('/coach/durandom', ['{"distance": 1208, "message": "brake", "priority": 8}']),
Expand Down
3 changes: 2 additions & 1 deletion components/paddock/telemetry/tests/test_coach.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ def test_coach(self):
coach.notify(topic, row)
if use_threads:
print("waiting for history to be ready")
while not history.ready:
while not history._ready:
time.sleep(0.1)
else:
history.init()
history._do_init = False

captured_responses = []
try:
Expand Down

0 comments on commit 29fbea7

Please sign in to comment.