mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 13:24:13 +08:00
Keep native replay display visible through audio drain
This commit is contained in:
@@ -83,9 +83,23 @@ done
|
||||
bridge_status=0
|
||||
wait "$bridge_pid" || bridge_status=$?
|
||||
bridge_pid=""
|
||||
for attempt in $(seq 1 8); do
|
||||
kill -0 "$audio_pid" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# EOF closes the audio stream; keep the replay display alive while it drains.
|
||||
# Never silently turn a slow/failed audio shutdown into a successful archive.
|
||||
if [ -n "$audio_pid" ]; then
|
||||
for attempt in $(seq 1 150); do
|
||||
kill -0 "$audio_pid" 2>/dev/null || break
|
||||
sleep .2
|
||||
done
|
||||
if kill -0 "$audio_pid" 2>/dev/null; then
|
||||
echo 'RoadScore audio did not finish within 30 seconds after replay EOF'
|
||||
exit 1
|
||||
fi
|
||||
audio_status=0
|
||||
wait "$audio_pid" || audio_status=$?
|
||||
audio_pid=""
|
||||
if [ "$audio_status" != 0 ]; then
|
||||
cat results/current/app.log
|
||||
exit "$audio_status"
|
||||
fi
|
||||
fi
|
||||
exit "$bridge_status"
|
||||
|
||||
@@ -61,6 +61,7 @@ env['ROADSCORE_COMPOSITION_POLICY']=composition_policy
|
||||
env['ROADSCORE_COMPOSER']=a.composer
|
||||
env['ROADSCORE_ACE_PROFILE']=a.profile
|
||||
if native:env.setdefault('ROADSCORE_REPLAY_PRIME','1')
|
||||
if native and not a.replay:env['ROADSCORE_AUDIO_DRAIN_FILE']=str(R/'results/current/audio_drained.json')
|
||||
env['ROADSCORE_OVERLAY_CAPTURE']=str(out/'overlay.png')
|
||||
env['ROADSCORE_STATUS_FILE']=str(out/'roadscore_status.json')
|
||||
initial_display={'readiness':'Preparing','composer':a.composer,'profile':a.profile if a.composer=='ace' else None,'render_mode':a.render_mode,'style':a.profile.title() if a.composer=='ace' else settings.style}
|
||||
|
||||
@@ -3,6 +3,14 @@ import os,json,time,runpy
|
||||
from pathlib import Path
|
||||
from openpilot.selfdrive.ui.ui_state import UIState, device
|
||||
from preparing_awake import PreparationWake
|
||||
from replay_display_hold import ReplayDisplayHold
|
||||
display_hold=ReplayDisplayHold(os.environ.get("ROADSCORE_AUDIO_DRAIN_FILE"), enabled=os.environ.get("OPENPILOT_PREFIX")=="roadscore_replay")
|
||||
original_state=UIState._update_state
|
||||
def replay_state(self,*args,**kwargs):
|
||||
result=original_state(self,*args,**kwargs)
|
||||
self.started=display_hold.apply(self.started)
|
||||
return result
|
||||
UIState._update_state=replay_state
|
||||
preparation_wake=PreparationWake(os.environ.get("ROADSCORE_STATUS_FILE"),Path("/TICI").exists())
|
||||
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
|
||||
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Keep only the replay presentation visible while its final audio drains."""
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
|
||||
class ReplayDisplayHold:
|
||||
def __init__(self, marker=None, enabled=False, clock=time.monotonic, maximum_hold=20.):
|
||||
self.marker = Path(marker) if marker else None
|
||||
self.enabled = bool(enabled and marker)
|
||||
self.clock = clock
|
||||
self.started_at = clock()
|
||||
self.maximum_hold = maximum_hold
|
||||
self.seen_onroad = False
|
||||
self.holding_since = None
|
||||
self.released = False
|
||||
|
||||
def apply(self, started):
|
||||
if not self.enabled or self.released:
|
||||
return started
|
||||
now = self.clock()
|
||||
try:
|
||||
value = json.loads(self.marker.read_text())
|
||||
wall = value.get('wall')
|
||||
if (isinstance(wall, (int, float)) and math.isfinite(wall)
|
||||
and self.started_at <= wall <= now and type(value.get('drained')) is bool):
|
||||
self.released = True
|
||||
return started
|
||||
except (OSError, ValueError, TypeError):
|
||||
pass
|
||||
if started:
|
||||
self.seen_onroad = True
|
||||
self.holding_since = None
|
||||
return True
|
||||
if not self.seen_onroad:
|
||||
return False
|
||||
if self.holding_since is None:
|
||||
self.holding_since = now
|
||||
if now - self.holding_since >= self.maximum_hold:
|
||||
self.released = True
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from replay_display_hold import ReplayDisplayHold
|
||||
|
||||
class DisplayHoldTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp=tempfile.TemporaryDirectory();self.addCleanup(self.tmp.cleanup)
|
||||
self.path=Path(self.tmp.name)/'audio_drained.json';self.now=100.
|
||||
self.hold=ReplayDisplayHold(self.path,True,lambda:self.now)
|
||||
def marker(self,wall,drained=True):self.path.write_text(json.dumps(dict(wall=wall,drained=drained)))
|
||||
def test_actual_onroad_required(self):
|
||||
self.assertFalse(self.hold.apply(False));self.assertTrue(self.hold.apply(True))
|
||||
self.now+=1;self.assertTrue(self.hold.apply(False))
|
||||
self.now+=2;self.marker(self.now);self.assertFalse(self.hold.apply(False))
|
||||
def test_stale_marker_ignored_and_timeout_bounded(self):
|
||||
self.marker(99);self.hold.apply(True);self.assertTrue(self.hold.apply(False))
|
||||
self.now+=20;self.assertFalse(self.hold.apply(False))
|
||||
def test_failure_releases_and_cannot_relatch(self):
|
||||
self.hold.apply(True);self.marker(100,False)
|
||||
self.assertFalse(self.hold.apply(False));self.assertTrue(self.hold.apply(True));self.assertFalse(self.hold.apply(False))
|
||||
def test_disabled_never_holds(self):
|
||||
hold=ReplayDisplayHold(self.path,False,lambda:self.now)
|
||||
self.assertTrue(hold.apply(True));self.assertFalse(hold.apply(False))
|
||||
def test_partial_and_future_marker_do_not_release(self):
|
||||
self.hold.apply(True);self.path.write_text('{')
|
||||
self.assertTrue(self.hold.apply(False));self.marker(200)
|
||||
self.assertTrue(self.hold.apply(False))
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user