mirror of
https://github.com/ajouatom/openpilot.git
synced 2026-06-08 11:04:57 +08:00
c3 ui fix. (#294)
This commit is contained in:
@@ -107,6 +107,7 @@ class CarState(CarStateBase):
|
|||||||
self.manual_speed_limit_assist = None
|
self.manual_speed_limit_assist = None
|
||||||
self.accelerator = None
|
self.accelerator = None
|
||||||
self.blinkers = None
|
self.blinkers = None
|
||||||
|
self.blinkers_alt = None
|
||||||
self.doors_seatbelts = None
|
self.doors_seatbelts = None
|
||||||
self.cruise_buttons_alt2 = None
|
self.cruise_buttons_alt2 = None
|
||||||
|
|
||||||
@@ -248,6 +249,7 @@ class CarState(CarStateBase):
|
|||||||
if self.gear_msg_canfd == "ACCELERATOR":
|
if self.gear_msg_canfd == "ACCELERATOR":
|
||||||
add_and_cache(self.cp, "ACCELERATOR", "accelerator", ignore_counter = True)
|
add_and_cache(self.cp, "ACCELERATOR", "accelerator", ignore_counter = True)
|
||||||
add_and_cache(self.cp, "BLINKERS", "blinkers")
|
add_and_cache(self.cp, "BLINKERS", "blinkers")
|
||||||
|
add_and_cache(self.cp, "BLINKERS_ALT", "blinkers_alt")
|
||||||
add_and_cache(self.cp, "DOORS_SEATBELTS", "doors_seatbelts")
|
add_and_cache(self.cp, "DOORS_SEATBELTS", "doors_seatbelts")
|
||||||
elif self.controls_ready_count == 126:
|
elif self.controls_ready_count == 126:
|
||||||
add_and_cache(self.cp, "CRUISE_BUTTONS_ALT2", "cruise_buttons_alt2", ignore_counter = True)
|
add_and_cache(self.cp, "CRUISE_BUTTONS_ALT2", "cruise_buttons_alt2", ignore_counter = True)
|
||||||
@@ -534,8 +536,8 @@ class CarState(CarStateBase):
|
|||||||
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 or cp.vl["MDPS"]["LFA2_FAULT"] != 0
|
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 or cp.vl["MDPS"]["LFA2_FAULT"] != 0
|
||||||
#ret.steerFaultTemporary = False
|
#ret.steerFaultTemporary = False
|
||||||
|
|
||||||
if self.blinkers is not None:
|
blinkers_info = self.blinkers if self.blinkers is not None else self.blinkers_alt if self.blinkers_alt is not None else None
|
||||||
blinkers_info = self.blinkers
|
if blinkers_info is not None:
|
||||||
left_blinker_lamp = blinkers_info["LEFT_LAMP"] or blinkers_info["LEFT_LAMP_ALT"]
|
left_blinker_lamp = blinkers_info["LEFT_LAMP"] or blinkers_info["LEFT_LAMP_ALT"]
|
||||||
right_blinker_lamp = blinkers_info["RIGHT_LAMP"] or blinkers_info["RIGHT_LAMP_ALT"]
|
right_blinker_lamp = blinkers_info["RIGHT_LAMP"] or blinkers_info["RIGHT_LAMP_ALT"]
|
||||||
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, left_blinker_lamp, right_blinker_lamp)
|
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, left_blinker_lamp, right_blinker_lamp)
|
||||||
|
|||||||
@@ -766,6 +766,16 @@ BO_ 961 BLINKER_STALKS: 8 XXX
|
|||||||
SG_ LEFT_BLINKER : 30|1@0+ (1,0) [0|1] "" XXX
|
SG_ LEFT_BLINKER : 30|1@0+ (1,0) [0|1] "" XXX
|
||||||
SG_ LIGHT_KNOB_POSITION : 21|2@0+ (1,0) [0|3] "" XXX
|
SG_ LIGHT_KNOB_POSITION : 21|2@0+ (1,0) [0|3] "" XXX
|
||||||
|
|
||||||
|
BO_ 995 BLINKERS_ALT: 16 XXX
|
||||||
|
SG_ LEFT_BLINKER : 74|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ RIGHT_BLINKER : 76|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ LEFT_LAMP_ALT : 89|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ LEFT_LAMP : 90|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ RIGHT_LAMP_ALT : 91|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ RIGHT_LAMP : 92|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ NEW_SIGNAL_2 : 95|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ BLINKER_ACTIVE : 97|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
|
||||||
BO_ 1041 DOORS_SEATBELTS: 8 XXX
|
BO_ 1041 DOORS_SEATBELTS: 8 XXX
|
||||||
SG_ CHECKSUM_MAYBE : 7|8@0+ (1,0) [0|65535] "" XXX
|
SG_ CHECKSUM_MAYBE : 7|8@0+ (1,0) [0|65535] "" XXX
|
||||||
SG_ COUNTER_ALT : 15|4@0+ (1,0) [0|15] "" XXX
|
SG_ COUNTER_ALT : 15|4@0+ (1,0) [0|15] "" XXX
|
||||||
@@ -851,6 +861,12 @@ BO_ 687 STEER_TOUCH_2AF: 8 XXX
|
|||||||
SG_ NEW_SIGNAL_3 : 55|8@0+ (1,0) [0|255] "" XXX
|
SG_ NEW_SIGNAL_3 : 55|8@0+ (1,0) [0|255] "" XXX
|
||||||
SG_ NEW_SIGNAL_4 : 63|8@0+ (1,0) [0|255] "" XXX
|
SG_ NEW_SIGNAL_4 : 63|8@0+ (1,0) [0|255] "" XXX
|
||||||
|
|
||||||
|
BO_ 1052 LIGHT_SENSOR: 8 XXX
|
||||||
|
SG_ CHECKSUM : 7|8@0+ (1,0) [0|255] "" XXX
|
||||||
|
SG_ COUNTER : 15|4@0+ (1,0) [0|15] "" XXX
|
||||||
|
SG_ IS_DARK : 19|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ LIGHT_LEVEL : 24|10@1+ (1,0) [0|1023] "" XXX
|
||||||
|
|
||||||
BO_ 1144 DRIVE_MODE: 8 XXX
|
BO_ 1144 DRIVE_MODE: 8 XXX
|
||||||
SG_ DRIVE_MODE : 0|16@1+ (1,-61611) [0|61611] "" XXX
|
SG_ DRIVE_MODE : 0|16@1+ (1,-61611) [0|61611] "" XXX
|
||||||
SG_ DRIVE_MODE2 : 28|3@1+ (1,0) [1|3] "" XXX
|
SG_ DRIVE_MODE2 : 28|3@1+ (1,0) [1|3] "" XXX
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
selfdrive/test/.gitignore
vendored
2
selfdrive/test/.gitignore
vendored
@@ -3,7 +3,7 @@ docker_out/
|
|||||||
|
|
||||||
process_replay/diff.txt
|
process_replay/diff.txt
|
||||||
process_replay/model_diff.txt
|
process_replay/model_diff.txt
|
||||||
|
process_replay/fakedata/
|
||||||
valgrind_logs.txt
|
valgrind_logs.txt
|
||||||
|
|
||||||
*.bz2
|
|
||||||
*.hevc
|
*.hevc
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# To build sim and docs, you can run the following to mount the scons cache to the same place as in CI:
|
|
||||||
# mkdir -p .ci_cache/scons_cache
|
|
||||||
# sudo mount --bind /tmp/scons_cache/ .ci_cache/scons_cache
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$0")
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
OPENPILOT_DIR=$SCRIPT_DIR/../../
|
OPENPILOT_DIR=$SCRIPT_DIR/../../
|
||||||
|
|
||||||
|
DOCKER_IMAGE=openpilot
|
||||||
|
DOCKER_FILE=Dockerfile.openpilot
|
||||||
|
DOCKER_REGISTRY=ghcr.io/commaai
|
||||||
|
COMMIT_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
if [ -n "$TARGET_ARCHITECTURE" ]; then
|
if [ -n "$TARGET_ARCHITECTURE" ]; then
|
||||||
PLATFORM="linux/$TARGET_ARCHITECTURE"
|
PLATFORM="linux/$TARGET_ARCHITECTURE"
|
||||||
TAG_SUFFIX="-$TARGET_ARCHITECTURE"
|
TAG_SUFFIX="-$TARGET_ARCHITECTURE"
|
||||||
@@ -15,9 +17,11 @@ else
|
|||||||
TAG_SUFFIX=""
|
TAG_SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX"
|
LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX
|
||||||
|
REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG
|
||||||
|
REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA
|
||||||
|
|
||||||
DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR
|
DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR
|
||||||
|
|
||||||
if [ -n "$PUSH_IMAGE" ]; then
|
if [ -n "$PUSH_IMAGE" ]; then
|
||||||
docker push $REMOTE_TAG
|
docker push $REMOTE_TAG
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ class FuzzyGenerator:
|
|||||||
except capnp.lib.capnp.KjException:
|
except capnp.lib.capnp.KjException:
|
||||||
return self.generate_struct(field.schema)
|
return self.generate_struct(field.schema)
|
||||||
|
|
||||||
def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]:
|
def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]:
|
||||||
single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else ()
|
single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else ()
|
||||||
fields_to_generate = schema.non_union_fields + single_fill
|
fields_to_generate = [f for f in schema.non_union_fields + single_fill if not f.endswith('DEPRECATED') and f != 'deprecated']
|
||||||
return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')})
|
return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@cache
|
@cache
|
||||||
|
|||||||
@@ -37,6 +37,43 @@ def release_only(f):
|
|||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
def collect_logs(services, duration):
|
||||||
|
socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services]
|
||||||
|
logs = []
|
||||||
|
start = time.monotonic()
|
||||||
|
while time.monotonic() - start < duration:
|
||||||
|
for s in socks:
|
||||||
|
logs.extend(messaging.drain_sock(s))
|
||||||
|
return logs
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def log_collector(services):
|
||||||
|
"""Background thread that continuously drains messages from services.
|
||||||
|
Use when the main thread needs to do blocking work (e.g. capturing images)."""
|
||||||
|
socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services]
|
||||||
|
raw_logs = []
|
||||||
|
lock = threading.Lock()
|
||||||
|
stop_event = threading.Event()
|
||||||
|
|
||||||
|
def _drain():
|
||||||
|
while not stop_event.is_set():
|
||||||
|
for s in socks:
|
||||||
|
msgs = messaging.drain_sock(s)
|
||||||
|
if msgs:
|
||||||
|
with lock:
|
||||||
|
raw_logs.extend(msgs)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=_drain, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
yield raw_logs, lock
|
||||||
|
finally:
|
||||||
|
stop_event.set()
|
||||||
|
thread.join(timeout=2)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def processes_context(processes, init_time=0, ignore_stopped=None):
|
def processes_context(processes, init_time=0, ignore_stopped=None):
|
||||||
ignore_stopped = [] if ignore_stopped is None else ignore_stopped
|
ignore_stopped = [] if ignore_stopped is None else ignore_stopped
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ class Maneuver:
|
|||||||
log['distance_lead'],
|
log['distance_lead'],
|
||||||
log['speed'],
|
log['speed'],
|
||||||
speed_lead,
|
speed_lead,
|
||||||
log['acceleration']]))
|
log['acceleration'],
|
||||||
|
log['d_rel']]))
|
||||||
|
|
||||||
if d_rel < .4 and (self.only_radar or prob_lead > 0.5):
|
if d_rel < .4 and (self.only_radar or prob_lead > 0.5):
|
||||||
print("Crashed!!!!")
|
print("Crashed!!!!")
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class Plant:
|
|||||||
position = log.XYZTData.new_message()
|
position = log.XYZTData.new_message()
|
||||||
position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)]
|
position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)]
|
||||||
model.modelV2.position = position
|
model.modelV2.position = position
|
||||||
|
model.modelV2.action.desiredAcceleration = float(self.acceleration + 0.1)
|
||||||
velocity = log.XYZTData.new_message()
|
velocity = log.XYZTData.new_message()
|
||||||
velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
|
velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
|
||||||
velocity.x[0] = float(self.speed) # always start at current speed
|
velocity.x[0] = float(self.speed) # always start at current speed
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import itertools
|
import itertools
|
||||||
from parameterized import parameterized_class
|
from openpilot.common.parameterized import parameterized_class
|
||||||
|
|
||||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
|
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
|
||||||
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Process replay is a regression test designed to identify any changes in the outp
|
|||||||
If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated.
|
If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated.
|
||||||
|
|
||||||
Use `test_processes.py` to run the test locally.
|
Use `test_processes.py` to run the test locally.
|
||||||
Use `FILEREADER_CACHE='1' test_processes.py` to cache log files.
|
Log files are cached by default. Use `DISABLE_FILEREADER_CACHE='1' test_processes.py` to disable caching.
|
||||||
|
|
||||||
Currently the following processes are tested:
|
Currently the following processes are tested:
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Currently the following processes are tested:
|
|||||||
### Usage
|
### Usage
|
||||||
```
|
```
|
||||||
Usage: test_processes.py [-h] [--whitelist-procs PROCS] [--whitelist-cars CARS] [--blacklist-procs PROCS]
|
Usage: test_processes.py [-h] [--whitelist-procs PROCS] [--whitelist-cars CARS] [--blacklist-procs PROCS]
|
||||||
[--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs] [--upload-only]
|
[--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs]
|
||||||
Regression test to identify changes in a process's output
|
Regression test to identify changes in a process's output
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@@ -33,12 +33,11 @@ optional arguments:
|
|||||||
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events)
|
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events)
|
||||||
--ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents)
|
--ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents)
|
||||||
--update-refs Updates reference logs using current commit
|
--update-refs Updates reference logs using current commit
|
||||||
--upload-only Skips testing processes and uploads logs from previous test run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Forks
|
## Forks
|
||||||
|
|
||||||
openpilot forks can use this test with their own reference logs, by default `test_proccess.py` saves logs locally.
|
openpilot forks can use this test with their own reference logs, by default `test_proccesses.py` saves logs locally.
|
||||||
|
|
||||||
To generate new logs:
|
To generate new logs:
|
||||||
|
|
||||||
@@ -48,13 +47,13 @@ Then, check in the new logs using git-lfs. Make sure to also update the `ref_com
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs.
|
Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, *args, **kwargs) -> List[capnp._DynamicStructReader]:
|
def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, *args, **kwargs) -> List[capnp._DynamicStructReader]:
|
||||||
|
|
||||||
def replay_process(
|
def replay_process(
|
||||||
cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None,
|
cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None,
|
||||||
fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, disable_progress: bool = False
|
fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, disable_progress: bool = False
|
||||||
) -> List[capnp._DynamicStructReader]:
|
) -> List[capnp._DynamicStructReader]:
|
||||||
```
|
```
|
||||||
@@ -73,14 +72,14 @@ output_logs = replay_process_with_name('locationd', lr)
|
|||||||
output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr)
|
output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr)
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported processes:
|
Supported processes:
|
||||||
* controlsd
|
* controlsd
|
||||||
* radard
|
* radard
|
||||||
* plannerd
|
* plannerd
|
||||||
* calibrationd
|
* calibrationd
|
||||||
* dmonitoringd
|
* dmonitoringd
|
||||||
* locationd
|
* locationd
|
||||||
* paramsd
|
* paramsd
|
||||||
* ubloxd
|
* ubloxd
|
||||||
* torqued
|
* torqued
|
||||||
* modeld
|
* modeld
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import sys
|
|||||||
import math
|
import math
|
||||||
import capnp
|
import capnp
|
||||||
import numbers
|
import numbers
|
||||||
import dictdiffer
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
from openpilot.tools.lib.logreader import LogReader
|
from openpilot.tools.lib.logreader import LogReader
|
||||||
|
|
||||||
EPSILON = sys.float_info.epsilon
|
EPSILON = sys.float_info.epsilon
|
||||||
|
|
||||||
|
_DynamicStructReader = capnp.lib.capnp._DynamicStructReader
|
||||||
|
_DynamicListReader = capnp.lib.capnp._DynamicListReader
|
||||||
|
_DynamicEnum = capnp.lib.capnp._DynamicEnum
|
||||||
|
|
||||||
|
|
||||||
def remove_ignored_fields(msg, ignore):
|
def remove_ignored_fields(msg, ignore):
|
||||||
msg = msg.as_builder()
|
msg = msg.as_builder()
|
||||||
@@ -39,6 +42,61 @@ def remove_ignored_fields(msg, ignore):
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def _diff_capnp(r1, r2, path, tolerance):
|
||||||
|
"""Walk two capnp struct readers and yield (action, dotted_path, value) diffs.
|
||||||
|
|
||||||
|
Floats are compared with the given tolerance (combined absolute+relative).
|
||||||
|
"""
|
||||||
|
schema = r1.schema
|
||||||
|
|
||||||
|
for fname in schema.non_union_fields:
|
||||||
|
child_path = path + (fname,)
|
||||||
|
v1 = getattr(r1, fname)
|
||||||
|
v2 = getattr(r2, fname)
|
||||||
|
yield from _diff_capnp_values(v1, v2, child_path, tolerance)
|
||||||
|
|
||||||
|
if schema.union_fields:
|
||||||
|
w1, w2 = r1.which(), r2.which()
|
||||||
|
if w1 != w2:
|
||||||
|
yield 'change', '.'.join(path), (w1, w2)
|
||||||
|
else:
|
||||||
|
child_path = path + (w1,)
|
||||||
|
v1, v2 = getattr(r1, w1), getattr(r2, w2)
|
||||||
|
yield from _diff_capnp_values(v1, v2, child_path, tolerance)
|
||||||
|
|
||||||
|
|
||||||
|
def _diff_capnp_values(v1, v2, path, tolerance):
|
||||||
|
if isinstance(v1, _DynamicStructReader):
|
||||||
|
yield from _diff_capnp(v1, v2, path, tolerance)
|
||||||
|
|
||||||
|
elif isinstance(v1, _DynamicListReader):
|
||||||
|
dot = '.'.join(path)
|
||||||
|
n1, n2 = len(v1), len(v2)
|
||||||
|
n = min(n1, n2)
|
||||||
|
for i in range(n):
|
||||||
|
yield from _diff_capnp_values(v1[i], v2[i], path + (str(i),), tolerance)
|
||||||
|
if n2 > n:
|
||||||
|
yield 'add', dot, list(enumerate(v2[n:], n))
|
||||||
|
if n1 > n:
|
||||||
|
yield 'remove', dot, list(reversed([(i, v1[i]) for i in range(n, n1)]))
|
||||||
|
|
||||||
|
elif isinstance(v1, _DynamicEnum):
|
||||||
|
s1, s2 = str(v1), str(v2)
|
||||||
|
if s1 != s2:
|
||||||
|
yield 'change', '.'.join(path), (s1, s2)
|
||||||
|
|
||||||
|
elif isinstance(v1, float):
|
||||||
|
if not (v1 == v2 or (
|
||||||
|
math.isfinite(v1) and math.isfinite(v2) and
|
||||||
|
abs(v1 - v2) <= max(tolerance, tolerance * max(abs(v1), abs(v2)))
|
||||||
|
)):
|
||||||
|
yield 'change', '.'.join(path), (v1, v2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if v1 != v2:
|
||||||
|
yield 'change', '.'.join(path), (v1, v2)
|
||||||
|
|
||||||
|
|
||||||
def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None,):
|
def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None,):
|
||||||
if ignore_fields is None:
|
if ignore_fields is None:
|
||||||
ignore_fields = []
|
ignore_fields = []
|
||||||
@@ -65,26 +123,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
|
|||||||
msg2 = remove_ignored_fields(msg2, ignore_fields)
|
msg2 = remove_ignored_fields(msg2, ignore_fields)
|
||||||
|
|
||||||
if msg1.to_bytes() != msg2.to_bytes():
|
if msg1.to_bytes() != msg2.to_bytes():
|
||||||
msg1_dict = msg1.as_reader().to_dict(verbose=True)
|
dd = list(_diff_capnp(msg1.as_reader(), msg2.as_reader(), (), tolerance))
|
||||||
msg2_dict = msg2.as_reader().to_dict(verbose=True)
|
|
||||||
|
|
||||||
dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields)
|
|
||||||
|
|
||||||
# Dictdiffer only supports relative tolerance, we also want to check for absolute
|
|
||||||
# TODO: add this to dictdiffer
|
|
||||||
def outside_tolerance(diff):
|
|
||||||
try:
|
|
||||||
if diff[0] == "change":
|
|
||||||
a, b = diff[2]
|
|
||||||
finite = math.isfinite(a) and math.isfinite(b)
|
|
||||||
if finite and isinstance(a, numbers.Number) and isinstance(b, numbers.Number):
|
|
||||||
return abs(a - b) > max(tolerance, tolerance * max(abs(a), abs(b)))
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
|
|
||||||
dd = list(filter(outside_tolerance, dd))
|
|
||||||
|
|
||||||
diff.extend(dd)
|
diff.extend(dd)
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
|
|||||||
92
selfdrive/test/process_replay/diff_report.py
Normal file
92
selfdrive/test/process_replay/diff_report.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import os
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from opendbc.car.tests.car_diff import format_diff, format_numeric_diffs
|
||||||
|
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs
|
||||||
|
from openpilot.selfdrive.test.process_replay.process_replay import PROC_REPLAY_DIR
|
||||||
|
|
||||||
|
|
||||||
|
class MsgWrap:
|
||||||
|
"""Adapter so to_dict() includes defaults"""
|
||||||
|
def __init__(self, msg):
|
||||||
|
self._msg = msg
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return self._msg.to_dict(verbose=True)
|
||||||
|
|
||||||
|
|
||||||
|
def diff_process(cfg, ref_msgs, new_msgs) -> tuple | None:
|
||||||
|
ref = defaultdict(list)
|
||||||
|
new = defaultdict(list)
|
||||||
|
for m in ref_msgs:
|
||||||
|
if m.which() in cfg.subs:
|
||||||
|
ref[m.which()].append(m)
|
||||||
|
for m in new_msgs:
|
||||||
|
if m.which() in cfg.subs:
|
||||||
|
new[m.which()].append(m)
|
||||||
|
|
||||||
|
diffs = []
|
||||||
|
for sub in cfg.subs:
|
||||||
|
if len(ref[sub]) != len(new[sub]):
|
||||||
|
diffs.append((f"{sub} (message count)", 0, (len(ref[sub]), len(new[sub])), 0))
|
||||||
|
for i, (r, n) in enumerate(zip(ref[sub], new[sub], strict=False)):
|
||||||
|
for d in compare_logs([r], [n], cfg.ignore, tolerance=cfg.tolerance):
|
||||||
|
if d[0] == "change":
|
||||||
|
a, b = d[2]
|
||||||
|
if a != a and b != b:
|
||||||
|
continue
|
||||||
|
diffs.append((d[1], i, d[2], r.logMonoTime))
|
||||||
|
elif d[0] in ("add", "remove"):
|
||||||
|
for item in d[2]:
|
||||||
|
if item[1] != item[1]:
|
||||||
|
continue
|
||||||
|
diffs.append((f"{d[1]}.{item[0]}", i, (d[0], item[1]), r.logMonoTime))
|
||||||
|
return (diffs, ref, new) if diffs else None
|
||||||
|
|
||||||
|
|
||||||
|
def diff_format(diffs, ref, new, field) -> list[str]:
|
||||||
|
if any(part.isdigit() for part in field.split(".")):
|
||||||
|
return format_numeric_diffs(diffs)
|
||||||
|
msg_type = field.split(".")[0]
|
||||||
|
ref_ts = [(m.logMonoTime, MsgWrap(m)) for m in ref.get(msg_type, [])]
|
||||||
|
new_wrapped = [MsgWrap(m) for m in new.get(msg_type, [])]
|
||||||
|
return format_diff(diffs, ref_ts, new_wrapped, field)
|
||||||
|
|
||||||
|
|
||||||
|
def diff_report(replay_diffs, segments) -> None:
|
||||||
|
seg_to_plat = {seg: plat for plat, seg in segments}
|
||||||
|
|
||||||
|
with_diffs, errors, n_passed = [], [], 0
|
||||||
|
for seg, proc, data in replay_diffs:
|
||||||
|
plat = seg_to_plat.get(seg, "UNKNOWN")
|
||||||
|
if data is None:
|
||||||
|
n_passed += 1
|
||||||
|
elif isinstance(data, str):
|
||||||
|
errors.append((plat, seg, proc, data))
|
||||||
|
else:
|
||||||
|
with_diffs.append((plat, seg, proc, data))
|
||||||
|
|
||||||
|
icon = "⚠️" if with_diffs else "✅"
|
||||||
|
lines = [
|
||||||
|
"## Process replay diff report",
|
||||||
|
"Replays driving segments through this PR and compares the behavior to master.",
|
||||||
|
"Please review any changes carefully to ensure they are expected.\n",
|
||||||
|
f"{icon} {len(with_diffs)} changed, {n_passed} passed, {len(errors)} errors",
|
||||||
|
]
|
||||||
|
|
||||||
|
for plat, seg, proc, err in errors:
|
||||||
|
lines.append(f"\nERROR {plat} - {seg} [{proc}]: {err}")
|
||||||
|
|
||||||
|
if with_diffs:
|
||||||
|
lines.append("<details><summary><b>Show changes</b></summary>\n\n```")
|
||||||
|
for plat, seg, proc, (diffs, ref, new) in with_diffs:
|
||||||
|
lines.append(f"\n{plat} - {seg} [{proc}]")
|
||||||
|
by_field = defaultdict(list)
|
||||||
|
for d in diffs:
|
||||||
|
by_field[d[0]].append(d)
|
||||||
|
for field, fd in sorted(by_field.items()):
|
||||||
|
lines.append(f"\n {field} ({len(fd)} diffs)")
|
||||||
|
lines.extend(diff_format(fd, ref, new, field))
|
||||||
|
lines.append("```\n</details>")
|
||||||
|
|
||||||
|
with open(os.path.join(PROC_REPLAY_DIR, "diff_report.txt"), "w") as f:
|
||||||
|
f.write("\n".join(lines))
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from typing import cast
|
||||||
import capnp
|
import capnp
|
||||||
import functools
|
import functools
|
||||||
import traceback
|
import traceback
|
||||||
@@ -9,10 +10,11 @@ from opendbc.car.fingerprints import MIGRATION
|
|||||||
from opendbc.car.toyota.values import EPS_SCALE, ToyotaSafetyFlags
|
from opendbc.car.toyota.values import EPS_SCALE, ToyotaSafetyFlags
|
||||||
from opendbc.car.ford.values import CAR as FORD, FordFlags, FordSafetyFlags
|
from opendbc.car.ford.values import CAR as FORD, FordFlags, FordSafetyFlags
|
||||||
from opendbc.car.hyundai.values import HyundaiSafetyFlags
|
from opendbc.car.hyundai.values import HyundaiSafetyFlags
|
||||||
|
from opendbc.car.gm.values import GMSafetyFlags
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta
|
from openpilot.selfdrive.modeld.fill_model_msg import fill_xyz_poly, fill_lane_line_meta
|
||||||
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index
|
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_encode_index
|
||||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_from_plan
|
from openpilot.selfdrive.controls.lib.longitudinal_planner import get_accel_from_plan, CONTROL_N_T_IDX
|
||||||
from openpilot.system.manager.process_config import managed_processes
|
from openpilot.system.manager.process_config import managed_processes
|
||||||
from openpilot.tools.lib.logreader import LogIterable
|
from openpilot.tools.lib.logreader import LogIterable
|
||||||
|
|
||||||
@@ -21,12 +23,12 @@ MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], lis
|
|||||||
MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
|
MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps]
|
||||||
|
|
||||||
|
|
||||||
## rules for migration functions
|
# rules for migration functions
|
||||||
## 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
|
# 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature
|
||||||
## 2. it only gets the messages that are in the inputs list
|
# 2. it only gets the messages that are in the inputs list
|
||||||
## 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr
|
||||||
## 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
|
# 4. it must return a list of operations to be applied to the logreader (replace, add, delete)
|
||||||
## 5. all migration functions must be independent of each other
|
# 5. all migration functions must be independent of each other
|
||||||
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
|
def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False):
|
||||||
migrations = [
|
migrations = [
|
||||||
migrate_sensorEvents,
|
migrate_sensorEvents,
|
||||||
@@ -37,6 +39,7 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo
|
|||||||
migrate_controlsState,
|
migrate_controlsState,
|
||||||
migrate_carState,
|
migrate_carState,
|
||||||
migrate_liveLocationKalman,
|
migrate_liveLocationKalman,
|
||||||
|
migrate_livePose,
|
||||||
migrate_liveTracks,
|
migrate_liveTracks,
|
||||||
migrate_driverAssistance,
|
migrate_driverAssistance,
|
||||||
migrate_drivingModelData,
|
migrate_drivingModelData,
|
||||||
@@ -66,7 +69,7 @@ def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]):
|
|||||||
if migration.product in grouped: # skip if product already exists
|
if migration.product in grouped: # skip if product already exists
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i])
|
sorted_indices = sorted(ii for i in cast(list[str], migration.inputs) for ii in grouped.get(i, []))
|
||||||
msg_gen = [(i, lr[i]) for i in sorted_indices]
|
msg_gen = [(i, lr[i]) for i in sorted_indices]
|
||||||
r_ops, a_ops, d_ops = migration(msg_gen)
|
r_ops, a_ops, d_ops = migration(msg_gen)
|
||||||
replace_ops.extend(r_ops)
|
replace_ops.extend(r_ops)
|
||||||
@@ -95,6 +98,17 @@ def migration(inputs: list[str], product: str|None=None):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_onroad_event(event: capnp.lib.capnp._DynamicStructReader):
|
||||||
|
event_dict = event.to_dict()
|
||||||
|
try:
|
||||||
|
return log.OnroadEvent(**event_dict)
|
||||||
|
except capnp.lib.capnp.KjException as e:
|
||||||
|
# Ignore legacy events the current schema no longer defines.
|
||||||
|
if "enum has no such enumerant" in str(e):
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
@migration(inputs=["longitudinalPlan", "carParams"])
|
@migration(inputs=["longitudinalPlan", "carParams"])
|
||||||
def migrate_longitudinalPlan(msgs):
|
def migrate_longitudinalPlan(msgs):
|
||||||
ops = []
|
ops = []
|
||||||
@@ -108,7 +122,7 @@ def migrate_longitudinalPlan(msgs):
|
|||||||
if msg.which() != 'longitudinalPlan':
|
if msg.which() != 'longitudinalPlan':
|
||||||
continue
|
continue
|
||||||
new_msg = msg.as_builder()
|
new_msg = msg.as_builder()
|
||||||
a_target, should_stop = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels)
|
a_target, should_stop = get_accel_from_plan(msg.longitudinalPlan.speeds, msg.longitudinalPlan.accels, CONTROL_N_T_IDX)
|
||||||
new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = float(a_target), bool(should_stop)
|
new_msg.longitudinalPlan.aTarget, new_msg.longitudinalPlan.shouldStop = float(a_target), bool(should_stop)
|
||||||
ops.append((index, new_msg.as_reader()))
|
ops.append((index, new_msg.as_reader()))
|
||||||
return ops, [], []
|
return ops, [], []
|
||||||
@@ -173,6 +187,7 @@ def migrate_liveLocationKalman(msgs):
|
|||||||
m = messaging.new_message('livePose')
|
m = messaging.new_message('livePose')
|
||||||
m.valid = msg.valid
|
m.valid = msg.valid
|
||||||
m.logMonoTime = msg.logMonoTime
|
m.logMonoTime = msg.logMonoTime
|
||||||
|
m.livePose.timestamp = msg.logMonoTime
|
||||||
for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]:
|
for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]:
|
||||||
lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field)
|
lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field)
|
||||||
lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans
|
lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans
|
||||||
@@ -184,6 +199,21 @@ def migrate_liveLocationKalman(msgs):
|
|||||||
return ops, [], []
|
return ops, [], []
|
||||||
|
|
||||||
|
|
||||||
|
@migration(inputs=["livePose"])
|
||||||
|
def migrate_livePose(msgs):
|
||||||
|
ops = []
|
||||||
|
needs_migration = all(msg.livePose.timestamp == 0 for _, msg in msgs if msg.which() == 'livePose')
|
||||||
|
if not needs_migration:
|
||||||
|
return [], [], []
|
||||||
|
|
||||||
|
for index, msg in msgs:
|
||||||
|
if msg.which() == "livePose":
|
||||||
|
new_msg = msg.as_builder()
|
||||||
|
new_msg.livePose.timestamp = msg.logMonoTime
|
||||||
|
ops.append((index, new_msg.as_reader()))
|
||||||
|
return ops, [], []
|
||||||
|
|
||||||
|
|
||||||
@migration(inputs=["controlsState"], product="selfdriveState")
|
@migration(inputs=["controlsState"], product="selfdriveState")
|
||||||
def migrate_controlsState(msgs):
|
def migrate_controlsState(msgs):
|
||||||
add_ops = []
|
add_ops = []
|
||||||
@@ -195,7 +225,7 @@ def migrate_controlsState(msgs):
|
|||||||
for field in ("enabled", "active", "state", "engageable", "alertText1", "alertText2",
|
for field in ("enabled", "active", "state", "engageable", "alertText1", "alertText2",
|
||||||
"alertStatus", "alertSize", "alertType", "experimentalMode",
|
"alertStatus", "alertSize", "alertType", "experimentalMode",
|
||||||
"personality"):
|
"personality"):
|
||||||
setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED"))
|
setattr(ss, field, getattr(msg.controlsState.deprecated, field))
|
||||||
add_ops.append(m.as_reader())
|
add_ops.append(m.as_reader())
|
||||||
return [], add_ops, []
|
return [], add_ops, []
|
||||||
|
|
||||||
@@ -208,10 +238,10 @@ def migrate_carState(msgs):
|
|||||||
if msg.which() == 'controlsState':
|
if msg.which() == 'controlsState':
|
||||||
last_cs = msg
|
last_cs = msg
|
||||||
elif msg.which() == 'carState' and last_cs is not None:
|
elif msg.which() == 'carState' and last_cs is not None:
|
||||||
if last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruise > 0.1:
|
if last_cs.controlsState.deprecated.vCruise - msg.carState.vCruise > 0.1:
|
||||||
msg = msg.as_builder()
|
msg = msg.as_builder()
|
||||||
msg.carState.vCruise = last_cs.controlsState.vCruiseDEPRECATED
|
msg.carState.vCruise = last_cs.controlsState.deprecated.vCruise
|
||||||
msg.carState.vCruiseCluster = last_cs.controlsState.vCruiseClusterDEPRECATED
|
msg.carState.vCruiseCluster = last_cs.controlsState.deprecated.vCruiseCluster
|
||||||
ops.append((index, msg.as_reader()))
|
ops.append((index, msg.as_reader()))
|
||||||
return ops, [], []
|
return ops, [], []
|
||||||
|
|
||||||
@@ -241,14 +271,16 @@ def migrate_gpsLocation(msgs):
|
|||||||
|
|
||||||
@migration(inputs=["deviceState", "initData"])
|
@migration(inputs=["deviceState", "initData"])
|
||||||
def migrate_deviceState(msgs):
|
def migrate_deviceState(msgs):
|
||||||
|
init_data = next((m.initData for _, m in msgs if m.which() == 'initData'), None)
|
||||||
|
device_state = next((m.deviceState for _, m in msgs if m.which() == 'deviceState'), None)
|
||||||
|
if init_data is None or device_state is None:
|
||||||
|
return [], [], []
|
||||||
|
|
||||||
ops = []
|
ops = []
|
||||||
dt = None
|
|
||||||
for i, msg in msgs:
|
for i, msg in msgs:
|
||||||
if msg.which() == 'initData':
|
|
||||||
dt = msg.initData.deviceType
|
|
||||||
if msg.which() == 'deviceState':
|
if msg.which() == 'deviceState':
|
||||||
n = msg.as_builder()
|
n = msg.as_builder()
|
||||||
n.deviceState.deviceType = dt
|
n.deviceState.deviceType = init_data.deviceType
|
||||||
ops.append((i, n.as_reader()))
|
ops.append((i, n.as_reader()))
|
||||||
return ops, [], []
|
return ops, [], []
|
||||||
|
|
||||||
@@ -271,10 +303,12 @@ def migrate_pandaStates(msgs):
|
|||||||
safety_param_migration = {
|
safety_param_migration = {
|
||||||
"TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | ToyotaSafetyFlags.STOCK_LONGITUDINAL,
|
"TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | ToyotaSafetyFlags.STOCK_LONGITUDINAL,
|
||||||
"TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | ToyotaSafetyFlags.ALT_BRAKE,
|
"TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | ToyotaSafetyFlags.ALT_BRAKE,
|
||||||
"KIA_EV6": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEERING,
|
"KIA_EV6": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEER_MSG,
|
||||||
|
"CHEVROLET_VOLT": GMSafetyFlags.EV,
|
||||||
|
"CHEVROLET_BOLT_EUV": GMSafetyFlags.EV | GMSafetyFlags.HW_CAM,
|
||||||
}
|
}
|
||||||
# TODO: get new Ford route
|
# TODO: get new Ford route
|
||||||
safety_param_migration |= {car: FordSafetyFlags.LONG_CONTROL for car in (set(FORD) - FORD.with_flags(FordFlags.CANFD))}
|
safety_param_migration |= dict.fromkeys((set(FORD) - FORD.with_flags(FordFlags.CANFD)), FordSafetyFlags.LONG_CONTROL)
|
||||||
|
|
||||||
# Migrate safety param base on carParams
|
# Migrate safety param base on carParams
|
||||||
CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
|
CP = next((m.carParams for _, m in msgs if m.which() == 'carParams'), None)
|
||||||
@@ -301,6 +335,8 @@ def migrate_pandaStates(msgs):
|
|||||||
elif msg.which() == 'pandaStates':
|
elif msg.which() == 'pandaStates':
|
||||||
new_msg = msg.as_builder()
|
new_msg = msg.as_builder()
|
||||||
new_msg.pandaStates[-1].safetyParam = safety_param
|
new_msg.pandaStates[-1].safetyParam = safety_param
|
||||||
|
# Clear DISABLE_DISENGAGE_ON_GAS bit to fix controls mismatch
|
||||||
|
new_msg.pandaStates[-1].alternativeExperience &= ~1
|
||||||
ops.append((index, new_msg.as_reader()))
|
ops.append((index, new_msg.as_reader()))
|
||||||
return ops, [], []
|
return ops, [], []
|
||||||
|
|
||||||
@@ -431,12 +467,13 @@ def migrate_onroadEvents(msgs):
|
|||||||
for event in msg.onroadEventsDEPRECATED:
|
for event in msg.onroadEventsDEPRECATED:
|
||||||
try:
|
try:
|
||||||
if not str(event.name).endswith('DEPRECATED'):
|
if not str(event.name).endswith('DEPRECATED'):
|
||||||
# dict converts name enum into string representation
|
migrated_event = migrate_onroad_event(event)
|
||||||
onroadEvents.append(log.OnroadEvent(**event.to_dict()))
|
if migrated_event is not None:
|
||||||
|
onroadEvents.append(migrated_event)
|
||||||
except RuntimeError: # Member was null
|
except RuntimeError: # Member was null
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
new_msg = messaging.new_message('onroadEvents', len(msg.onroadEventsDEPRECATED))
|
new_msg = messaging.new_message('onroadEvents', len(onroadEvents))
|
||||||
new_msg.valid = msg.valid
|
new_msg.valid = msg.valid
|
||||||
new_msg.logMonoTime = msg.logMonoTime
|
new_msg.logMonoTime = msg.logMonoTime
|
||||||
new_msg.onroadEvents = onroadEvents
|
new_msg.onroadEvents = onroadEvents
|
||||||
@@ -451,11 +488,12 @@ def migrate_driverMonitoringState(msgs):
|
|||||||
for index, msg in msgs:
|
for index, msg in msgs:
|
||||||
msg = msg.as_builder()
|
msg = msg.as_builder()
|
||||||
events = []
|
events = []
|
||||||
for event in msg.driverMonitoringState.eventsDEPRECATED:
|
for event in msg.driverMonitoringState.deprecated.events:
|
||||||
try:
|
try:
|
||||||
if not str(event.name).endswith('DEPRECATED'):
|
if not str(event.name).endswith('DEPRECATED'):
|
||||||
# dict converts name enum into string representation
|
migrated_event = migrate_onroad_event(event)
|
||||||
events.append(log.OnroadEvent(**event.to_dict()))
|
if migrated_event is not None:
|
||||||
|
events.append(migrated_event)
|
||||||
except RuntimeError: # Member was null
|
except RuntimeError: # Member was null
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
|
import pickle
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -8,22 +9,22 @@ from itertools import zip_longest
|
|||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from tabulate import tabulate
|
from openpilot.common.utils import tabulate
|
||||||
|
|
||||||
from openpilot.common.git import get_commit
|
from openpilot.common.git import get_commit
|
||||||
from openpilot.system.hardware import PC
|
from openpilot.system.hardware import PC
|
||||||
from openpilot.tools.lib.openpilotci import get_url
|
from openpilot.tools.lib.openpilotci import get_url
|
||||||
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
||||||
from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
|
from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process
|
||||||
from openpilot.tools.lib.framereader import FrameReader, NumpyFrameReader
|
from openpilot.tools.lib.framereader import FrameReader
|
||||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||||
from openpilot.tools.lib.github_utils import GithubUtils
|
from openpilot.tools.lib.github_utils import GithubUtils
|
||||||
|
|
||||||
TEST_ROUTE = "8494c69d3c710e81|000001d4--2648a9a404"
|
TEST_ROUTE = "8494c69d3c710e81|000001d4--2648a9a404"
|
||||||
SEGMENT = 4
|
SEGMENT = 4
|
||||||
MAX_FRAMES = 100 if PC else 400
|
START_FRAME = 0
|
||||||
|
END_FRAME = 60
|
||||||
|
|
||||||
NO_MODEL = "NO_MODEL" in os.environ
|
|
||||||
SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0")))
|
SEND_EXTRA_INPUTS = bool(int(os.getenv("SEND_EXTRA_INPUTS", "0")))
|
||||||
|
|
||||||
DATA_TOKEN = os.getenv("CI_ARTIFACTS_TOKEN","")
|
DATA_TOKEN = os.getenv("CI_ARTIFACTS_TOKEN","")
|
||||||
@@ -33,8 +34,8 @@ GITHUB = GithubUtils(API_TOKEN, DATA_TOKEN)
|
|||||||
|
|
||||||
EXEC_TIMINGS = [
|
EXEC_TIMINGS = [
|
||||||
# model, instant max, average max
|
# model, instant max, average max
|
||||||
("modelV2", 0.035, 0.025),
|
("modelV2", 0.05, 0.028),
|
||||||
("driverStateV2", 0.02, 0.015),
|
("driverStateV2", 0.05, 0.016),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_log_fn(test_route, ref="master"):
|
def get_log_fn(test_route, ref="master"):
|
||||||
@@ -64,6 +65,7 @@ def generate_report(proposed, master, tmp, commit):
|
|||||||
ModelV2_Plots = zl([
|
ModelV2_Plots = zl([
|
||||||
(lambda x: get_idx_if_non_empty(x.velocity.x, 0), "velocity.x"),
|
(lambda x: get_idx_if_non_empty(x.velocity.x, 0), "velocity.x"),
|
||||||
(lambda x: get_idx_if_non_empty(x.action.desiredCurvature), "desiredCurvature"),
|
(lambda x: get_idx_if_non_empty(x.action.desiredCurvature), "desiredCurvature"),
|
||||||
|
(lambda x: get_idx_if_non_empty(x.action.desiredAcceleration), "desiredAcceleration"),
|
||||||
(lambda x: get_idx_if_non_empty(x.leadsV3[0].x, 0), "leadsV3.x"),
|
(lambda x: get_idx_if_non_empty(x.leadsV3[0].x, 0), "leadsV3.x"),
|
||||||
(lambda x: get_idx_if_non_empty(x.laneLines[1].y, 0), "laneLines.y"),
|
(lambda x: get_idx_if_non_empty(x.laneLines[1].y, 0), "laneLines.y"),
|
||||||
(lambda x: get_idx_if_non_empty(x.meta.desireState, 3), "desireState.laneChangeLeft"),
|
(lambda x: get_idx_if_non_empty(x.meta.desireState, 3), "desireState.laneChangeLeft"),
|
||||||
@@ -74,8 +76,8 @@ def generate_report(proposed, master, tmp, commit):
|
|||||||
(lambda x: get_idx_if_non_empty(x.wheelOnRightProb), "wheelOnRightProb"),
|
(lambda x: get_idx_if_non_empty(x.wheelOnRightProb), "wheelOnRightProb"),
|
||||||
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceProb), "leftDriverData.faceProb"),
|
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceProb), "leftDriverData.faceProb"),
|
||||||
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceOrientation, 0), "leftDriverData.faceOrientation0"),
|
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceOrientation, 0), "leftDriverData.faceOrientation0"),
|
||||||
(lambda x: get_idx_if_non_empty(x.leftDriverData.leftBlinkProb), "leftDriverData.leftBlinkProb"),
|
(lambda x: get_idx_if_non_empty(x.leftDriverData.eyesClosedProb), "leftDriverData.eyesClosedProb"),
|
||||||
(lambda x: get_idx_if_non_empty(x.leftDriverData.notReadyProb, 0), "leftDriverData.notReadyProb0"),
|
(lambda x: get_idx_if_non_empty(x.leftDriverData.phoneProb), "leftDriverData.phoneProb"),
|
||||||
(lambda x: get_idx_if_non_empty(x.rightDriverData.faceProb), "rightDriverData.faceProb"),
|
(lambda x: get_idx_if_non_empty(x.rightDriverData.faceProb), "rightDriverData.faceProb"),
|
||||||
], "driverStateV2")
|
], "driverStateV2")
|
||||||
|
|
||||||
@@ -122,18 +124,17 @@ def comment_replay_report(proposed, master, full_logs):
|
|||||||
diff_plots = create_table("Model Replay Differences", diff_files, link, open_table=True)
|
diff_plots = create_table("Model Replay Differences", diff_files, link, open_table=True)
|
||||||
all_plots = create_table("All Model Replay Plots", files, link)
|
all_plots = create_table("All Model Replay Plots", files, link)
|
||||||
comment = f"ref for commit {commit}: {link}/{log_name}" + diff_plots + all_plots
|
comment = f"ref for commit {commit}: {link}/{log_name}" + diff_plots + all_plots
|
||||||
GITHUB.comment_on_pr(comment, PR_BRANCH)
|
GITHUB.comment_on_pr(comment, PR_BRANCH, "commaci-public", True)
|
||||||
|
|
||||||
def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types):
|
def trim_logs(logs, start_frame, end_frame, frs_types, include_all_types):
|
||||||
all_msgs = []
|
all_msgs = []
|
||||||
cam_state_counts = defaultdict(int)
|
cam_state_counts = defaultdict(int)
|
||||||
# keep adding messages until cam states are equal to MAX_FRAMES
|
|
||||||
for msg in sorted(logs, key=lambda m: m.logMonoTime):
|
for msg in sorted(logs, key=lambda m: m.logMonoTime):
|
||||||
all_msgs.append(msg)
|
|
||||||
if msg.which() in frs_types:
|
if msg.which() in frs_types:
|
||||||
cam_state_counts[msg.which()] += 1
|
cam_state_counts[msg.which()] += 1
|
||||||
|
if any(cam_state_counts[state] >= start_frame for state in frs_types):
|
||||||
if all(cam_state_counts[state] == max_frames for state in frs_types):
|
all_msgs.append(msg)
|
||||||
|
if all(cam_state_counts[state] == end_frame for state in frs_types):
|
||||||
break
|
break
|
||||||
|
|
||||||
if len(include_all_types) != 0:
|
if len(include_all_types) != 0:
|
||||||
@@ -145,9 +146,9 @@ def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types):
|
|||||||
|
|
||||||
def model_replay(lr, frs):
|
def model_replay(lr, frs):
|
||||||
# modeld is using frame pairs
|
# modeld is using frame pairs
|
||||||
modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"},
|
modeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"roadCameraState", "wideRoadCameraState"},
|
||||||
{"roadEncodeIdx", "wideRoadEncodeIdx", "carParams", "carState", "carControl"})
|
{"roadEncodeIdx", "wideRoadEncodeIdx", "carParams", "carState", "carControl", "can"})
|
||||||
dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx", "carParams"})
|
dmodeld_logs = trim_logs(lr, START_FRAME, END_FRAME, {"driverCameraState"}, {"driverEncodeIdx", "carParams", "can"})
|
||||||
|
|
||||||
if not SEND_EXTRA_INPUTS:
|
if not SEND_EXTRA_INPUTS:
|
||||||
modeld_logs = [msg for msg in modeld_logs if msg.which() != 'liveCalibration']
|
modeld_logs = [msg for msg in modeld_logs if msg.which() != 'liveCalibration']
|
||||||
@@ -164,9 +165,6 @@ def model_replay(lr, frs):
|
|||||||
dmonitoringmodeld = get_process_config("dmonitoringmodeld")
|
dmonitoringmodeld = get_process_config("dmonitoringmodeld")
|
||||||
|
|
||||||
modeld_msgs = replay_process(modeld, modeld_logs, frs)
|
modeld_msgs = replay_process(modeld, modeld_logs, frs)
|
||||||
if isinstance(frs['roadCameraState'], NumpyFrameReader):
|
|
||||||
del frs['roadCameraState'].frames
|
|
||||||
del frs['wideRoadCameraState'].frames
|
|
||||||
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
|
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
|
||||||
|
|
||||||
msgs = modeld_msgs + dmonitoringmodeld_msgs
|
msgs = modeld_msgs + dmonitoringmodeld_msgs
|
||||||
@@ -192,34 +190,36 @@ def model_replay(lr, frs):
|
|||||||
print("----------------- Model Timing -----------------")
|
print("----------------- Model Timing -----------------")
|
||||||
print("------------------------------------------------")
|
print("------------------------------------------------")
|
||||||
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".4f"))
|
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".4f"))
|
||||||
assert timings_ok
|
assert timings_ok or PC
|
||||||
|
|
||||||
return msgs
|
return msgs
|
||||||
|
|
||||||
|
|
||||||
def get_frames():
|
def get_frames():
|
||||||
regen_cache = "--regen-cache" in sys.argv
|
regen_cache = "--regen-cache" in sys.argv
|
||||||
cache = "--cache" in sys.argv or not PC or regen_cache
|
frames_cache = '/tmp/model_replay_cache' if PC else '/data/model_replay_cache'
|
||||||
videos = ('fcamera.hevc', 'dcamera.hevc', 'ecamera.hevc')
|
os.makedirs(frames_cache, exist_ok=True)
|
||||||
cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
|
|
||||||
|
|
||||||
if cache:
|
cache_name = f'{frames_cache}/{TEST_ROUTE}_{SEGMENT}_{START_FRAME}_{END_FRAME}.pkl'
|
||||||
frames_cache = '/tmp/model_replay_cache' if PC else '/data/model_replay_cache'
|
if os.path.isfile(cache_name) and not regen_cache:
|
||||||
os.makedirs(frames_cache, exist_ok=True)
|
try:
|
||||||
|
print(f"Loading frames from cache {cache_name}")
|
||||||
cache_size = 200
|
return pickle.load(open(cache_name, "rb"))
|
||||||
for v in videos:
|
except Exception as e:
|
||||||
if not all(os.path.isfile(f'{frames_cache}/{TEST_ROUTE}_{v}_{i}.npy') for i in range(MAX_FRAMES//cache_size)) or regen_cache:
|
print(f"Failed to load frames from cache {cache_name}: {e}")
|
||||||
f = FrameReader(get_url(TEST_ROUTE, SEGMENT, v)).get(0, MAX_FRAMES + 1, pix_fmt="nv12")
|
|
||||||
print(f'Caching {v}...')
|
|
||||||
for i in range(MAX_FRAMES//cache_size):
|
|
||||||
np.save(f'{frames_cache}/{TEST_ROUTE}_{v}_{i}', f[(i * cache_size) + 1:((i + 1) * cache_size) + 1])
|
|
||||||
del f
|
|
||||||
|
|
||||||
return {c : NumpyFrameReader(f"{frames_cache}/{TEST_ROUTE}_{v}", 1928, 1208, cache_size) for c,v in zip(cams, videos, strict=True)}
|
|
||||||
else:
|
|
||||||
return {c : FrameReader(get_url(TEST_ROUTE, SEGMENT, v), readahead=True) for c,v in zip(cams, videos, strict=True)}
|
|
||||||
|
|
||||||
|
frs = {
|
||||||
|
'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
|
||||||
|
'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
|
||||||
|
'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
|
||||||
|
}
|
||||||
|
for fr in frs.values():
|
||||||
|
for fidx in range(START_FRAME, END_FRAME):
|
||||||
|
fr.get(fidx)
|
||||||
|
fr.it = None
|
||||||
|
print(f"Dumping frame cache {cache_name}")
|
||||||
|
pickle.dump(frs, open(cache_name, "wb"))
|
||||||
|
return frs
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
|
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
|
||||||
@@ -231,8 +231,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
log_msgs = []
|
log_msgs = []
|
||||||
# run replays
|
# run replays
|
||||||
if not NO_MODEL:
|
log_msgs += model_replay(lr, frs)
|
||||||
log_msgs += model_replay(lr, frs)
|
|
||||||
|
|
||||||
# get diff
|
# get diff
|
||||||
failed = False
|
failed = False
|
||||||
@@ -241,13 +240,10 @@ if __name__ == "__main__":
|
|||||||
try:
|
try:
|
||||||
all_logs = list(LogReader(GITHUB.get_file_url(MODEL_REPLAY_BUCKET, log_fn)))
|
all_logs = list(LogReader(GITHUB.get_file_url(MODEL_REPLAY_BUCKET, log_fn)))
|
||||||
cmp_log = []
|
cmp_log = []
|
||||||
|
model_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ("modelV2", "drivingModelData", "cameraOdometry"))
|
||||||
# logs are ordered based on type: modelV2, drivingModelData, driverStateV2
|
cmp_log += all_logs[model_start_index+START_FRAME*3:model_start_index + END_FRAME*3]
|
||||||
if not NO_MODEL:
|
dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2")
|
||||||
model_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ("modelV2", "drivingModelData", "cameraOdometry"))
|
cmp_log += all_logs[dmon_start_index+START_FRAME:dmon_start_index + END_FRAME]
|
||||||
cmp_log += all_logs[model_start_index:model_start_index + MAX_FRAMES*3]
|
|
||||||
dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2")
|
|
||||||
cmp_log += all_logs[dmon_start_index:dmon_start_index + MAX_FRAMES]
|
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
'logMonoTime',
|
'logMonoTime',
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import heapq
|
import heapq
|
||||||
import signal
|
import signal
|
||||||
from collections import Counter, OrderedDict
|
import numpy as np
|
||||||
|
from collections import Counter
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from itertools import islice
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
@@ -17,37 +18,25 @@ import cereal.messaging as messaging
|
|||||||
from cereal import car
|
from cereal import car
|
||||||
from cereal.services import SERVICE_LIST
|
from cereal.services import SERVICE_LIST
|
||||||
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
|
from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name
|
||||||
|
from opendbc.car.can_definitions import CanData
|
||||||
from opendbc.car.car_helpers import get_car, interfaces
|
from opendbc.car.car_helpers import get_car, interfaces
|
||||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.prefix import OpenpilotPrefix
|
from openpilot.common.prefix import OpenpilotPrefix
|
||||||
from openpilot.common.timeout import Timeout
|
from openpilot.common.timeout import Timeout
|
||||||
from openpilot.common.realtime import DT_CTRL
|
from openpilot.common.realtime import DT_CTRL
|
||||||
from openpilot.selfdrive.car.card import can_comm_callbacks
|
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||||
from openpilot.system.manager.process_config import managed_processes
|
from openpilot.system.manager.process_config import managed_processes
|
||||||
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams
|
from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams
|
||||||
from openpilot.selfdrive.test.process_replay.migration import migrate_all
|
from openpilot.selfdrive.test.process_replay.migration import migrate_all
|
||||||
from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture
|
from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture
|
||||||
from openpilot.tools.lib.logreader import LogIterable
|
from openpilot.tools.lib.logreader import LogIterable
|
||||||
from openpilot.tools.lib.framereader import BaseFrameReader
|
from openpilot.tools.lib.framereader import FrameReader
|
||||||
|
|
||||||
# Numpy gives different results based on CPU features after version 19
|
# Numpy gives different results based on CPU features after version 19
|
||||||
NUMPY_TOLERANCE = 1e-7
|
NUMPY_TOLERANCE = 1e-2
|
||||||
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
|
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
|
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
|
||||||
|
|
||||||
class DummySocket:
|
|
||||||
def __init__(self):
|
|
||||||
self.data: list[bytes] = []
|
|
||||||
|
|
||||||
def receive(self, non_blocking: bool = False) -> bytes | None:
|
|
||||||
if non_blocking:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.data.pop()
|
|
||||||
|
|
||||||
def send(self, data: bytes):
|
|
||||||
self.data.append(data)
|
|
||||||
|
|
||||||
class LauncherWithCapture:
|
class LauncherWithCapture:
|
||||||
def __init__(self, capture: ProcessOutputCapture, launcher: Callable):
|
def __init__(self, capture: ProcessOutputCapture, launcher: Callable):
|
||||||
@@ -65,8 +54,7 @@ class ReplayContext:
|
|||||||
self.pubs = cfg.pubs
|
self.pubs = cfg.pubs
|
||||||
self.main_pub = cfg.main_pub
|
self.main_pub = cfg.main_pub
|
||||||
self.main_pub_drained = cfg.main_pub_drained
|
self.main_pub_drained = cfg.main_pub_drained
|
||||||
self.unlocked_pubs = cfg.unlocked_pubs
|
assert len(self.pubs) != 0 or self.main_pub is not None
|
||||||
assert(len(self.pubs) != 0 or self.main_pub is not None)
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.open_context()
|
self.open_context()
|
||||||
@@ -81,9 +69,8 @@ class ReplayContext:
|
|||||||
messaging.set_fake_prefix(self.proc_name)
|
messaging.set_fake_prefix(self.proc_name)
|
||||||
|
|
||||||
if self.main_pub is None:
|
if self.main_pub is None:
|
||||||
self.events = OrderedDict()
|
self.events = {}
|
||||||
pubs_with_events = [pub for pub in self.pubs if pub not in self.unlocked_pubs]
|
for pub in self.pubs:
|
||||||
for pub in pubs_with_events:
|
|
||||||
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
|
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
|
||||||
else:
|
else:
|
||||||
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
|
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
|
||||||
@@ -140,19 +127,25 @@ class ProcessConfig:
|
|||||||
processing_time: float = 0.001
|
processing_time: float = 0.001
|
||||||
timeout: int = 30
|
timeout: int = 30
|
||||||
simulation: bool = True
|
simulation: bool = True
|
||||||
|
# Set to service process receives on first
|
||||||
main_pub: str | None = None
|
main_pub: str | None = None
|
||||||
main_pub_drained: bool = True
|
main_pub_drained: bool = False
|
||||||
vision_pubs: list[str] = field(default_factory=list)
|
vision_pubs: list[str] = field(default_factory=list)
|
||||||
ignore_alive_pubs: list[str] = field(default_factory=list)
|
ignore_alive_pubs: list[str] = field(default_factory=list)
|
||||||
unlocked_pubs: list[str] = field(default_factory=list)
|
|
||||||
|
def __post_init__(self):
|
||||||
|
# If the process is polling a service, we can just lock that one to speed up replay
|
||||||
|
if self.main_pub is None and isinstance(self.should_recv_callback, MessageBasedRcvCallback):
|
||||||
|
self.main_pub = self.should_recv_callback.trigger_msg_type
|
||||||
|
|
||||||
|
|
||||||
class ProcessContainer:
|
class ProcessContainer:
|
||||||
def __init__(self, cfg: ProcessConfig):
|
def __init__(self, cfg: ProcessConfig):
|
||||||
self.prefix = OpenpilotPrefix(clean_dirs_on_exit=False)
|
self.prefix = OpenpilotPrefix(create_dirs_on_enter=False, clean_dirs_on_exit=False)
|
||||||
self.cfg = copy.deepcopy(cfg)
|
self.cfg = copy.deepcopy(cfg)
|
||||||
self.process = copy.deepcopy(managed_processes[cfg.proc_name])
|
self.process = copy.deepcopy(managed_processes[cfg.proc_name])
|
||||||
self.msg_queue: list[capnp._DynamicStructReader] = []
|
self.msg_queue: list[capnp._DynamicStructReader] = []
|
||||||
|
self.last_input_log_mono_time: int = -1
|
||||||
self.cnt = 0
|
self.cnt = 0
|
||||||
self.pm: messaging.PubMaster | None = None
|
self.pm: messaging.PubMaster | None = None
|
||||||
self.sockets: list[messaging.SubSocket] | None = None
|
self.sockets: list[messaging.SubSocket] | None = None
|
||||||
@@ -211,8 +204,10 @@ class ProcessContainer:
|
|||||||
streams_metas = available_streams(all_msgs)
|
streams_metas = available_streams(all_msgs)
|
||||||
for meta in streams_metas:
|
for meta in streams_metas:
|
||||||
if meta.camera_state in self.cfg.vision_pubs:
|
if meta.camera_state in self.cfg.vision_pubs:
|
||||||
|
assert frs[meta.camera_state].pix_fmt == 'nv12'
|
||||||
frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h)
|
frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h)
|
||||||
vipc_server.create_buffers(meta.stream, 2, *frame_size)
|
stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1])
|
||||||
|
vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height)
|
||||||
vipc_server.start_listener()
|
vipc_server.start_listener()
|
||||||
|
|
||||||
self.vipc_server = vipc_server
|
self.vipc_server = vipc_server
|
||||||
@@ -226,10 +221,11 @@ class ProcessContainer:
|
|||||||
|
|
||||||
def start(
|
def start(
|
||||||
self, params_config: dict[str, Any], environ_config: dict[str, Any],
|
self, params_config: dict[str, Any], environ_config: dict[str, Any],
|
||||||
all_msgs: LogIterable, frs: dict[str, BaseFrameReader] | None,
|
all_msgs: LogIterable, frs: dict[str, FrameReader] | None,
|
||||||
fingerprint: str | None, capture_output: bool
|
fingerprint: str | None, capture_output: bool
|
||||||
):
|
):
|
||||||
with self.prefix as p:
|
with self.prefix as p:
|
||||||
|
self.prefix.create_dirs()
|
||||||
self._setup_env(params_config, environ_config)
|
self._setup_env(params_config, environ_config)
|
||||||
|
|
||||||
if self.cfg.config_callback is not None:
|
if self.cfg.config_callback is not None:
|
||||||
@@ -255,11 +251,6 @@ class ProcessContainer:
|
|||||||
if self.cfg.init_callback is not None:
|
if self.cfg.init_callback is not None:
|
||||||
self.cfg.init_callback(self.rc, self.pm, all_msgs, fingerprint)
|
self.cfg.init_callback(self.rc, self.pm, all_msgs, fingerprint)
|
||||||
|
|
||||||
# wait for process to startup
|
|
||||||
with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(self.cfg.proc_name)}"):
|
|
||||||
while not all(self.pm.all_readers_updated(s) for s in self.cfg.pubs if s not in self.cfg.ignore_alive_pubs):
|
|
||||||
time.sleep(0)
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
with self.prefix:
|
with self.prefix:
|
||||||
self.process.signal(signal.SIGKILL)
|
self.process.signal(signal.SIGKILL)
|
||||||
@@ -268,50 +259,70 @@ class ProcessContainer:
|
|||||||
self.prefix.clean_dirs()
|
self.prefix.clean_dirs()
|
||||||
self._clean_env()
|
self._clean_env()
|
||||||
|
|
||||||
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, BaseFrameReader] | None) -> list[capnp._DynamicStructReader]:
|
def get_output_msgs(self, start_time: int):
|
||||||
|
assert self.rc and self.sockets
|
||||||
|
|
||||||
|
output_msgs = []
|
||||||
|
self.rc.wait_for_recv_called()
|
||||||
|
for socket in self.sockets:
|
||||||
|
ms = messaging.drain_sock(socket)
|
||||||
|
for m in ms:
|
||||||
|
m = m.as_builder()
|
||||||
|
assert start_time > 0, "start_time must be positive"
|
||||||
|
m.logMonoTime = start_time + int(self.cfg.processing_time * 1e9)
|
||||||
|
output_msgs.append(m.as_reader())
|
||||||
|
return output_msgs
|
||||||
|
|
||||||
|
def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
|
||||||
assert self.rc and self.pm and self.sockets and self.process.proc
|
assert self.rc and self.pm and self.sockets and self.process.proc
|
||||||
|
|
||||||
output_msgs = []
|
output_msgs = []
|
||||||
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
|
end_of_cycle = True
|
||||||
end_of_cycle = True
|
if self.cfg.should_recv_callback is not None:
|
||||||
if self.cfg.should_recv_callback is not None:
|
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
|
||||||
end_of_cycle = self.cfg.should_recv_callback(msg, self.cfg, self.cnt)
|
|
||||||
|
|
||||||
self.msg_queue.append(msg)
|
|
||||||
if end_of_cycle:
|
|
||||||
self.rc.wait_for_recv_called()
|
|
||||||
|
|
||||||
|
self.msg_queue.append(msg)
|
||||||
|
if end_of_cycle:
|
||||||
|
with self.prefix, Timeout(self.cfg.timeout, error_msg=f"timed out testing process {repr(self.cfg.proc_name)}"):
|
||||||
# call recv to let sub-sockets reconnect, after we know the process is ready
|
# call recv to let sub-sockets reconnect, after we know the process is ready
|
||||||
if self.cnt == 0:
|
if self.cnt == 0:
|
||||||
for s in self.sockets:
|
for s in self.sockets:
|
||||||
messaging.recv_one_or_none(s)
|
messaging.recv_one_or_none(s)
|
||||||
|
|
||||||
# empty recv on drained pub indicates the end of messages, only do that if there're any
|
# certain processes use drain_sock. need to cause empty recv to break from this loop
|
||||||
trigger_empty_recv = False
|
trigger_empty_recv = False
|
||||||
if self.cfg.main_pub and self.cfg.main_pub_drained:
|
if self.cfg.main_pub and self.cfg.main_pub_drained:
|
||||||
trigger_empty_recv = next((True for m in self.msg_queue if m.which() == self.cfg.main_pub), False)
|
trigger_empty_recv = any(m.which() == self.cfg.main_pub for m in self.msg_queue)
|
||||||
|
|
||||||
|
# get output msgs from previous inputs
|
||||||
|
output_msgs = self.get_output_msgs(self.last_input_log_mono_time)
|
||||||
|
|
||||||
for m in self.msg_queue:
|
for m in self.msg_queue:
|
||||||
self.pm.send(m.which(), m.as_builder())
|
self.pm.send(m.which(), m.as_builder())
|
||||||
|
self.last_input_log_mono_time = max(self.last_input_log_mono_time, m.logMonoTime)
|
||||||
# send frames if needed
|
# send frames if needed
|
||||||
if self.vipc_server is not None and m.which() in self.cfg.vision_pubs:
|
if self.vipc_server is not None and m.which() in self.cfg.vision_pubs:
|
||||||
camera_state = getattr(m, m.which())
|
camera_state = getattr(m, m.which())
|
||||||
camera_meta = meta_from_camera_state(m.which())
|
camera_meta = meta_from_camera_state(m.which())
|
||||||
assert frs is not None
|
assert frs is not None
|
||||||
img = frs[m.which()].get(camera_state.frameId, pix_fmt="nv12")[0]
|
img = frs[m.which()].get(camera_state.frameId)
|
||||||
self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(),
|
|
||||||
|
h, w = frs[m.which()].h, frs[m.which()].w
|
||||||
|
stride, y_height, _, yuv_size = get_nv12_info(w, h)
|
||||||
|
uv_offset = stride * y_height
|
||||||
|
padded_img = np.zeros(((uv_offset //stride) + (h // 2), stride))
|
||||||
|
padded_img[:h, :w] = img[:h * w].reshape((-1, w))
|
||||||
|
padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w))
|
||||||
|
img_bytes = np.zeros((yuv_size,), dtype=np.uint8)
|
||||||
|
img_bytes[:padded_img.size] = padded_img.flatten()
|
||||||
|
|
||||||
|
self.vipc_server.send(camera_meta.stream, img_bytes.tobytes(),
|
||||||
camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof)
|
camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof)
|
||||||
self.msg_queue = []
|
self.msg_queue = []
|
||||||
|
|
||||||
self.rc.unlock_sockets()
|
self.rc.unlock_sockets()
|
||||||
self.rc.wait_for_next_recv(trigger_empty_recv)
|
if trigger_empty_recv:
|
||||||
|
self.rc.unlock_sockets()
|
||||||
for socket in self.sockets:
|
|
||||||
ms = messaging.drain_sock(socket)
|
|
||||||
for m in ms:
|
|
||||||
m = m.as_builder()
|
|
||||||
m.logMonoTime = msg.logMonoTime + int(self.cfg.processing_time * 1e9)
|
|
||||||
output_msgs.append(m.as_reader())
|
|
||||||
self.cnt += 1
|
self.cnt += 1
|
||||||
assert self.process.proc.is_alive()
|
assert self.process.proc.is_alive()
|
||||||
|
|
||||||
@@ -321,7 +332,7 @@ class ProcessContainer:
|
|||||||
def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
||||||
print("start fingerprinting")
|
print("start fingerprinting")
|
||||||
params = Params()
|
params = Params()
|
||||||
canmsgs = [msg for msg in msgs if msg.which() == "can"][:300]
|
canmsgs = list(islice((m for m in msgs if m.which() == "can"), 300))
|
||||||
|
|
||||||
# card expects one arbitrary can and pandaState
|
# card expects one arbitrary can and pandaState
|
||||||
rc.send_sync(pm, "can", messaging.new_message("can", 1))
|
rc.send_sync(pm, "can", messaging.new_message("can", 1))
|
||||||
@@ -342,40 +353,28 @@ def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
|||||||
def get_car_params_callback(rc, pm, msgs, fingerprint):
|
def get_car_params_callback(rc, pm, msgs, fingerprint):
|
||||||
params = Params()
|
params = Params()
|
||||||
if fingerprint:
|
if fingerprint:
|
||||||
CarInterface, _, _, _ = interfaces[fingerprint]
|
CarInterface = interfaces[fingerprint]
|
||||||
CP = CarInterface.get_non_essential_params(fingerprint)
|
CP = CarInterface.get_non_essential_params(fingerprint)
|
||||||
else:
|
else:
|
||||||
can = DummySocket()
|
can_msgs = ([CanData(can.address, can.dat, can.src) for can in m.can] for m in msgs if m.which() == "can")
|
||||||
sendcan = DummySocket()
|
|
||||||
|
|
||||||
canmsgs = [msg for msg in msgs if msg.which() == "can"]
|
|
||||||
cached_params_raw = params.get("CarParamsCache")
|
cached_params_raw = params.get("CarParamsCache")
|
||||||
has_cached_cp = cached_params_raw is not None
|
assert next(can_msgs, None), "CAN messages are required for fingerprinting"
|
||||||
assert len(canmsgs) != 0, "CAN messages are required for fingerprinting"
|
assert os.environ.get("SKIP_FW_QUERY", False) or cached_params_raw is not None, \
|
||||||
assert os.environ.get("SKIP_FW_QUERY", False) or has_cached_cp, \
|
|
||||||
"CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs."
|
"CarParamsCache is required for fingerprinting. Make sure to keep carParams msgs in the logs."
|
||||||
|
|
||||||
for m in canmsgs[:300]:
|
def can_recv(wait_for_one: bool = False) -> list[list[CanData]]:
|
||||||
can.send(m.as_builder().to_bytes())
|
return [next(can_msgs, [])]
|
||||||
can_callbacks = can_comm_callbacks(can, sendcan)
|
|
||||||
|
|
||||||
cached_params = None
|
cached_params = None
|
||||||
if has_cached_cp:
|
if cached_params_raw is not None:
|
||||||
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
||||||
cached_params = _cached_params
|
cached_params = _cached_params
|
||||||
|
|
||||||
CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("ExperimentalLongitudinalEnabled"), cached_params=cached_params).CP
|
CP = get_car(can_recv, lambda _msgs: None, lambda obd: None, params.get_bool("AlphaLongitudinalEnabled"), False, cached_params=cached_params).CP
|
||||||
|
|
||||||
if not params.get_bool("DisengageOnAccelerator"):
|
|
||||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
|
||||||
|
|
||||||
params.put("CarParams", CP.to_bytes())
|
params.put("CarParams", CP.to_bytes())
|
||||||
|
|
||||||
|
|
||||||
def selfdrived_rcv_callback(msg, cfg, frame):
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'carState'
|
|
||||||
|
|
||||||
|
|
||||||
def card_rcv_callback(msg, cfg, frame):
|
def card_rcv_callback(msg, cfg, frame):
|
||||||
# no sendcan until card is initialized
|
# no sendcan until card is initialized
|
||||||
if msg.which() != "can":
|
if msg.which() != "can":
|
||||||
@@ -390,21 +389,6 @@ def card_rcv_callback(msg, cfg, frame):
|
|||||||
return len(socks) > 0
|
return len(socks) > 0
|
||||||
|
|
||||||
|
|
||||||
def calibration_rcv_callback(msg, cfg, frame):
|
|
||||||
# calibrationd publishes 1 calibrationData every 5 cameraOdometry packets.
|
|
||||||
# should_recv always true to increment frame
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'cameraOdometry'
|
|
||||||
|
|
||||||
|
|
||||||
def torqued_rcv_callback(msg, cfg, frame):
|
|
||||||
# should_recv always true to increment frame
|
|
||||||
return (frame - 1) == 0 or msg.which() == 'livePose'
|
|
||||||
|
|
||||||
|
|
||||||
def dmonitoringmodeld_rcv_callback(msg, cfg, frame):
|
|
||||||
return msg.which() == "driverCameraState"
|
|
||||||
|
|
||||||
|
|
||||||
class ModeldCameraSyncRcvCallback:
|
class ModeldCameraSyncRcvCallback:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.road_present = False
|
self.road_present = False
|
||||||
@@ -429,26 +413,13 @@ class ModeldCameraSyncRcvCallback:
|
|||||||
|
|
||||||
|
|
||||||
class MessageBasedRcvCallback:
|
class MessageBasedRcvCallback:
|
||||||
def __init__(self, trigger_msg_type):
|
def __init__(self, trigger_msg_type: str, first_frame: bool = False):
|
||||||
self.trigger_msg_type = trigger_msg_type
|
self.trigger_msg_type = trigger_msg_type
|
||||||
|
self.first_frame = first_frame
|
||||||
|
|
||||||
def __call__(self, msg, cfg, frame):
|
def __call__(self, msg, cfg, frame):
|
||||||
return msg.which() == self.trigger_msg_type
|
# publish on first frame or trigger msg
|
||||||
|
return ((frame - 1) == 0 and self.first_frame) or msg.which() == self.trigger_msg_type
|
||||||
|
|
||||||
class FrequencyBasedRcvCallback:
|
|
||||||
def __init__(self, trigger_msg_type):
|
|
||||||
self.trigger_msg_type = trigger_msg_type
|
|
||||||
|
|
||||||
def __call__(self, msg, cfg, frame):
|
|
||||||
if msg.which() != self.trigger_msg_type:
|
|
||||||
return False
|
|
||||||
|
|
||||||
resp_sockets = [
|
|
||||||
s for s in cfg.subs
|
|
||||||
if frame % max(1, int(SERVICE_LIST[msg.which()].frequency / SERVICE_LIST[s].frequency)) == 0
|
|
||||||
]
|
|
||||||
return bool(len(resp_sockets))
|
|
||||||
|
|
||||||
|
|
||||||
def selfdrived_config_callback(params, cfg, lr):
|
def selfdrived_config_callback(params, cfg, lr):
|
||||||
@@ -463,16 +434,16 @@ CONFIGS = [
|
|||||||
proc_name="selfdrived",
|
proc_name="selfdrived",
|
||||||
pubs=[
|
pubs=[
|
||||||
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
|
"carState", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState",
|
||||||
"longitudinalPlan", "livePose", "liveParameters", "radarState",
|
"longitudinalPlan", "livePose", "liveDelay", "liveParameters", "radarState", "modelV2",
|
||||||
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
|
"driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "liveTorqueParameters",
|
||||||
"liveTorqueParameters", "accelerometer", "gyroscope", "carOutput",
|
"accelerometer", "gyroscope", "carOutput", "gpsLocationExternal", "gpsLocation", "controlsState",
|
||||||
"gpsLocationExternal", "gpsLocation", "controlsState", "carControl", "driverAssistance", "alertDebug",
|
"carControl", "driverAssistance", "alertDebug", "audioFeedback",
|
||||||
],
|
],
|
||||||
subs=["selfdriveState", "onroadEvents"],
|
subs=["selfdriveState", "onroadEvents"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
config_callback=selfdrived_config_callback,
|
config_callback=selfdrived_config_callback,
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=selfdrived_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("carState", True),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
),
|
),
|
||||||
@@ -497,6 +468,7 @@ CONFIGS = [
|
|||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
main_pub="can",
|
main_pub="can",
|
||||||
|
main_pub_drained=True,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="radard",
|
proc_name="radard",
|
||||||
@@ -504,7 +476,7 @@ CONFIGS = [
|
|||||||
subs=["radarState"],
|
subs=["radarState"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
|
should_recv_callback=MessageBasedRcvCallback("modelV2"),
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="plannerd",
|
proc_name="plannerd",
|
||||||
@@ -512,22 +484,23 @@ CONFIGS = [
|
|||||||
subs=["longitudinalPlan", "driverAssistance"],
|
subs=["longitudinalPlan", "driverAssistance"],
|
||||||
ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"],
|
ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
|
should_recv_callback=MessageBasedRcvCallback("modelV2"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="calibrationd",
|
proc_name="calibrationd",
|
||||||
pubs=["carState", "cameraOdometry", "carParams"],
|
pubs=["carState", "cameraOdometry"],
|
||||||
subs=["liveCalibration"],
|
subs=["liveCalibration"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=calibration_rcv_callback,
|
init_callback=get_car_params_callback,
|
||||||
|
should_recv_callback=MessageBasedRcvCallback("cameraOdometry", True),
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="dmonitoringd",
|
proc_name="dmonitoringd",
|
||||||
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"],
|
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "selfdriveState"],
|
||||||
subs=["driverMonitoringState"],
|
subs=["driverMonitoringState"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"),
|
should_recv_callback=MessageBasedRcvCallback("driverStateV2"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
@@ -539,7 +512,7 @@ CONFIGS = [
|
|||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
should_recv_callback=MessageBasedRcvCallback("cameraOdometry"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
unlocked_pubs=["accelerometer", "gyroscope"],
|
processing_time=0.01,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="paramsd",
|
proc_name="paramsd",
|
||||||
@@ -547,10 +520,19 @@ CONFIGS = [
|
|||||||
subs=["liveParameters"],
|
subs=["liveParameters"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=FrequencyBasedRcvCallback("livePose"),
|
should_recv_callback=MessageBasedRcvCallback("livePose"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.004,
|
processing_time=0.004,
|
||||||
),
|
),
|
||||||
|
ProcessConfig(
|
||||||
|
proc_name="lagd",
|
||||||
|
pubs=["livePose", "liveCalibration", "carState", "carControl", "controlsState"],
|
||||||
|
subs=["liveDelay"],
|
||||||
|
ignore=["logMonoTime"],
|
||||||
|
init_callback=get_car_params_callback,
|
||||||
|
should_recv_callback=MessageBasedRcvCallback("livePose"),
|
||||||
|
tolerance=NUMPY_TOLERANCE,
|
||||||
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="ubloxd",
|
proc_name="ubloxd",
|
||||||
pubs=["ubloxRaw"],
|
pubs=["ubloxRaw"],
|
||||||
@@ -559,23 +541,22 @@ CONFIGS = [
|
|||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="torqued",
|
proc_name="torqued",
|
||||||
pubs=["livePose", "liveCalibration", "carState", "carControl", "carOutput"],
|
pubs=["livePose", "liveCalibration", "liveDelay", "carState", "carControl", "carOutput"],
|
||||||
subs=["liveTorqueParameters"],
|
subs=["liveTorqueParameters"],
|
||||||
ignore=["logMonoTime"],
|
ignore=["logMonoTime"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
should_recv_callback=torqued_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("livePose", True),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
),
|
),
|
||||||
ProcessConfig(
|
ProcessConfig(
|
||||||
proc_name="modeld",
|
proc_name="modeld",
|
||||||
pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState", "carControl"],
|
pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"],
|
||||||
subs=["modelV2", "drivingModelData", "cameraOdometry"],
|
subs=["modelV2", "drivingModelData", "cameraOdometry"],
|
||||||
ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"],
|
ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"],
|
||||||
should_recv_callback=ModeldCameraSyncRcvCallback(),
|
should_recv_callback=ModeldCameraSyncRcvCallback(),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.020,
|
processing_time=0.020,
|
||||||
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream),
|
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("roadCameraState").stream),
|
||||||
main_pub_drained=False,
|
|
||||||
vision_pubs=["roadCameraState", "wideRoadCameraState"],
|
vision_pubs=["roadCameraState", "wideRoadCameraState"],
|
||||||
ignore_alive_pubs=["wideRoadCameraState"],
|
ignore_alive_pubs=["wideRoadCameraState"],
|
||||||
init_callback=get_car_params_callback,
|
init_callback=get_car_params_callback,
|
||||||
@@ -585,11 +566,10 @@ CONFIGS = [
|
|||||||
pubs=["liveCalibration", "driverCameraState"],
|
pubs=["liveCalibration", "driverCameraState"],
|
||||||
subs=["driverStateV2"],
|
subs=["driverStateV2"],
|
||||||
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
|
ignore=["logMonoTime", "driverStateV2.modelExecutionTime", "driverStateV2.gpuExecutionTime"],
|
||||||
should_recv_callback=dmonitoringmodeld_rcv_callback,
|
should_recv_callback=MessageBasedRcvCallback("driverCameraState"),
|
||||||
tolerance=NUMPY_TOLERANCE,
|
tolerance=NUMPY_TOLERANCE,
|
||||||
processing_time=0.020,
|
processing_time=0.020,
|
||||||
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream),
|
main_pub=vipc_get_endpoint_name("camerad", meta_from_camera_state("driverCameraState").stream),
|
||||||
main_pub_drained=False,
|
|
||||||
vision_pubs=["driverCameraState"],
|
vision_pubs=["driverCameraState"],
|
||||||
ignore_alive_pubs=["driverCameraState"],
|
ignore_alive_pubs=["driverCameraState"],
|
||||||
),
|
),
|
||||||
@@ -628,9 +608,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") ->
|
|||||||
if len(live_calibration) > 0:
|
if len(live_calibration) > 0:
|
||||||
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
custom_params["CalibrationParams"] = live_calibration[msg_index].as_builder().to_bytes()
|
||||||
if len(live_parameters) > 0:
|
if len(live_parameters) > 0:
|
||||||
lp_dict = live_parameters[msg_index].to_dict()
|
custom_params["LiveParametersV2"] = live_parameters[msg_index].as_builder().to_bytes()
|
||||||
lp_dict["carFingerprint"] = CP.carFingerprint
|
|
||||||
custom_params["LiveParameters"] = json.dumps(lp_dict)
|
|
||||||
if len(live_torque_parameters) > 0:
|
if len(live_torque_parameters) > 0:
|
||||||
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
custom_params["LiveTorqueParameters"] = live_torque_parameters[msg_index].as_builder().to_bytes()
|
||||||
|
|
||||||
@@ -649,9 +627,9 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args,
|
|||||||
|
|
||||||
|
|
||||||
def replay_process(
|
def replay_process(
|
||||||
cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] = None,
|
cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None = None,
|
||||||
fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None,
|
fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None,
|
||||||
captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False
|
captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False
|
||||||
) -> list[capnp._DynamicStructReader]:
|
) -> list[capnp._DynamicStructReader]:
|
||||||
if isinstance(cfg, Iterable):
|
if isinstance(cfg, Iterable):
|
||||||
cfgs = list(cfg)
|
cfgs = list(cfg)
|
||||||
@@ -677,7 +655,7 @@ def replay_process(
|
|||||||
|
|
||||||
|
|
||||||
def _replay_multi_process(
|
def _replay_multi_process(
|
||||||
cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] | None, fingerprint: str | None,
|
cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None, fingerprint: str | None,
|
||||||
custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool
|
custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool
|
||||||
) -> list[capnp._DynamicStructReader]:
|
) -> list[capnp._DynamicStructReader]:
|
||||||
if fingerprint is not None:
|
if fingerprint is not None:
|
||||||
@@ -699,8 +677,8 @@ def _replay_multi_process(
|
|||||||
|
|
||||||
all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime)
|
all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime)
|
||||||
log_msgs = []
|
log_msgs = []
|
||||||
|
containers = []
|
||||||
try:
|
try:
|
||||||
containers = []
|
|
||||||
for cfg in cfgs:
|
for cfg in cfgs:
|
||||||
container = ProcessContainer(cfg)
|
container = ProcessContainer(cfg)
|
||||||
containers.append(container)
|
containers.append(container)
|
||||||
@@ -735,6 +713,11 @@ def _replay_multi_process(
|
|||||||
internal_pub_queue.append(m)
|
internal_pub_queue.append(m)
|
||||||
heapq.heappush(internal_pub_index_heap, (m.logMonoTime, len(internal_pub_queue) - 1))
|
heapq.heappush(internal_pub_index_heap, (m.logMonoTime, len(internal_pub_queue) - 1))
|
||||||
log_msgs.extend(output_msgs)
|
log_msgs.extend(output_msgs)
|
||||||
|
|
||||||
|
# flush last set of messages from each process
|
||||||
|
for container in containers:
|
||||||
|
last_time = container.last_input_log_mono_time if container.last_input_log_mono_time > 0 else int(time.monotonic() * 1e9)
|
||||||
|
log_msgs.extend(container.get_output_msgs(last_time))
|
||||||
finally:
|
finally:
|
||||||
for container in containers:
|
for container in containers:
|
||||||
container.stop()
|
container.stop()
|
||||||
@@ -762,15 +745,12 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non
|
|||||||
params_dict["IsRhdDetected"] = is_rhd
|
params_dict["IsRhdDetected"] = is_rhd
|
||||||
|
|
||||||
if CP is not None:
|
if CP is not None:
|
||||||
if CP.alternativeExperience == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS:
|
|
||||||
params_dict["DisengageOnAccelerator"] = False
|
|
||||||
|
|
||||||
if fingerprint is None:
|
if fingerprint is None:
|
||||||
if CP.fingerprintSource == "fw":
|
if CP.fingerprintSource == "fw":
|
||||||
params_dict["CarParamsCache"] = CP.as_builder().to_bytes()
|
params_dict["CarParamsCache"] = CP.as_builder().to_bytes()
|
||||||
|
|
||||||
if CP.openpilotLongitudinalControl:
|
if CP.openpilotLongitudinalControl:
|
||||||
params_dict["ExperimentalLongitudinalEnabled"] = True
|
params_dict["AlphaLongitudinalEnabled"] = True
|
||||||
|
|
||||||
if CP.notCar:
|
if CP.notCar:
|
||||||
params_dict["JoystickDebugMode"] = True
|
params_dict["JoystickDebugMode"] = True
|
||||||
|
|||||||
@@ -3,43 +3,20 @@ import os
|
|||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
import capnp
|
import capnp
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \
|
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \
|
||||||
check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr
|
check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr
|
||||||
from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FRAME_SIZES
|
|
||||||
from openpilot.selfdrive.test.update_ci_routes import upload_route
|
from openpilot.selfdrive.test.update_ci_routes import upload_route
|
||||||
from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType
|
from openpilot.tools.lib.framereader import FrameReader
|
||||||
from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log
|
from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log
|
||||||
from openpilot.tools.lib.openpilotci import get_url
|
from openpilot.tools.lib.openpilotci import get_url
|
||||||
|
|
||||||
class DummyFrameReader(BaseFrameReader):
|
|
||||||
def __init__(self, w: int, h: int, frame_count: int, pix_val: int):
|
|
||||||
self.pix_val = pix_val
|
|
||||||
self.w, self.h = w, h
|
|
||||||
self.frame_count = frame_count
|
|
||||||
self.frame_type = FrameType.raw
|
|
||||||
|
|
||||||
def get(self, idx, count=1, pix_fmt="yuv420p"):
|
|
||||||
if pix_fmt == "rgb24":
|
|
||||||
shape = (self.h, self.w, 3)
|
|
||||||
elif pix_fmt == "nv12" or pix_fmt == "yuv420p":
|
|
||||||
shape = (int((self.h * self.w) * 3 / 2),)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
return [np.full(shape, self.pix_val, dtype=np.uint8) for _ in range(count)]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def zero_dcamera():
|
|
||||||
return DummyFrameReader(*DRIVER_CAMERA_FRAME_SIZES[("tici", "ar0231")], 1200, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def regen_segment(
|
def regen_segment(
|
||||||
lr: LogIterable, frs: dict[str, Any] = None,
|
lr: LogIterable, frs: dict[str, Any] | None = None,
|
||||||
processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False
|
processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False
|
||||||
) -> list[capnp._DynamicStructReader]:
|
) -> list[capnp._DynamicStructReader]:
|
||||||
all_msgs = sorted(lr, key=lambda m: m.logMonoTime)
|
all_msgs = sorted(lr, key=lambda m: m.logMonoTime)
|
||||||
@@ -64,7 +41,7 @@ def setup_data_readers(
|
|||||||
frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc"))
|
frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc"))
|
||||||
if needs_driver_cam:
|
if needs_driver_cam:
|
||||||
if dummy_driver_cam:
|
if dummy_driver_cam:
|
||||||
frs['driverCameraState'] = DummyFrameReader.zero_dcamera()
|
frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) # Use fcam as dummy
|
||||||
else:
|
else:
|
||||||
device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData")
|
device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData")
|
||||||
assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera."
|
assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera."
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import copy
|
|||||||
import os
|
import os
|
||||||
from hypothesis import given, HealthCheck, Phase, settings
|
from hypothesis import given, HealthCheck, Phase, settings
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
|
|
||||||
from cereal import log
|
from cereal import log
|
||||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||||
|
|||||||
@@ -3,87 +3,87 @@ import argparse
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from opendbc.car.car_helpers import interface_names
|
from opendbc.car.car_helpers import interface_names
|
||||||
from openpilot.common.git import get_commit
|
from openpilot.common.git import get_commit
|
||||||
from openpilot.tools.lib.openpilotci import get_url, upload_file
|
from openpilot.tools.lib.openpilotci import get_url
|
||||||
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff
|
||||||
|
from openpilot.selfdrive.test.process_replay.diff_report import diff_process, diff_report
|
||||||
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \
|
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \
|
||||||
check_most_messages_valid
|
check_most_messages_valid
|
||||||
from openpilot.tools.lib.filereader import FileReader
|
from openpilot.tools.lib.filereader import FileReader
|
||||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||||
|
from openpilot.tools.lib.url_file import URLFile
|
||||||
|
|
||||||
source_segments = [
|
source_segments = [
|
||||||
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.COMMA_BODY
|
|
||||||
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA
|
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA
|
||||||
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS)
|
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS)
|
||||||
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS
|
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS
|
||||||
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.TOYOTA_RAV4
|
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.TOYOTA_RAV4
|
||||||
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.TOYOTA_COROLLA_TSS2
|
("TOYOTA3", "8011d605be1cbb77|000000cc--8e8d8ec716--6"), # TOYOTA.TOYOTA_COROLLA_TSS2
|
||||||
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.HONDA_CIVIC (NIDEC)
|
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.HONDA_CIVIC (NIDEC)
|
||||||
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.HONDA_ACCORD (BOSCH)
|
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.HONDA_ACCORD (BOSCH)
|
||||||
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID
|
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID
|
||||||
("RAM", "17fc16d840fe9d21|2023-04-26--13-28-44--5"), # CHRYSLER.RAM_1500_5TH_GEN
|
("RAM", "17fc16d840fe9d21|2023-04-26--13-28-44--5"), # CHRYSLER.RAM_1500_5TH_GEN
|
||||||
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.SUBARU_OUTBACK
|
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.SUBARU_OUTBACK
|
||||||
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.CHEVROLET_VOLT
|
("GM", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.CHEVROLET_BOLT_EUV
|
||||||
("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.CHEVROLET_BOLT_EUV
|
|
||||||
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.NISSAN_XTRAIL
|
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.NISSAN_XTRAIL
|
||||||
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.VOLKSWAGEN_GOLF
|
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.VOLKSWAGEN_GOLF
|
||||||
|
# FIXME the sensor timings are bad in mazda segment, we're not fully testing it, but it should be replaced
|
||||||
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.MAZDA_CX9_2021
|
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.MAZDA_CX9_2021
|
||||||
("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.FORD_BRONCO_SPORT_MK1
|
("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.FORD_BRONCO_SPORT_MK1
|
||||||
("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"), # RIVIAN.RIVIAN_R1_GEN1
|
("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"), # RIVIAN.RIVIAN_R1_GEN1
|
||||||
|
("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"), # TESLA.TESLA_MODEL_Y
|
||||||
|
|
||||||
# Enable when port is tested and dashcamOnly is no longer set
|
# Enable when port is tested and dashcamOnly is no longer set
|
||||||
#("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS
|
#("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS
|
||||||
]
|
]
|
||||||
|
|
||||||
segments = [
|
segments = [
|
||||||
("BODY", "regenA67A128BCD8|2024-08-30--02-36-22--0"),
|
("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"),
|
||||||
("HYUNDAI", "regenCCD47FEBC0C|2025-03-04--03-21-48--0"),
|
("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"),
|
||||||
("HYUNDAI2", "regen306779F6870|2024-10-03--04-03-23--0"),
|
("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"),
|
||||||
("TOYOTA", "regen4A5115B248D|2025-03-04--03-21-43--0"),
|
# TODO: get new RAV4 route without enableDsu
|
||||||
("TOYOTA2", "regen6E484EDAB96|2024-08-30--02-47-37--0"),
|
# ("TOYOTA2", "regen107352E20EB|2025-04-08--22-57-46--0"),
|
||||||
("TOYOTA3", "regen4CE950B0267|2024-08-30--02-51-30--0"),
|
("TOYOTA3", "regen1455E3B4BDF|2025-04-09--03-26-06--0"),
|
||||||
("HONDA", "regenB8CABEC09CC|2025-03-04--03-32-55--0"),
|
("HONDA", "regenB328FF8BA0A|2025-04-08--22-57-45--0"),
|
||||||
("HONDA2", "regen4B38A7428CD|2024-08-30--02-56-31--0"),
|
("HONDA2", "regen6170C8C9A35|2025-04-08--22-57-46--0"),
|
||||||
("CHRYSLER", "regenF3DBBA9E8DF|2024-08-30--02-59-03--0"),
|
("CHRYSLER", "regen5B28FC2A437|2025-04-08--23-04-24--0"),
|
||||||
("RAM", "regenDB02684E00A|2024-08-30--03-02-54--0"),
|
("RAM", "regenBF81EA96E08|2025-04-08--23-06-54--0"),
|
||||||
("SUBARU", "regen5E3347D0A0F|2025-03-04--03-23-55--0"),
|
("SUBARU", "regen7366F13F6A1|2025-04-08--23-07-07--0"),
|
||||||
("GM", "regen720F2BA4CF6|2024-08-30--03-09-15--0"),
|
("GM", "regen1271097D038|2025-04-09--03-26-00--0"),
|
||||||
("GM2", "regen9ADBECBCD1C|2024-08-30--03-13-04--0"),
|
("NISSAN", "regen15D60604EAB|2025-04-08--23-06-59--0"),
|
||||||
("NISSAN", "regen58464878D07|2024-08-30--03-15-31--0"),
|
("VOLKSWAGEN", "regen0F2F06C9539|2025-04-08--23-06-56--0"),
|
||||||
("VOLKSWAGEN", "regenED976DEB757|2024-08-30--03-18-02--0"),
|
|
||||||
("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"),
|
("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"),
|
||||||
("FORD", "regenA75209BD115|2025-03-04--03-23-53--0"),
|
("FORD", "regen755D8CB1E1F|2025-04-08--23-13-43--0"),
|
||||||
("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"),
|
("RIVIAN", "regen5FCAC896BBE|2025-04-08--23-13-35--0"),
|
||||||
|
("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# dashcamOnly makes don't need to be tested until a full port is done
|
# dashcamOnly makes don't need to be tested until a full port is done
|
||||||
excluded_interfaces = ["mock", "tesla", "rivian"]
|
excluded_interfaces = ["mock", "body", "psa"]
|
||||||
|
|
||||||
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
|
BASE_URL = "https://raw.githubusercontent.com/commaai/ci-artifacts/refs/heads/process-replay/"
|
||||||
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
|
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
|
||||||
EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"}
|
EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"}
|
||||||
|
|
||||||
|
|
||||||
def run_test_process(data):
|
def run_test_process(data):
|
||||||
segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data
|
segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data
|
||||||
res = None
|
ref_log_msgs = list(LogReader(ref_log_path))
|
||||||
if not args.upload_only:
|
lr = LogReader.from_bytes(lr_dat)
|
||||||
lr = LogReader.from_bytes(lr_dat)
|
res, log_msgs = test_process(cfg, lr, segment, ref_log_msgs, cur_log_fn, args.ignore_fields, args.ignore_msgs)
|
||||||
res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs)
|
# save logs so we can update refs
|
||||||
# save logs so we can upload when updating refs
|
save_log(cur_log_fn, log_msgs)
|
||||||
save_log(cur_log_fn, log_msgs)
|
try:
|
||||||
|
diff_data = diff_process(cfg, ref_log_msgs, log_msgs)
|
||||||
if args.update_refs or args.upload_only:
|
except Exception:
|
||||||
print(f'Uploading: {os.path.basename(cur_log_fn)}')
|
diff_data = traceback.format_exc()
|
||||||
assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}"
|
return (segment, cfg.proc_name, res, diff_data)
|
||||||
upload_file(cur_log_fn, os.path.basename(cur_log_fn))
|
|
||||||
os.remove(cur_log_fn)
|
|
||||||
return (segment, cfg.proc_name, res)
|
|
||||||
|
|
||||||
|
|
||||||
def get_log_data(segment):
|
def get_log_data(segment):
|
||||||
@@ -92,14 +92,12 @@ def get_log_data(segment):
|
|||||||
return (segment, f.read())
|
return (segment, f.read())
|
||||||
|
|
||||||
|
|
||||||
def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None):
|
def test_process(cfg, lr, segment, ref_log_msgs, new_log_path, ignore_fields=None, ignore_msgs=None):
|
||||||
if ignore_fields is None:
|
if ignore_fields is None:
|
||||||
ignore_fields = []
|
ignore_fields = []
|
||||||
if ignore_msgs is None:
|
if ignore_msgs is None:
|
||||||
ignore_msgs = []
|
ignore_msgs = []
|
||||||
|
|
||||||
ref_log_msgs = list(LogReader(ref_log_path))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log_msgs = replay_process(cfg, lr, disable_progress=True)
|
log_msgs = replay_process(cfg, lr, disable_progress=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -142,8 +140,6 @@ if __name__ == "__main__":
|
|||||||
help="Msgs to ignore (e.g. carEvents)")
|
help="Msgs to ignore (e.g. carEvents)")
|
||||||
parser.add_argument("--update-refs", action="store_true",
|
parser.add_argument("--update-refs", action="store_true",
|
||||||
help="Updates reference logs using current commit")
|
help="Updates reference logs using current commit")
|
||||||
parser.add_argument("--upload-only", action="store_true",
|
|
||||||
help="Skips testing processes and uploads logs from previous test run")
|
|
||||||
parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1),
|
parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1),
|
||||||
help="Max amount of parallel jobs")
|
help="Max amount of parallel jobs")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -153,18 +149,16 @@ if __name__ == "__main__":
|
|||||||
tested_cars = {c.upper() for c in tested_cars}
|
tested_cars = {c.upper() for c in tested_cars}
|
||||||
|
|
||||||
full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs))
|
full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs))
|
||||||
upload = args.update_refs or args.upload_only
|
|
||||||
os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True)
|
os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True)
|
||||||
|
|
||||||
if upload:
|
if args.update_refs:
|
||||||
assert full_test, "Need to run full test when updating refs"
|
assert full_test, "Need to run full test when updating refs"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(REF_COMMIT_FN) as f:
|
with open(REF_COMMIT_FN) as f:
|
||||||
ref_commit = f.read().strip()
|
ref_commit = f.read().strip()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Couldn't find reference commit")
|
ref_commit = URLFile(BASE_URL + "ref_commit", cache=False).read().decode().strip()
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
cur_commit = get_commit()
|
cur_commit = get_commit()
|
||||||
if not cur_commit:
|
if not cur_commit:
|
||||||
@@ -179,12 +173,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
log_paths: defaultdict[str, dict[str, dict[str, str]]] = defaultdict(lambda: defaultdict(dict))
|
log_paths: defaultdict[str, dict[str, dict[str, str]]] = defaultdict(lambda: defaultdict(dict))
|
||||||
with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool:
|
with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool:
|
||||||
if not args.upload_only:
|
download_segments = [seg for car, seg in segments if car in tested_cars]
|
||||||
download_segments = [seg for car, seg in segments if car in tested_cars]
|
log_data: dict[str, LogReader] = {}
|
||||||
log_data: dict[str, LogReader] = {}
|
p1 = pool.map(get_log_data, download_segments)
|
||||||
p1 = pool.map(get_log_data, download_segments)
|
for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)):
|
||||||
for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)):
|
log_data[segment] = lr
|
||||||
log_data[segment] = lr
|
|
||||||
|
|
||||||
pool_args: Any = []
|
pool_args: Any = []
|
||||||
for car_brand, segment in segments:
|
for car_brand, segment in segments:
|
||||||
@@ -196,38 +189,42 @@ if __name__ == "__main__":
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# to speed things up, we only test all segments on card
|
# to speed things up, we only test all segments on card
|
||||||
if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD'):
|
if cfg.proc_name not in ('card', 'controlsd', 'lagd') and car_brand not in ('HYUNDAI', 'TOYOTA'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst")
|
cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst".replace("|", "_"))
|
||||||
if args.update_refs: # reference logs will not exist if routes were just regenerated
|
if args.update_refs: # reference logs will not exist if routes were just regenerated
|
||||||
ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst")
|
route, seg_num = segment.rsplit("--", 1)
|
||||||
|
ref_log_path = get_url(route, seg_num, "rlog.zst")
|
||||||
else:
|
else:
|
||||||
ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst")
|
ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst".replace("|", "_"))
|
||||||
ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn)
|
ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn)
|
||||||
|
|
||||||
dat = None if args.upload_only else log_data[segment]
|
pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, log_data[segment]))
|
||||||
pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat))
|
|
||||||
|
|
||||||
log_paths[segment][cfg.proc_name]['ref'] = ref_log_path
|
log_paths[segment][cfg.proc_name]['ref'] = ref_log_path
|
||||||
log_paths[segment][cfg.proc_name]['new'] = cur_log_fn
|
log_paths[segment][cfg.proc_name]['new'] = cur_log_fn
|
||||||
|
|
||||||
results: Any = defaultdict(dict)
|
results: Any = defaultdict(dict)
|
||||||
|
diffs: list = []
|
||||||
p2 = pool.map(run_test_process, pool_args)
|
p2 = pool.map(run_test_process, pool_args)
|
||||||
for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)):
|
for (segment, proc, result, diff_data) in tqdm(p2, desc="Running Tests", total=len(pool_args)):
|
||||||
if not args.upload_only:
|
results[segment][proc] = result
|
||||||
results[segment][proc] = result
|
diffs.append((segment, proc, diff_data))
|
||||||
|
|
||||||
diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit)
|
diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit)
|
||||||
if not upload:
|
if not args.update_refs:
|
||||||
with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f:
|
with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f:
|
||||||
f.write(diff_long)
|
f.write(diff_long)
|
||||||
print(diff_short)
|
print(diff_short)
|
||||||
|
|
||||||
|
try:
|
||||||
|
diff_report(diffs, segments)
|
||||||
|
except Exception:
|
||||||
|
print(f"failed to generate diff report:\n{traceback.format_exc()}")
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
print("TEST FAILED")
|
print("TEST FAILED")
|
||||||
print("\n\nTo push the new reference logs for this commit run:")
|
|
||||||
print("./test_processes.py --upload-only")
|
|
||||||
else:
|
else:
|
||||||
print("TEST SUCCEEDED")
|
print("TEST SUCCEEDED")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from parameterized import parameterized
|
from openpilot.common.parameterized import parameterized
|
||||||
|
|
||||||
from openpilot.selfdrive.test.process_replay.regen import regen_segment, DummyFrameReader
|
from openpilot.selfdrive.test.process_replay.regen import regen_segment
|
||||||
from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled
|
from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled
|
||||||
from openpilot.tools.lib.openpilotci import get_url
|
from openpilot.tools.lib.openpilotci import get_url
|
||||||
from openpilot.tools.lib.logreader import LogReader
|
from openpilot.tools.lib.logreader import LogReader
|
||||||
@@ -18,7 +18,7 @@ def ci_setup_data_readers(route, sidx):
|
|||||||
lr = LogReader(get_url(route, sidx, "rlog.bz2"))
|
lr = LogReader(get_url(route, sidx, "rlog.bz2"))
|
||||||
frs = {
|
frs = {
|
||||||
'roadCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
|
'roadCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
|
||||||
'driverCameraState': DummyFrameReader.zero_dcamera()
|
'driverCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
|
||||||
}
|
}
|
||||||
if next((True for m in lr if m.which() == "wideRoadCameraState"), False):
|
if next((True for m in lr if m.which() == "wideRoadCameraState"), False):
|
||||||
frs["wideRoadCameraState"] = FrameReader(get_url(route, sidx, "ecamera.hevc"))
|
frs["wideRoadCameraState"] = FrameReader(get_url(route, sidx, "ecamera.hevc"))
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ if [ -z "$TEST_DIR" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# prevent storage from filling up
|
||||||
|
rm -rf /data/media/0/realdata/*
|
||||||
|
|
||||||
rm -rf /data/safe_staging/ || true
|
rm -rf /data/safe_staging/ || true
|
||||||
if [ -d /data/safe_staging/ ]; then
|
if [ -d /data/safe_staging/ ]; then
|
||||||
sudo umount /data/safe_staging/merged/ || true
|
sudo umount /data/safe_staging/merged/ || true
|
||||||
@@ -39,6 +42,7 @@ sudo systemctl restart NetworkManager
|
|||||||
sudo systemctl disable ssh-param-watcher.path
|
sudo systemctl disable ssh-param-watcher.path
|
||||||
sudo systemctl disable ssh-param-watcher.service
|
sudo systemctl disable ssh-param-watcher.service
|
||||||
sudo mount -o ro,remount /
|
sudo mount -o ro,remount /
|
||||||
|
sudo systemctl stop power_monitor
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
if ! sudo systemctl is-active -q ssh; then
|
if ! sudo systemctl is-active -q ssh; then
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
# Sets up a virtual display for running map renderer and simulator without an X11 display
|
# Sets up a virtual display for running map renderer and simulator without an X11 display
|
||||||
|
|
||||||
DISP_ID=99
|
if uname -r | grep -q "WSL2"; then
|
||||||
|
DISP_ID=0 # WSLg uses display :0
|
||||||
|
else
|
||||||
|
DISP_ID=99 # Standard Xvfb display
|
||||||
|
fi
|
||||||
export DISPLAY=:$DISP_ID
|
export DISPLAY=:$DISP_ID
|
||||||
|
|
||||||
sudo Xvfb $DISPLAY -screen 0 2160x1080x24 2>/dev/null &
|
sudo Xvfb $DISPLAY -screen 0 2160x1080x24 2>/dev/null &
|
||||||
@@ -15,5 +19,4 @@ do
|
|||||||
done
|
done
|
||||||
|
|
||||||
touch ~/.Xauthority
|
touch ~/.Xauthority
|
||||||
export XDG_SESSION_TYPE="x11"
|
export XDG_SESSION_TYPE="x11"
|
||||||
xset -q
|
|
||||||
@@ -8,7 +8,7 @@ import time
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tabulate import tabulate
|
from openpilot.common.utils import tabulate
|
||||||
|
|
||||||
from cereal import log
|
from cereal import log
|
||||||
import cereal.messaging as messaging
|
import cereal.messaging as messaging
|
||||||
@@ -18,7 +18,6 @@ from openpilot.common.timeout import Timeout
|
|||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
|
from openpilot.selfdrive.selfdrived.events import EVENTS, ET
|
||||||
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
|
from openpilot.selfdrive.test.helpers import set_params_enabled, release_only
|
||||||
from openpilot.system.hardware import HARDWARE
|
|
||||||
from openpilot.system.hardware.hw import Paths
|
from openpilot.system.hardware.hw import Paths
|
||||||
from openpilot.tools.lib.logreader import LogReader
|
from openpilot.tools.lib.logreader import LogReader
|
||||||
from openpilot.tools.lib.log_time_series import msgs_to_time_series
|
from openpilot.tools.lib.log_time_series import msgs_to_time_series
|
||||||
@@ -33,52 +32,44 @@ CPU usage budget
|
|||||||
TEST_DURATION = 25
|
TEST_DURATION = 25
|
||||||
LOG_OFFSET = 8
|
LOG_OFFSET = 8
|
||||||
|
|
||||||
MAX_TOTAL_CPU = 280. # total for all 8 cores
|
MAX_TOTAL_CPU = 350. # total for all 8 cores
|
||||||
PROCS = {
|
PROCS = {
|
||||||
# Baseline CPU usage by process
|
# Baseline CPU usage by process
|
||||||
"selfdrive.controls.controlsd": 16.0,
|
"selfdrive.controls.controlsd": 16.0,
|
||||||
"selfdrive.selfdrived.selfdrived": 16.0,
|
"selfdrive.selfdrived.selfdrived": 16.0,
|
||||||
"selfdrive.car.card": 26.0,
|
"selfdrive.car.card": 26.0,
|
||||||
"./loggerd": 14.0,
|
"./loggerd": 14.0,
|
||||||
"./encoderd": 17.0,
|
"./encoderd": 13.0,
|
||||||
"./camerad": 10.0,
|
"./camerad": 10.0,
|
||||||
"selfdrive.controls.plannerd": 9.0,
|
"selfdrive.controls.plannerd": 8.0,
|
||||||
"./ui": 18.0,
|
"selfdrive.ui.ui": 40.0,
|
||||||
"selfdrive.locationd.paramsd": 9.0,
|
"system.sensord.sensord": 13.0,
|
||||||
"./sensord": 7.0,
|
|
||||||
"selfdrive.controls.radard": 2.0,
|
"selfdrive.controls.radard": 2.0,
|
||||||
"selfdrive.modeld.modeld": 22.0,
|
"selfdrive.modeld.modeld": 22.0,
|
||||||
"selfdrive.modeld.dmonitoringmodeld": 21.0,
|
"selfdrive.modeld.dmonitoringmodeld": 18.0,
|
||||||
"system.hardware.hardwared": 4.0,
|
"system.hardware.hardwared": 4.0,
|
||||||
"selfdrive.locationd.calibrationd": 2.0,
|
"selfdrive.locationd.calibrationd": 2.0,
|
||||||
"selfdrive.locationd.torqued": 5.0,
|
"selfdrive.locationd.torqued": 5.0,
|
||||||
"selfdrive.locationd.locationd": 25.0,
|
"selfdrive.locationd.locationd": 25.0,
|
||||||
|
"selfdrive.locationd.paramsd": 9.0,
|
||||||
|
"selfdrive.locationd.lagd": 11.0,
|
||||||
"selfdrive.ui.soundd": 3.0,
|
"selfdrive.ui.soundd": 3.0,
|
||||||
|
"selfdrive.ui.feedback.feedbackd": 1.0,
|
||||||
"selfdrive.monitoring.dmonitoringd": 4.0,
|
"selfdrive.monitoring.dmonitoringd": 4.0,
|
||||||
"./proclogd": 2.0,
|
"system.proclogd": 7.0,
|
||||||
"system.logmessaged": 1.0,
|
"system.logmessaged": 1.0,
|
||||||
"system.tombstoned": 0,
|
"system.tombstoned": 0,
|
||||||
"./logcatd": 1.0,
|
"system.journald": 1.0,
|
||||||
"system.micd": 5.0,
|
"system.micd": 5.0,
|
||||||
"system.timed": 0,
|
"system.timed": 0,
|
||||||
"selfdrive.pandad.pandad": 0,
|
"selfdrive.pandad.pandad": 0,
|
||||||
"system.statsd": 1.0,
|
"system.statsd": 1.0,
|
||||||
"system.loggerd.uploader": 15.0,
|
"system.loggerd.uploader": 15.0,
|
||||||
"system.loggerd.deleter": 1.0,
|
"system.loggerd.deleter": 1.0,
|
||||||
|
"./pandad": 19.0,
|
||||||
|
"system.qcomgpsd.qcomgpsd": 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROCS.update({
|
|
||||||
"tici": {
|
|
||||||
"./pandad": 5.0,
|
|
||||||
"./ubloxd": 1.0,
|
|
||||||
"system.ubloxd.pigeond": 6.0,
|
|
||||||
},
|
|
||||||
"tizi": {
|
|
||||||
"./pandad": 19.0,
|
|
||||||
"system.qcomgpsd.qcomgpsd": 1.0,
|
|
||||||
}
|
|
||||||
}.get(HARDWARE.get_device_type(), {}))
|
|
||||||
|
|
||||||
TIMINGS = {
|
TIMINGS = {
|
||||||
# rtols: max/min, rsd
|
# rtols: max/min, rsd
|
||||||
"can": [2.5, 0.35],
|
"can": [2.5, 0.35],
|
||||||
@@ -95,6 +86,7 @@ TIMINGS = {
|
|||||||
"modelV2": [2.5, 0.35],
|
"modelV2": [2.5, 0.35],
|
||||||
"driverStateV2": [2.5, 0.40],
|
"driverStateV2": [2.5, 0.40],
|
||||||
"livePose": [2.5, 0.35],
|
"livePose": [2.5, 0.35],
|
||||||
|
"liveParameters": [2.5, 0.35],
|
||||||
"wideRoadCameraState": [1.5, 0.35],
|
"wideRoadCameraState": [1.5, 0.35],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +121,7 @@ class TestOnroad:
|
|||||||
params.put_bool("RecordFront", True)
|
params.put_bool("RecordFront", True)
|
||||||
set_params_enabled()
|
set_params_enabled()
|
||||||
os.environ['REPLAY'] = '1'
|
os.environ['REPLAY'] = '1'
|
||||||
|
os.environ['MSGQ_PREALLOC'] = '1'
|
||||||
os.environ['TESTING_CLOSET'] = '1'
|
os.environ['TESTING_CLOSET'] = '1'
|
||||||
if os.path.exists(Paths.log_root()):
|
if os.path.exists(Paths.log_root()):
|
||||||
shutil.rmtree(Paths.log_root())
|
shutil.rmtree(Paths.log_root())
|
||||||
@@ -145,7 +138,7 @@ class TestOnroad:
|
|||||||
while not sm.seen['carState']:
|
while not sm.seen['carState']:
|
||||||
sm.update(1000)
|
sm.update(1000)
|
||||||
|
|
||||||
route = params.get("CurrentRoute", encoding="utf-8")
|
route = params.get("CurrentRoute")
|
||||||
assert route is not None
|
assert route is not None
|
||||||
|
|
||||||
segs = list(Path(Paths.log_root()).glob(f"{route}--*"))
|
segs = list(Path(Paths.log_root()).glob(f"{route}--*"))
|
||||||
@@ -187,7 +180,7 @@ class TestOnroad:
|
|||||||
|
|
||||||
def test_manager_starting_time(self):
|
def test_manager_starting_time(self):
|
||||||
st = self.ts['managerState']['t'][0]
|
st = self.ts['managerState']['t'][0]
|
||||||
assert (st - self.manager_st) < 10, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg"
|
assert (st - self.manager_st) < 15.0, f"manager.py took {st - self.manager_st}s to publish the first 'managerState' msg"
|
||||||
|
|
||||||
def test_cloudlog_size(self):
|
def test_cloudlog_size(self):
|
||||||
msgs = self.msgs['logMessage']
|
msgs = self.msgs['logMessage']
|
||||||
@@ -214,7 +207,9 @@ class TestOnroad:
|
|||||||
result += "-------------- UI Draw Timing ------------------\n"
|
result += "-------------- UI Draw Timing ------------------\n"
|
||||||
result += "------------------------------------------------\n"
|
result += "------------------------------------------------\n"
|
||||||
|
|
||||||
ts = self.ts['uiDebug']['drawTimeMillis']
|
# other processes preempt ui while starting up
|
||||||
|
offset = int(20 * LOG_OFFSET)
|
||||||
|
ts = self.ts['uiDebug']['drawTimeMillis'][offset:]
|
||||||
result += f"min {min(ts):.2f}ms\n"
|
result += f"min {min(ts):.2f}ms\n"
|
||||||
result += f"max {max(ts):.2f}ms\n"
|
result += f"max {max(ts):.2f}ms\n"
|
||||||
result += f"std {np.std(ts):.2f}ms\n"
|
result += f"std {np.std(ts):.2f}ms\n"
|
||||||
@@ -223,7 +218,7 @@ class TestOnroad:
|
|||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
assert max(ts) < 250.
|
assert max(ts) < 250.
|
||||||
assert np.mean(ts) < 10.
|
assert np.mean(ts) < 20. # TODO: ~6-11ms, increase consistency
|
||||||
#self.assertLess(np.std(ts), 5.)
|
#self.assertLess(np.std(ts), 5.)
|
||||||
|
|
||||||
# some slow frames are expected since camerad/modeld can preempt ui
|
# some slow frames are expected since camerad/modeld can preempt ui
|
||||||
@@ -287,13 +282,17 @@ class TestOnroad:
|
|||||||
print("\n------------------------------------------------")
|
print("\n------------------------------------------------")
|
||||||
print("--------------- Memory Usage -------------------")
|
print("--------------- Memory Usage -------------------")
|
||||||
print("------------------------------------------------")
|
print("------------------------------------------------")
|
||||||
|
|
||||||
|
from openpilot.selfdrive.debug.mem_usage import print_report
|
||||||
|
print_report(self.msgs['procLog'], self.msgs['deviceState'])
|
||||||
|
|
||||||
offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET)
|
offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET)
|
||||||
mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]]
|
mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]]
|
||||||
print("Memory usage: ", mems)
|
print("MSGQ (/dev/shm/) usage: ", subprocess.check_output(["du", "-hs", "/dev/shm"]).split()[0].decode())
|
||||||
|
|
||||||
# check for big leaks. note that memory usage is
|
# check for big leaks. note that memory usage is
|
||||||
# expected to go up while the MSGQ buffers fill up
|
# expected to go up while the MSGQ buffers fill up
|
||||||
assert np.average(mems) <= 65, "Average memory usage above 65%"
|
assert np.average(mems) <= 80, "Average memory usage too high"
|
||||||
assert np.max(np.diff(mems)) <= 4, "Max memory increase too high"
|
assert np.max(np.diff(mems)) <= 4, "Max memory increase too high"
|
||||||
assert np.average(np.diff(mems)) <= 1, "Average memory increase too high"
|
assert np.average(np.diff(mems)) <= 1, "Average memory increase too high"
|
||||||
|
|
||||||
@@ -330,25 +329,28 @@ class TestOnroad:
|
|||||||
assert np.all(eof_sof_diff > 0)
|
assert np.all(eof_sof_diff > 0)
|
||||||
assert np.all(eof_sof_diff < 50*1e6)
|
assert np.all(eof_sof_diff < 50*1e6)
|
||||||
|
|
||||||
first_fid = {c: min(self.ts[c]['frameId']) for c in cams}
|
first_fid = {min(self.ts[c]['frameId']) for c in cams}
|
||||||
|
assert len(first_fid) == 1, "Cameras don't start on same frame ID"
|
||||||
if cam.endswith('CameraState'):
|
if cam.endswith('CameraState'):
|
||||||
# camerad guarantees that all cams start on frame ID 0
|
# camerad guarantees that all cams start on frame ID 0
|
||||||
# (note loggerd also needs to start up fast enough to catch it)
|
# (note loggerd also needs to start up fast enough to catch it)
|
||||||
assert set(first_fid.values()) == {0, }, "Cameras don't start on frame ID 0"
|
assert next(iter(first_fid)) < 100, "Cameras start on frame ID too high"
|
||||||
else:
|
|
||||||
# encoder guarantees all cams start on the same frame ID
|
|
||||||
assert len(set(first_fid.values())) == 1, "Cameras don't start on same frame ID"
|
|
||||||
|
|
||||||
# we don't do a full segment rotation, so these might not match exactly
|
# we don't do a full segment rotation, so these might not match exactly
|
||||||
last_fid = {c: max(self.ts[c]['frameId']) for c in cams}
|
last_fid = {max(self.ts[c]['frameId']) for c in cams}
|
||||||
assert max(last_fid.values()) - min(last_fid.values()) < 10
|
assert max(last_fid) - min(last_fid) < 10
|
||||||
|
|
||||||
start, end = min(first_fid.values()), min(last_fid.values())
|
start, end = min(first_fid), min(last_fid)
|
||||||
for i in range(end-start):
|
for i in range(end-start):
|
||||||
ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams}
|
# road and wide cameras (first two) should be synced within 2ms
|
||||||
|
ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams[:2]}
|
||||||
diff = (max(ts.values()) - min(ts.values()))
|
diff = (max(ts.values()) - min(ts.values()))
|
||||||
assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}"
|
assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}"
|
||||||
|
|
||||||
|
# driver camera should be staggered ~25ms from road camera
|
||||||
|
offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6
|
||||||
|
assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms"
|
||||||
|
|
||||||
def test_camera_encoder_matches(self, subtests):
|
def test_camera_encoder_matches(self, subtests):
|
||||||
# sanity check that the frame metadata is consistent with the encoded frames
|
# sanity check that the frame metadata is consistent with the encoded frames
|
||||||
pairs = [('roadCameraState', 'roadEncodeIdx'),
|
pairs = [('roadCameraState', 'roadEncodeIdx'),
|
||||||
@@ -391,8 +393,12 @@ class TestOnroad:
|
|||||||
result += "----------------- Model Timing -----------------\n"
|
result += "----------------- Model Timing -----------------\n"
|
||||||
result += "------------------------------------------------\n"
|
result += "------------------------------------------------\n"
|
||||||
cfgs = [
|
cfgs = [
|
||||||
("modelV2", 0.045, 0.035),
|
# since multiple processes use the GPU and can preempt each other,
|
||||||
("driverStateV2", 0.045, 0.035),
|
# these numbers are not fully self-contained.
|
||||||
|
("modelV2", 0.06, 0.040),
|
||||||
|
|
||||||
|
# can miss cycles here and there, just important the avg frequency is 20Hz
|
||||||
|
("driverStateV2", 0.3, 0.05),
|
||||||
]
|
]
|
||||||
for (s, instant_max, avg_max) in cfgs:
|
for (s, instant_max, avg_max) in cfgs:
|
||||||
ts = [getattr(m, s).modelExecutionTime for m in self.msgs[s]]
|
ts = [getattr(m, s).modelExecutionTime for m in self.msgs[s]]
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class TestUpdated:
|
|||||||
ret = None
|
ret = None
|
||||||
start_time = time.monotonic()
|
start_time = time.monotonic()
|
||||||
while ret is None:
|
while ret is None:
|
||||||
ret = self.params.get(key, encoding='utf8')
|
ret = self.params.get(key)
|
||||||
if time.monotonic() - start_time > timeout:
|
if time.monotonic() - start_time > timeout:
|
||||||
break
|
break
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
@@ -162,8 +162,7 @@ class TestUpdated:
|
|||||||
|
|
||||||
def _check_update_state(self, update_available):
|
def _check_update_state(self, update_available):
|
||||||
# make sure LastUpdateTime is recent
|
# make sure LastUpdateTime is recent
|
||||||
t = self._read_param("LastUpdateTime")
|
last_update_time = self._read_param("LastUpdateTime")
|
||||||
last_update_time = datetime.datetime.fromisoformat(t)
|
|
||||||
td = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - last_update_time
|
td = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - last_update_time
|
||||||
assert td.total_seconds() < 10
|
assert td.total_seconds() < 10
|
||||||
self.params.remove("LastUpdateTime")
|
self.params.remove("LastUpdateTime")
|
||||||
@@ -174,7 +173,7 @@ class TestUpdated:
|
|||||||
# check params
|
# check params
|
||||||
update = self._read_param("UpdateAvailable")
|
update = self._read_param("UpdateAvailable")
|
||||||
assert update == "1" == update_available, f"UpdateAvailable: {repr(update)}"
|
assert update == "1" == update_available, f"UpdateAvailable: {repr(update)}"
|
||||||
assert self._read_param("UpdateFailedCount") == "0"
|
assert self._read_param("UpdateFailedCount") == 0
|
||||||
|
|
||||||
# TODO: check that the finalized update actually matches remote
|
# TODO: check that the finalized update actually matches remote
|
||||||
# check the .overlay_init and .overlay_consistent flags
|
# check the .overlay_init and .overlay_consistent flags
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ SOURCES: list[AzureContainer] = [
|
|||||||
|
|
||||||
DEST = OpenpilotCIContainer
|
DEST = OpenpilotCIContainer
|
||||||
|
|
||||||
def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None:
|
def upload_route(path: str, exclude_patterns: Iterable[str] | None = None) -> None:
|
||||||
if exclude_patterns is None:
|
if exclude_patterns is None:
|
||||||
exclude_patterns = [r'dcamera\.hevc']
|
exclude_patterns = [r'dcamera\.hevc']
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
@@ -137,6 +139,12 @@ class HudRenderer(Widget):
|
|||||||
self._traffic_red_icon = gui_app.texture('images/traffic_red.png')
|
self._traffic_red_icon = gui_app.texture('images/traffic_red.png')
|
||||||
self._traffic_green_icon = gui_app.texture('images/traffic_green.png')
|
self._traffic_green_icon = gui_app.texture('images/traffic_green.png')
|
||||||
|
|
||||||
|
self._ic_turn_l = gui_app.texture('images/turn_l.png')
|
||||||
|
self._ic_turn_r = gui_app.texture('images/turn_r.png')
|
||||||
|
self._ic_lane_change_l = gui_app.texture('images/lane_change_l.png')
|
||||||
|
self._ic_lane_change_r = gui_app.texture('images/lane_change_r.png')
|
||||||
|
self._ic_turn_u = gui_app.texture('images/turn_u.png')
|
||||||
|
|
||||||
self._set_speed_override = SetSpeedOverride()
|
self._set_speed_override = SetSpeedOverride()
|
||||||
self._debug_speed_panel = False
|
self._debug_speed_panel = False
|
||||||
self._engaged = False
|
self._engaged = False
|
||||||
@@ -149,6 +157,7 @@ class HudRenderer(Widget):
|
|||||||
self._memory_usage = 0
|
self._memory_usage = 0
|
||||||
self._free_space = 0.0
|
self._free_space = 0.0
|
||||||
self._voltage = 0.0
|
self._voltage = 0.0
|
||||||
|
self._plot_renderer = None
|
||||||
|
|
||||||
def _update_state(self) -> None:
|
def _update_state(self) -> None:
|
||||||
"""Update HUD state based on car state and controls state."""
|
"""Update HUD state based on car state and controls state."""
|
||||||
@@ -201,6 +210,12 @@ class HudRenderer(Widget):
|
|||||||
button_y = rect.y + UI_CONFIG.border_size
|
button_y = rect.y + UI_CONFIG.border_size
|
||||||
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
|
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
|
||||||
|
|
||||||
|
if self._plot_renderer is None:
|
||||||
|
self._plot_renderer = PlotRenderer()
|
||||||
|
self._plot_renderer.draw(rect, self._font_display)
|
||||||
|
self._draw_date_time(rect)
|
||||||
|
self._draw_tpms_top_right(rect)
|
||||||
|
|
||||||
def user_interacting(self) -> bool:
|
def user_interacting(self) -> bool:
|
||||||
return self._exp_button.is_pressed
|
return self._exp_button.is_pressed
|
||||||
|
|
||||||
@@ -344,14 +359,6 @@ class HudRenderer(Widget):
|
|||||||
return gap
|
return gap
|
||||||
|
|
||||||
def _get_driving_mode_text_and_color(self) -> tuple[str, rl.Color]:
|
def _get_driving_mode_text_and_color(self) -> tuple[str, rl.Color]:
|
||||||
carState = ui_state.sm["carState"]
|
|
||||||
if carState.brakeHoldActive:
|
|
||||||
return tr("brake hold"), rl.Color(255, 0, 0, 230)
|
|
||||||
elif carState.softHoldActive:
|
|
||||||
return tr("soft hold"), rl.Color(255, 165, 0, 230)
|
|
||||||
elif carState.carrotCruise:
|
|
||||||
return tr("carrot"), rl.Color(0, 255, 0, 230)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mode_val = int(ui_state.sm["longitudinalPlan"].myDrivingMode)
|
mode_val = int(ui_state.sm["longitudinalPlan"].myDrivingMode)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -376,6 +383,7 @@ class HudRenderer(Widget):
|
|||||||
self._memory_usage = 0
|
self._memory_usage = 0
|
||||||
self._free_space = 0.0
|
self._free_space = 0.0
|
||||||
self._voltage = 0.0
|
self._voltage = 0.0
|
||||||
|
#self._plot_renderer = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
device_state = sm["deviceState"]
|
device_state = sm["deviceState"]
|
||||||
@@ -589,8 +597,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
mode_text, dx, dy - 2, 32, rl.WHITE,
|
mode_text, dx, dy - 2, 32, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -598,8 +606,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
"GPS", dx, dy - 45, 30, rl.GREEN,
|
"GPS", dx, dy - 45, 30, rl.GREEN,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -608,8 +616,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
str(gap), bx + 220, by + 77, 40, rl.WHITE,
|
str(gap), bx + 220, by + 77, 40, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -647,8 +655,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
gear, gx, gy + 5, 70, rl.WHITE,
|
gear, gx, gy + 5, 70, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -669,8 +677,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
"APN", dx, dy, 40, rl.WHITE,
|
"APN", dx, dy, 40, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
elif active_carrot >= 1:
|
elif active_carrot >= 1:
|
||||||
@@ -685,8 +693,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
"APM", dx, dy, 40, rl.WHITE,
|
"APM", dx, dy, 40, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -694,8 +702,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
"ROUTE", dx, dy - 45, 30, rl.WHITE,
|
"ROUTE", dx, dy - 45, 30, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -726,8 +734,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
label, dx, dy - 45, 30, rl.WHITE,
|
label, dx, dy - 45, 30, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -743,8 +751,8 @@ class HudRenderer(Widget):
|
|||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
str(disp_speed), dx, dy, 40, rl.WHITE,
|
str(disp_speed), dx, dy, 40, rl.WHITE,
|
||||||
font=self._font_display,
|
font=self._font_display,
|
||||||
border_width=0.0,
|
border_width=2.0,
|
||||||
shadow_offset=0.0,
|
shadow_offset=4.0,
|
||||||
align="center_bottom",
|
align="center_bottom",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -793,26 +801,357 @@ class HudRenderer(Widget):
|
|||||||
# CPU
|
# CPU
|
||||||
cpu_fill = rl.Color(255, 0, 0, 255) if (self._cpu_temp > 80 and self._blink_timer <= 8) else ok_color
|
cpu_fill = rl.Color(255, 0, 0, 255) if (self._cpu_temp > 80 and self._blink_timer <= 8) else ok_color
|
||||||
self._draw_round_box(dx - 65, dy - 38, 130, 90, cpu_fill, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
self._draw_round_box(dx - 65, dy - 38, 130, 90, cpu_fill, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
||||||
draw_text_ui_style("CPU", dx, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style("CPU", dx, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
draw_text_ui_style(f"{self._cpu_temp:.0f}°C", dx, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style(f"{self._cpu_temp:.0f}°C", dx, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
|
|
||||||
# MEM
|
# MEM
|
||||||
dx2 = dx + 150
|
dx2 = dx + 150
|
||||||
mem_fill = rl.Color(255, 0, 0, 255) if (self._memory_usage > 85 and self._blink_timer <= 8) else ok_color
|
mem_fill = rl.Color(255, 0, 0, 255) if (self._memory_usage > 85 and self._blink_timer <= 8) else ok_color
|
||||||
self._draw_round_box(dx2 - 65, dy - 38, 130, 90, mem_fill, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
self._draw_round_box(dx2 - 65, dy - 38, 130, 90, mem_fill, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
||||||
draw_text_ui_style("MEM", dx2, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style("MEM", dx2, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
draw_text_ui_style(f"{self._memory_usage}%", dx2, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style(f"{self._memory_usage}%", dx2, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
|
|
||||||
# DISK / VOLT
|
# DISK / VOLT
|
||||||
dx3 = dx2 + 150
|
dx3 = dx2 + 150
|
||||||
self._draw_round_box(dx3 - 65, dy - 38, 130, 90, ok_color, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
self._draw_round_box(dx3 - 65, dy - 38, 130, 90, ok_color, line_color=rl.WHITE, roundness=0.16, segments=8, line_thickness=2)
|
||||||
|
|
||||||
if self._disp_timer < 32:
|
if self._disp_timer < 32:
|
||||||
draw_text_ui_style("DISK", dx3, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style("DISK", dx3, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
draw_text_ui_style(f"{100 - self._free_space:.0f}%", dx3, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style(f"{100 - self._free_space:.0f}%", dx3, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
else:
|
else:
|
||||||
draw_text_ui_style("VOLT", dx3, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style("VOLT", dx3, dy - 5, 25, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
draw_text_ui_style(f"{self._voltage:.1f}V", dx3, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=0.0, shadow_offset=0.0, align="center_bottom")
|
draw_text_ui_style(f"{self._voltage:.1f}V", dx3, dy + 40, 40, rl.WHITE, font=self._font_display, border_width=1.0, shadow_offset=4.0, align="center_bottom")
|
||||||
|
|
||||||
|
def _draw_date_time(self, rect: rl.Rectangle) -> None:
|
||||||
|
show_datetime = ui_state.params.get_int("ShowDateTime")
|
||||||
|
if show_datetime <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
now = time.localtime()
|
||||||
|
weekdays_ko = ["일", "월", "화", "수", "목", "금", "토"]
|
||||||
|
|
||||||
|
x = int(rect.x + 170)
|
||||||
|
y = int(rect.y + 120)
|
||||||
|
|
||||||
|
if show_datetime in (1, 2):
|
||||||
|
draw_text_ui_style(
|
||||||
|
time.strftime("%H:%M", now), x, y, 100, rl.WHITE,
|
||||||
|
font=self._font_display,
|
||||||
|
border_width=3.0,
|
||||||
|
shadow_offset=8.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
|
||||||
|
if show_datetime in (1, 3):
|
||||||
|
weekday = weekdays_ko[(now.tm_wday + 1) % 7]
|
||||||
|
date_text = f"{time.strftime('%m-%d', now)}({weekday})"
|
||||||
|
draw_text_ui_style(
|
||||||
|
date_text, x, y + 70, 60, rl.WHITE,
|
||||||
|
font=self._font_display,
|
||||||
|
border_width=3.0,
|
||||||
|
shadow_offset=8.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_tpms_color(self, tpms: float) -> rl.Color:
|
||||||
|
if tpms < 5 or tpms > 60:
|
||||||
|
return rl.Color(255, 255, 255, 220)
|
||||||
|
if tpms < 31:
|
||||||
|
return rl.Color(255, 90, 90, 220)
|
||||||
|
return rl.Color(255, 255, 255, 220)
|
||||||
|
|
||||||
|
def _get_tpms_text(self, tpms: float) -> str:
|
||||||
|
if tpms < 5 or tpms > 60:
|
||||||
|
return ' -'
|
||||||
|
return f'{round(tpms):.0f}'
|
||||||
|
|
||||||
|
def _draw_tpms_top_right(self, rect: rl.Rectangle) -> None:
|
||||||
|
show_tpms = 1 #ui_state.params.get_int('ShowTpms')
|
||||||
|
if show_tpms not in (1, 3):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
tpms = ui_state.sm['carState'].tpms
|
||||||
|
fl = float(tpms.fl)
|
||||||
|
fr = float(tpms.fr)
|
||||||
|
rl_v = float(tpms.rl)
|
||||||
|
rr = float(tpms.rr)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
bx = rect.x + rect.width - 125
|
||||||
|
by = rect.y + 130
|
||||||
|
dw = 80
|
||||||
|
|
||||||
|
draw_text_ui_style(
|
||||||
|
self._get_tpms_text(fl), bx - dw, by - 55, 40, self._get_tpms_color(fl),
|
||||||
|
font=self._font_display, border_width=1.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
draw_text_ui_style(
|
||||||
|
self._get_tpms_text(fr), bx + dw, by - 55, 40, self._get_tpms_color(fr),
|
||||||
|
font=self._font_display, border_width=1.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
draw_text_ui_style(
|
||||||
|
self._get_tpms_text(rl_v), bx - dw, by + 70, 40, self._get_tpms_color(rl_v),
|
||||||
|
font=self._font_display, border_width=1.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
draw_text_ui_style(
|
||||||
|
self._get_tpms_text(rr), bx + dw, by + 70, 40, self._get_tpms_color(rr),
|
||||||
|
font=self._font_display, border_width=1.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_turn_info_hud_data(self) -> dict:
|
||||||
|
try:
|
||||||
|
cm = ui_state.sm["carrotMan"]
|
||||||
|
except Exception:
|
||||||
|
return {
|
||||||
|
"active_carrot": 0,
|
||||||
|
"x_turn_info": 0,
|
||||||
|
"x_dist_to_turn": 0,
|
||||||
|
"n_go_pos_dist": 0,
|
||||||
|
"n_go_pos_time": 0,
|
||||||
|
"atc_type": "",
|
||||||
|
"sdi_descr": "",
|
||||||
|
"road_name": "",
|
||||||
|
"tbt_main_text": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
active_carrot = int(cm.activeCarrot)
|
||||||
|
except Exception:
|
||||||
|
active_carrot = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
x_turn_info = int(cm.xTurnInfo)
|
||||||
|
except Exception:
|
||||||
|
x_turn_info = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
x_dist_to_turn = int(cm.xDistToTurn)
|
||||||
|
except Exception:
|
||||||
|
x_dist_to_turn = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
n_go_pos_dist = int(cm.nGoPosDist)
|
||||||
|
except Exception:
|
||||||
|
n_go_pos_dist = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
n_go_pos_time = int(cm.nGoPosTime)
|
||||||
|
except Exception:
|
||||||
|
n_go_pos_time = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
atc_type = str(cm.atcType or "")
|
||||||
|
except Exception:
|
||||||
|
atc_type = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
sdi_descr = str(cm.szSdiDescr or "")
|
||||||
|
except Exception:
|
||||||
|
sdi_descr = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
road_name = str(cm.szPosRoadName or "")
|
||||||
|
except Exception:
|
||||||
|
road_name = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
tbt_main_text = str(cm.szTBTMainText or "")
|
||||||
|
except Exception:
|
||||||
|
tbt_main_text = ""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"active_carrot": active_carrot,
|
||||||
|
"x_turn_info": x_turn_info,
|
||||||
|
"x_dist_to_turn": x_dist_to_turn,
|
||||||
|
"n_go_pos_dist": n_go_pos_dist,
|
||||||
|
"n_go_pos_time": n_go_pos_time,
|
||||||
|
"atc_type": atc_type,
|
||||||
|
"sdi_descr": sdi_descr,
|
||||||
|
"road_name": road_name,
|
||||||
|
"tbt_main_text": tbt_main_text,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _format_turn_distance_text(self, dist_m: int) -> str:
|
||||||
|
if dist_m <= 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if ui_state.is_metric:
|
||||||
|
if dist_m < 1000:
|
||||||
|
return f"{dist_m} m"
|
||||||
|
return f"{dist_m / 1000.0:.1f} km"
|
||||||
|
else:
|
||||||
|
if dist_m < 1609:
|
||||||
|
return f"{int(dist_m * 3.28084)} ft"
|
||||||
|
return f"{dist_m / 1609.344:.1f} mi"
|
||||||
|
|
||||||
|
def _format_eta_text(self, remain_sec: int) -> str:
|
||||||
|
if remain_sec <= 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
eta_tm = time.localtime(time.time() + remain_sec)
|
||||||
|
remain_min = remain_sec / 60.0
|
||||||
|
return f"도착: {remain_min:.1f}분({eta_tm.tm_hour:02d}:{eta_tm.tm_min:02d})"
|
||||||
|
|
||||||
|
def _format_go_pos_distance_text(self, dist_m: int) -> str:
|
||||||
|
if dist_m <= 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if ui_state.is_metric:
|
||||||
|
return f"{dist_m / 1000.0:.1f}km"
|
||||||
|
else:
|
||||||
|
return f"{dist_m / 1000.0 * KM_TO_MILE:.1f}mile"
|
||||||
|
|
||||||
|
def _draw_text_left_bottom(self, text: str, x: float, y: float, size: int, color, font=None, border_width: float = 2.0, shadow_offset: float = 4.0):
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
|
||||||
|
draw_text_ui_style(
|
||||||
|
text, x, y, size, color,
|
||||||
|
font=font or self._font_display,
|
||||||
|
border_width=border_width,
|
||||||
|
shadow_offset=shadow_offset,
|
||||||
|
align="left_bottom",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _draw_turn_icon(self, turn_info: int, bx: int, by: int, icon_size: int = 140):
|
||||||
|
if turn_info == 1:
|
||||||
|
self._draw_texture_rect(self._ic_turn_l, bx - icon_size / 2, by - icon_size / 2, icon_size, icon_size)
|
||||||
|
elif turn_info == 2:
|
||||||
|
self._draw_texture_rect(self._ic_turn_r, bx - icon_size / 2, by - icon_size / 2, icon_size, icon_size)
|
||||||
|
elif turn_info == 3:
|
||||||
|
self._draw_texture_rect(self._ic_lane_change_l, bx - icon_size / 2, by - icon_size / 2, icon_size, icon_size)
|
||||||
|
elif turn_info == 4:
|
||||||
|
self._draw_texture_rect(self._ic_lane_change_r, bx - icon_size / 2, by - icon_size / 2, icon_size, icon_size)
|
||||||
|
elif turn_info == 7:
|
||||||
|
self._draw_texture_rect(self._ic_turn_u, bx - icon_size / 2, by - icon_size / 2, icon_size, icon_size)
|
||||||
|
elif turn_info == 6:
|
||||||
|
draw_text_ui_style(
|
||||||
|
"TG", bx, by + 20, 35, rl.WHITE,
|
||||||
|
font=self._font_display,
|
||||||
|
border_width=2.0,
|
||||||
|
shadow_offset=4.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
elif turn_info == 8:
|
||||||
|
draw_text_ui_style(
|
||||||
|
"목적지", bx, by + 20, 35, rl.WHITE,
|
||||||
|
font=self._font_display,
|
||||||
|
border_width=2.0,
|
||||||
|
shadow_offset=4.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
draw_text_ui_style(
|
||||||
|
f"감속:{turn_info}", bx, by + 20, 35, rl.WHITE,
|
||||||
|
font=self._font_display,
|
||||||
|
border_width=2.0,
|
||||||
|
shadow_offset=4.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _draw_turn_info_hud(self, rect: rl.Rectangle):
|
||||||
|
if rect.width < 1200:
|
||||||
|
return
|
||||||
|
|
||||||
|
info = self._get_turn_info_hud_data()
|
||||||
|
n_go_pos_dist = info["n_go_pos_dist"]
|
||||||
|
n_go_pos_time = info["n_go_pos_time"]
|
||||||
|
|
||||||
|
if not (n_go_pos_dist > 0 and n_go_pos_time > 0):
|
||||||
|
return
|
||||||
|
|
||||||
|
tbt_x = int(rect.x + rect.width - 800)
|
||||||
|
tbt_y = int(rect.y + rect.height - 250)
|
||||||
|
|
||||||
|
self._draw_round_box(
|
||||||
|
tbt_x,
|
||||||
|
tbt_y - 60,
|
||||||
|
790,
|
||||||
|
300,
|
||||||
|
rl.Color(0, 0, 0, 120),
|
||||||
|
line_color=rl.WHITE,
|
||||||
|
roundness=30.0 / 300.0,
|
||||||
|
segments=12,
|
||||||
|
line_thickness=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if info["tbt_main_text"]:
|
||||||
|
self._draw_text_left_bottom(
|
||||||
|
info["tbt_main_text"], tbt_x + 20, tbt_y - 15, 40, rl.WHITE,
|
||||||
|
font=self._font_bold, border_width=2.0, shadow_offset=4.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
x_turn_info = info["x_turn_info"]
|
||||||
|
x_dist_to_turn = info["x_dist_to_turn"]
|
||||||
|
|
||||||
|
if x_turn_info > 0:
|
||||||
|
bx = tbt_x + 100
|
||||||
|
by = tbt_y + 85
|
||||||
|
|
||||||
|
if info["atc_type"]:
|
||||||
|
fill_color = rl.Color(0, 255, 0, 100) if "prepare" in info["atc_type"] else rl.GREEN
|
||||||
|
self._draw_round_box(
|
||||||
|
bx - 80, by - 90, 160, 230,
|
||||||
|
fill_color,
|
||||||
|
line_color=rl.BLACK,
|
||||||
|
roundness=15.0 / 230.0,
|
||||||
|
segments=8,
|
||||||
|
line_thickness=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._draw_turn_icon(x_turn_info, bx, by, 140)
|
||||||
|
|
||||||
|
dist_text = self._format_turn_distance_text(x_dist_to_turn)
|
||||||
|
if dist_text:
|
||||||
|
draw_text_ui_style(
|
||||||
|
dist_text,
|
||||||
|
bx, by + 120, 40, rl.WHITE,
|
||||||
|
font=self._font_bold,
|
||||||
|
border_width=2.0,
|
||||||
|
shadow_offset=4.0,
|
||||||
|
align="center_bottom",
|
||||||
|
)
|
||||||
|
|
||||||
|
if info["sdi_descr"]:
|
||||||
|
label_x = tbt_x + 200
|
||||||
|
label_y = tbt_y + 200
|
||||||
|
size = measure_text_cached(self._font_bold, info["sdi_descr"], 40)
|
||||||
|
box_h = max(48, int(size.y + 13))
|
||||||
|
self._draw_round_box(
|
||||||
|
label_x - 10,
|
||||||
|
label_y - int(size.y) - 2,
|
||||||
|
int(size.x) + 20,
|
||||||
|
box_h,
|
||||||
|
rl.GREEN,
|
||||||
|
roundness=10.0 / box_h,
|
||||||
|
segments=8,
|
||||||
|
line_thickness=0,
|
||||||
|
)
|
||||||
|
self._draw_text_left_bottom(
|
||||||
|
info["sdi_descr"], label_x, label_y, 40, rl.WHITE,
|
||||||
|
font=self._font_bold, border_width=1.5, shadow_offset=3.0,
|
||||||
|
)
|
||||||
|
elif info["road_name"]:
|
||||||
|
self._draw_text_left_bottom(
|
||||||
|
info["road_name"], tbt_x + 200, tbt_y + 200, 40, rl.WHITE,
|
||||||
|
font=self._font_bold, border_width=1.5, shadow_offset=3.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
eta_text = self._format_eta_text(n_go_pos_time)
|
||||||
|
if eta_text:
|
||||||
|
self._draw_text_left_bottom(
|
||||||
|
eta_text, tbt_x + 190, tbt_y + 80, 50, rl.WHITE,
|
||||||
|
font=self._font_bold, border_width=2.0, shadow_offset=4.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
go_dist_text = self._format_go_pos_distance_text(n_go_pos_dist)
|
||||||
|
if go_dist_text:
|
||||||
|
self._draw_text_left_bottom(
|
||||||
|
go_dist_text, tbt_x + 310, tbt_y + 130, 50, rl.WHITE,
|
||||||
|
font=self._font_bold, border_width=2.0, shadow_offset=4.0,
|
||||||
|
)
|
||||||
|
|
||||||
def _draw_set_speed_carrot(self, rect: rl.Rectangle) -> None:
|
def _draw_set_speed_carrot(self, rect: rl.Rectangle) -> None:
|
||||||
self._blink_timer = (self._blink_timer + 1) % 16
|
self._blink_timer = (self._blink_timer + 1) % 16
|
||||||
@@ -828,4 +1167,242 @@ class HudRenderer(Widget):
|
|||||||
self._draw_carrot_lower_status(bx, by)
|
self._draw_carrot_lower_status(bx, by)
|
||||||
self._draw_carrot_speed_limit_box(bx, by)
|
self._draw_carrot_speed_limit_box(bx, by)
|
||||||
self._draw_carrot_device_state(bx, by)
|
self._draw_carrot_device_state(bx, by)
|
||||||
|
self._draw_turn_info_hud(rect)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PlotRenderer:
|
||||||
|
PLOT_MAX = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._plot_size = 0
|
||||||
|
self._plot_index = 0
|
||||||
|
self._plot_queue = [[0.0] * self.PLOT_MAX for _ in range(3)]
|
||||||
|
self._plot_min = 0.0
|
||||||
|
self._plot_max = 0.0
|
||||||
|
self._plot_x = 350.0
|
||||||
|
self._plot_width = 1000.0
|
||||||
|
self._plot_y = 40.0
|
||||||
|
self._plot_height = 300.0
|
||||||
|
self._plot_dx = 2.0
|
||||||
|
self._show_plot_mode_prev = -1
|
||||||
|
|
||||||
|
def _clear(self):
|
||||||
|
self._plot_size = 0
|
||||||
|
self._plot_index = 0
|
||||||
|
self._plot_min = 0.0
|
||||||
|
self._plot_max = 0.0
|
||||||
|
self._plot_queue = [[0.0] * self.PLOT_MAX for _ in range(3)]
|
||||||
|
|
||||||
|
def _make_plot_data(self, sm, show_plot_mode: int):
|
||||||
|
car_state = sm['carState']
|
||||||
|
lp = sm['longitudinalPlan']
|
||||||
|
car_control = sm['carControl']
|
||||||
|
controls_state = sm['controlsState']
|
||||||
|
|
||||||
|
a_ego = float(car_state.aEgo)
|
||||||
|
v_ego = float(car_state.vEgo)
|
||||||
|
|
||||||
|
accel = 0.0
|
||||||
|
try:
|
||||||
|
accel = float(lp.accels[0])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
speeds_0 = 0.0
|
||||||
|
try:
|
||||||
|
speeds_0 = float(lp.speeds[0])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
accel_out = 0.0
|
||||||
|
try:
|
||||||
|
accel_out = float(car_control.actuators.accel)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if show_plot_mode in (0, 1):
|
||||||
|
return [a_ego, accel, accel_out], '1.Accel (Y:a_ego, G:a_target, O:a_out)'
|
||||||
|
|
||||||
|
if show_plot_mode == 2:
|
||||||
|
return [speeds_0, v_ego, a_ego], '2.Speed/Accel(Y:speed_0, G:v_ego, O:a_ego)'
|
||||||
|
|
||||||
|
if show_plot_mode == 3:
|
||||||
|
pos_32 = 0.0
|
||||||
|
vel_32 = 0.0
|
||||||
|
vel_0 = 0.0
|
||||||
|
try:
|
||||||
|
pos_32 = float(sm['modelV2'].position.x[32])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
vel_32 = float(sm['modelV2'].velocity.x[32])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
vel_0 = float(sm['modelV2'].velocity.x[0])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [pos_32, vel_32, vel_0], '3.Model(Y:pos_32, G:vel_32, O:vel_0)'
|
||||||
|
|
||||||
|
if show_plot_mode == 4:
|
||||||
|
a_lead_k = 0.0
|
||||||
|
v_rel = 0.0
|
||||||
|
try:
|
||||||
|
a_lead_k = float(sm['radarState'].leadOne.aLeadK)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
v_rel = float(sm['radarState'].leadOne.vRel)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [accel, a_lead_k, v_rel], '4.Lead(Y:accel, G:a_lead, O:v_rel)'
|
||||||
|
|
||||||
|
if show_plot_mode == 5:
|
||||||
|
a_lead = 0.0
|
||||||
|
j_lead = 0.0
|
||||||
|
try:
|
||||||
|
a_lead = float(sm['radarState'].leadOne.aLead)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
j_lead = float(sm['radarState'].leadOne.jLead)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [a_ego, a_lead, j_lead], '5.Lead(Y:a_ego, G:a_lead, O:j_lead)'
|
||||||
|
|
||||||
|
if show_plot_mode == 6:
|
||||||
|
actual_lat_accel = 0.0
|
||||||
|
desired_lat_accel = 0.0
|
||||||
|
output = 0.0
|
||||||
|
try:
|
||||||
|
actual_lat_accel = float(controls_state.lateralControlState.torqueState.actualLateralAccel) * 10.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
desired_lat_accel = float(controls_state.lateralControlState.torqueState.desiredLateralAccel) * 10.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
output = float(controls_state.lateralControlState.torqueState.output) * 10.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [actual_lat_accel, desired_lat_accel, output], '6.Steer(Y:actual, G:desire, O:output)'
|
||||||
|
|
||||||
|
if show_plot_mode == 7:
|
||||||
|
actual_angle = float(car_state.steeringAngleDeg)
|
||||||
|
target_angle = 0.0
|
||||||
|
angle_offset = 0.0
|
||||||
|
try:
|
||||||
|
target_angle = float(car_control.actuators.steeringAngleDeg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
angle_offset = float(sm['liveParameters'].angleOffsetDeg) * 10.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [actual_angle, target_angle, angle_offset], '7.SteerA (Y:Actual, G:Target, O:Offset*10)'
|
||||||
|
|
||||||
|
if show_plot_mode == 8:
|
||||||
|
curvature = 0.0
|
||||||
|
try:
|
||||||
|
curvature = float(car_control.actuators.curvature) * 10000.0
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return [curvature, curvature, curvature], '8.Curvature (*10000)'
|
||||||
|
|
||||||
|
return [0.0, 0.0, 0.0], 'no data'
|
||||||
|
|
||||||
|
def _update_plot_queue(self, plot_data):
|
||||||
|
self._plot_index = (self._plot_index + 1) % self.PLOT_MAX
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
self._plot_queue[i][self._plot_index] = float(plot_data[i])
|
||||||
|
|
||||||
|
if self._plot_size < self.PLOT_MAX:
|
||||||
|
self._plot_size += 1
|
||||||
|
|
||||||
|
self._plot_min = float('inf')
|
||||||
|
self._plot_max = float('-inf')
|
||||||
|
for i in range(3):
|
||||||
|
values = self._plot_queue[i][:self._plot_size] if self._plot_size < self.PLOT_MAX else self._plot_queue[i]
|
||||||
|
self._plot_min = min(self._plot_min, min(values))
|
||||||
|
self._plot_max = max(self._plot_max, max(values))
|
||||||
|
|
||||||
|
if self._plot_min == float('inf'):
|
||||||
|
self._plot_min = -2.0
|
||||||
|
if self._plot_max == float('-inf'):
|
||||||
|
self._plot_max = 2.0
|
||||||
|
|
||||||
|
if self._plot_min > -2.0:
|
||||||
|
self._plot_min = -2.0
|
||||||
|
if self._plot_max < 2.0:
|
||||||
|
self._plot_max = 2.0
|
||||||
|
|
||||||
|
def _draw_plotting(self, index: int, x_base: float, y_base: float, color, font):
|
||||||
|
if self._plot_size <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
plot_range = self._plot_max - self._plot_min
|
||||||
|
plot_ratio = self._plot_height if plot_range < 1.0 else (self._plot_height / plot_range)
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
latest_x = None
|
||||||
|
latest_y = None
|
||||||
|
latest_value = 0.0
|
||||||
|
|
||||||
|
for i in range(self._plot_size):
|
||||||
|
data = self._plot_queue[index][(self._plot_index - i + self.PLOT_MAX) % self.PLOT_MAX]
|
||||||
|
plot_y = y_base + self._plot_height - (data - self._plot_min) * plot_ratio
|
||||||
|
plot_x = x_base + (self._plot_size - i) * self._plot_dx
|
||||||
|
|
||||||
|
pt = rl.Vector2(plot_x, plot_y)
|
||||||
|
if prev is not None:
|
||||||
|
rl.draw_line_ex(prev, pt, 3.0, color)
|
||||||
|
else:
|
||||||
|
latest_x = plot_x
|
||||||
|
latest_y = plot_y
|
||||||
|
latest_value = data
|
||||||
|
prev = pt
|
||||||
|
|
||||||
|
if latest_x is not None and latest_y is not None:
|
||||||
|
draw_text_ui_style(
|
||||||
|
f'{latest_value:.2f}', latest_x + 50, latest_y + (40 if index > 0 else 0), 40, color,
|
||||||
|
font=font, border_width=2.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw(self, rect: rl.Rectangle, font) -> None:
|
||||||
|
show_plot_mode = ui_state.params.get_int('ShowPlotMode')
|
||||||
|
if show_plot_mode == 0:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if not ui_state.sm.alive['carState'] or not ui_state.sm.alive['longitudinalPlan']:
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
if show_plot_mode != self._show_plot_mode_prev:
|
||||||
|
self._clear()
|
||||||
|
self._show_plot_mode_prev = show_plot_mode
|
||||||
|
|
||||||
|
try:
|
||||||
|
plot_data, title = self._make_plot_data(ui_state.sm, show_plot_mode)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._update_plot_queue(plot_data)
|
||||||
|
|
||||||
|
if rect.width < 1200:
|
||||||
|
return
|
||||||
|
|
||||||
|
x_base = rect.x + self._plot_x
|
||||||
|
y_base = rect.y + self._plot_y
|
||||||
|
colors = [rl.YELLOW, rl.GREEN, rl.Color(255, 165, 0, 255)]
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
self._draw_plotting(i, x_base, y_base, colors[i], font)
|
||||||
|
|
||||||
|
draw_text_ui_style(
|
||||||
|
title, x_base + 400, y_base - 20, 25, rl.WHITE,
|
||||||
|
font=font, border_width=2.0, shadow_offset=4.0, align='center_bottom',
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import math
|
|||||||
import colorsys
|
import colorsys
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
from cereal import messaging, car
|
from cereal import messaging, car, log
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
@@ -17,6 +17,8 @@ CLIP_MARGIN = 500
|
|||||||
MIN_DRAW_DISTANCE = 10.0
|
MIN_DRAW_DISTANCE = 10.0
|
||||||
MAX_DRAW_DISTANCE = 100.0
|
MAX_DRAW_DISTANCE = 100.0
|
||||||
|
|
||||||
|
LaneChangeState = log.LaneChangeState
|
||||||
|
|
||||||
THROTTLE_COLORS = [
|
THROTTLE_COLORS = [
|
||||||
rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4)
|
rl.Color(13, 248, 122, 102), # HSLF(148/360, 0.94, 0.51, 0.4)
|
||||||
rl.Color(114, 255, 92, 89), # HSLF(112/360, 1.0, 0.68, 0.35)
|
rl.Color(114, 255, 92, 89), # HSLF(112/360, 1.0, 0.68, 0.35)
|
||||||
@@ -149,6 +151,7 @@ class ModelRenderer(Widget):
|
|||||||
# self._draw_lead_indicator()
|
# self._draw_lead_indicator()
|
||||||
self._draw_path_carrot(sm)
|
self._draw_path_carrot(sm)
|
||||||
self._draw_lane_lines_carrot(sm)
|
self._draw_lane_lines_carrot(sm)
|
||||||
|
self._draw_blind_spot_carrot(sm)
|
||||||
self._draw_radar_info_carrot(sm)
|
self._draw_radar_info_carrot(sm)
|
||||||
|
|
||||||
def _update_raw_points(self, model):
|
def _update_raw_points(self, model):
|
||||||
@@ -520,6 +523,12 @@ class ModelRenderer(Widget):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
self._carrot_lane_barrier_vertices = [
|
||||||
|
np.empty((0, 2), dtype=np.float32),
|
||||||
|
np.empty((0, 2), dtype=np.float32),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _refresh_carrot_params(self):
|
def _refresh_carrot_params(self):
|
||||||
self._carrot_show_lane_info = ui_state.params.get_int("ShowLaneInfo")
|
self._carrot_show_lane_info = ui_state.params.get_int("ShowLaneInfo")
|
||||||
self._carrot_show_radar_info = ui_state.params.get_int("ShowRadarInfo")
|
self._carrot_show_radar_info = ui_state.params.get_int("ShowRadarInfo")
|
||||||
@@ -642,10 +651,31 @@ class ModelRenderer(Widget):
|
|||||||
return tris
|
return tris
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_quad_fill_carrot(self, p0, p1, p2, p3, color: rl.Color):
|
||||||
|
pts = np.array([p0, p1, p2, p3], dtype=np.float32)
|
||||||
|
draw_polygon(self._rect, pts, color)
|
||||||
|
|
||||||
|
def _draw_two_quads_from_6pts_carrot(self, x, y, fill_color: rl.Color, brake_valid: bool, color_idx: int):
|
||||||
|
pts = np.array(list(zip(x, y)), dtype=np.float32)
|
||||||
|
if pts.shape[0] != 6:
|
||||||
|
return
|
||||||
|
|
||||||
|
left_pts = np.array([pts[0], pts[1], pts[2], pts[5]], dtype=np.float32)
|
||||||
|
right_pts = np.array([pts[5], pts[2], pts[3], pts[4]], dtype=np.float32)
|
||||||
|
|
||||||
|
draw_polygon(self._rect, left_pts, fill_color)
|
||||||
|
draw_polygon(self._rect, right_pts, fill_color)
|
||||||
|
|
||||||
|
if color_idx >= 10 or brake_valid:
|
||||||
|
self._draw_polygon_outline_carrot(
|
||||||
|
pts,
|
||||||
|
rl.Color(255, 0, 0, 255) if brake_valid else rl.Color(255, 255, 255, 255),
|
||||||
|
2.0,
|
||||||
|
)
|
||||||
|
|
||||||
def _draw_polygon_from_xy_carrot(self, xs, ys, fill_color: rl.Color, brake_valid: bool, color_idx: int):
|
def _draw_polygon_from_xy_carrot(self, xs, ys, fill_color: rl.Color, brake_valid: bool, color_idx: int):
|
||||||
pts = np.array(list(zip(xs, ys)), dtype=np.float32)
|
pts = np.array(list(zip(xs, ys)), dtype=np.float32)
|
||||||
|
|
||||||
# 연속 중복점 제거
|
|
||||||
if pts.shape[0] >= 2:
|
if pts.shape[0] >= 2:
|
||||||
keep = [0]
|
keep = [0]
|
||||||
for i in range(1, pts.shape[0]):
|
for i in range(1, pts.shape[0]):
|
||||||
@@ -653,7 +683,6 @@ class ModelRenderer(Widget):
|
|||||||
keep.append(i)
|
keep.append(i)
|
||||||
pts = pts[keep]
|
pts = pts[keep]
|
||||||
|
|
||||||
# 마지막 점이 첫 점과 거의 같으면 제거
|
|
||||||
if pts.shape[0] >= 3:
|
if pts.shape[0] >= 3:
|
||||||
if abs(float(pts[0][0] - pts[-1][0])) < 1e-3 and abs(float(pts[0][1] - pts[-1][1])) < 1e-3:
|
if abs(float(pts[0][0] - pts[-1][0])) < 1e-3 and abs(float(pts[0][1] - pts[-1][1])) < 1e-3:
|
||||||
pts = pts[:-1]
|
pts = pts[:-1]
|
||||||
@@ -661,19 +690,7 @@ class ModelRenderer(Widget):
|
|||||||
if pts.shape[0] < 3:
|
if pts.shape[0] < 3:
|
||||||
return
|
return
|
||||||
|
|
||||||
# shader_polygon.draw_polygon은 일부 concave/복잡한 도형에서 채움이 깨질 수 있어서
|
draw_polygon(self._rect, pts, fill_color)
|
||||||
# carrot path용은 ear clipping으로 삼각형 분할 후 직접 채움
|
|
||||||
tris = self._triangulate_polygon_carrot(pts)
|
|
||||||
if tris:
|
|
||||||
for a, b, c in tris:
|
|
||||||
rl.draw_triangle(
|
|
||||||
rl.Vector2(float(a[0]), float(a[1])),
|
|
||||||
rl.Vector2(float(b[0]), float(b[1])),
|
|
||||||
rl.Vector2(float(c[0]), float(c[1])),
|
|
||||||
fill_color,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
draw_polygon(self._rect, pts, fill_color)
|
|
||||||
|
|
||||||
if color_idx >= 10 or brake_valid:
|
if color_idx >= 10 or brake_valid:
|
||||||
self._draw_polygon_outline_carrot(
|
self._draw_polygon_outline_carrot(
|
||||||
@@ -701,7 +718,7 @@ class ModelRenderer(Widget):
|
|||||||
def _draw_text_box_carrot(self, x: int, y: int, text: str, font_size: int, box_color: rl.Color):
|
def _draw_text_box_carrot(self, x: int, y: int, text: str, font_size: int, box_color: rl.Color):
|
||||||
w = max(40, int(len(text) * font_size * 0.8))
|
w = max(40, int(len(text) * font_size * 0.8))
|
||||||
h = 42
|
h = 42
|
||||||
self._draw_rect_fill_outline_carrot(x - w / 2, y - 35, w, h, box_color, box_color, 0.0)
|
self._draw_rect_fill_outline_carrot(x - w / 2, y - 22, w, h, box_color, box_color, 0.0)
|
||||||
draw_text_ui_style(
|
draw_text_ui_style(
|
||||||
text,
|
text,
|
||||||
x,
|
x,
|
||||||
@@ -963,6 +980,133 @@ class ModelRenderer(Widget):
|
|||||||
draw_polygon(self._rect, road_vertices[i], color)
|
draw_polygon(self._rect, road_vertices[i], color)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _update_blind_spot_barriers_carrot(self, sm):
|
||||||
|
if not sm.valid['modelV2']:
|
||||||
|
self._carrot_lane_barrier_vertices[0] = np.empty((0, 2), dtype=np.float32)
|
||||||
|
self._carrot_lane_barrier_vertices[1] = np.empty((0, 2), dtype=np.float32)
|
||||||
|
return
|
||||||
|
|
||||||
|
model = sm['modelV2']
|
||||||
|
if len(model.position.x) == 0:
|
||||||
|
self._carrot_lane_barrier_vertices[0] = np.empty((0, 2), dtype=np.float32)
|
||||||
|
self._carrot_lane_barrier_vertices[1] = np.empty((0, 2), dtype=np.float32)
|
||||||
|
return
|
||||||
|
|
||||||
|
model_position = np.array([model.position.x, model.position.y, model.position.z], dtype=np.float32).T
|
||||||
|
max_idx_barrier = self._get_path_length_idx(model_position[:, 0], 40.0)
|
||||||
|
|
||||||
|
self._carrot_lane_barrier_vertices[0] = self._build_path_polygon_update_line_data2_carrot(
|
||||||
|
model_position,
|
||||||
|
1.2 - 0.05,
|
||||||
|
1.2 - 0.6,
|
||||||
|
1.2 - 0.6,
|
||||||
|
max_idx_barrier,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
self._carrot_lane_barrier_vertices[0][:, 0] = self._carrot_lane_barrier_vertices[0][:, 0]
|
||||||
|
|
||||||
|
self._carrot_lane_barrier_vertices[1] = self._build_path_polygon_update_line_data2_carrot(
|
||||||
|
model_position,
|
||||||
|
1.2 - 0.05,
|
||||||
|
1.2 - 0.6,
|
||||||
|
1.2 - 0.6,
|
||||||
|
max_idx_barrier,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shift left/right from vehicle center, matching carrot.cc offset usage
|
||||||
|
if self._carrot_lane_barrier_vertices[0].size != 0:
|
||||||
|
left_points = []
|
||||||
|
right_points = []
|
||||||
|
for i in range(0, max_idx_barrier + 1):
|
||||||
|
if model_position[i, 0] < 0:
|
||||||
|
continue
|
||||||
|
z_off = self._carrot_interp(float(model_position[i, 0]), [0.0, 100.0], [1.2 - 0.6, 1.2 - 0.6])
|
||||||
|
y_off = self._carrot_interp(z_off, [-3.0, 0.0, 3.0], [1.5, 0.5, 1.5]) * (1.2 - 0.05)
|
||||||
|
left = self._map_to_screen(model_position[i, 0], model_position[i, 1] - y_off - 1.7, model_position[i, 2] + z_off)
|
||||||
|
right = self._map_to_screen(model_position[i, 0], model_position[i, 1] + y_off - 1.7, model_position[i, 2] + z_off)
|
||||||
|
if left is not None and right is not None:
|
||||||
|
if len(left_points) > 0 and left[1] > left_points[-1][1]:
|
||||||
|
continue
|
||||||
|
left_points.append(left)
|
||||||
|
right_points.insert(0, right)
|
||||||
|
self._carrot_lane_barrier_vertices[0] = np.array(left_points + right_points, dtype=np.float32) if len(left_points) > 0 else np.empty((0, 2), dtype=np.float32)
|
||||||
|
|
||||||
|
if self._carrot_lane_barrier_vertices[1].size != 0:
|
||||||
|
left_points = []
|
||||||
|
right_points = []
|
||||||
|
for i in range(0, max_idx_barrier + 1):
|
||||||
|
if model_position[i, 0] < 0:
|
||||||
|
continue
|
||||||
|
z_off = self._carrot_interp(float(model_position[i, 0]), [0.0, 100.0], [1.2 - 0.6, 1.2 - 0.6])
|
||||||
|
y_off = self._carrot_interp(z_off, [-3.0, 0.0, 3.0], [1.5, 0.5, 1.5]) * (1.2 - 0.05)
|
||||||
|
left = self._map_to_screen(model_position[i, 0], model_position[i, 1] - y_off + 1.7, model_position[i, 2] + z_off)
|
||||||
|
right = self._map_to_screen(model_position[i, 0], model_position[i, 1] + y_off + 1.7, model_position[i, 2] + z_off)
|
||||||
|
if left is not None and right is not None:
|
||||||
|
if len(left_points) > 0 and left[1] > left_points[-1][1]:
|
||||||
|
continue
|
||||||
|
left_points.append(left)
|
||||||
|
right_points.insert(0, right)
|
||||||
|
self._carrot_lane_barrier_vertices[1] = np.array(left_points + right_points, dtype=np.float32) if len(left_points) > 0 else np.empty((0, 2), dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_blind_spot_segments_carrot(self, points: np.ndarray, color: rl.Color):
|
||||||
|
if points.size == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
count = points.shape[0]
|
||||||
|
half = count // 2
|
||||||
|
if half < 3:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(0, half - 2, 2):
|
||||||
|
p0 = points[i + 0]
|
||||||
|
p1 = points[i + 1]
|
||||||
|
p2 = points[count - i - 3]
|
||||||
|
p3 = points[count - i - 2]
|
||||||
|
self._draw_quad_fill_carrot(p0, p1, p2, p3, color)
|
||||||
|
self._draw_line_segment_carrot(p0, p1, color, 3.0)
|
||||||
|
self._draw_line_segment_carrot(p1, p2, color, 3.0)
|
||||||
|
self._draw_line_segment_carrot(p2, p3, color, 3.0)
|
||||||
|
self._draw_line_segment_carrot(p3, p0, color, 3.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_blind_spot_carrot(self, sm):
|
||||||
|
if not sm.valid['modelV2'] or not sm.valid['carState'] or not sm.valid['radarState']:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._update_blind_spot_barriers_carrot(sm)
|
||||||
|
|
||||||
|
warn_color = rl.Color(255, 215, 0, 150)
|
||||||
|
assist_color = rl.Color(0, 204, 0, 150)
|
||||||
|
|
||||||
|
car_state = sm['carState']
|
||||||
|
radar_state = sm['radarState']
|
||||||
|
meta = sm['modelV2'].meta
|
||||||
|
|
||||||
|
left_blindspot = bool(car_state.leftBlindspot)
|
||||||
|
right_blindspot = bool(car_state.rightBlindspot)
|
||||||
|
|
||||||
|
lead_left = radar_state.leadLeft
|
||||||
|
lead_right = radar_state.leadRight
|
||||||
|
|
||||||
|
lane_change_state = meta.laneChangeState
|
||||||
|
lane_change_direction = str(meta.laneChangeDirection).lower()
|
||||||
|
right_lane_change = (lane_change_state == LaneChangeState.preLaneChange) and ("right" in lane_change_direction)
|
||||||
|
left_lane_change = (lane_change_state == LaneChangeState.preLaneChange) and ("left" in lane_change_direction)
|
||||||
|
|
||||||
|
if left_blindspot:
|
||||||
|
self._draw_blind_spot_segments_carrot(self._carrot_lane_barrier_vertices[0], warn_color)
|
||||||
|
elif lead_left.status and float(lead_left.dRel) < float(car_state.vEgo) * 3.0 and left_lane_change:
|
||||||
|
self._draw_blind_spot_segments_carrot(self._carrot_lane_barrier_vertices[0], assist_color)
|
||||||
|
|
||||||
|
if right_blindspot:
|
||||||
|
self._draw_blind_spot_segments_carrot(self._carrot_lane_barrier_vertices[1], warn_color)
|
||||||
|
elif lead_right.status and float(lead_right.dRel) < float(car_state.vEgo) * 3.0 and right_lane_change:
|
||||||
|
self._draw_blind_spot_segments_carrot(self._carrot_lane_barrier_vertices[1], assist_color)
|
||||||
|
|
||||||
|
|
||||||
def _draw_radar_info_carrot(self, sm):
|
def _draw_radar_info_carrot(self, sm):
|
||||||
if self._carrot_show_radar_info <= 0:
|
if self._carrot_show_radar_info <= 0:
|
||||||
return
|
return
|
||||||
@@ -1316,7 +1460,7 @@ class ModelRenderer(Widget):
|
|||||||
x[3] = float(track_vertices[e - 1][0]); y[3] = float(track_vertices[e - 1][1])
|
x[3] = float(track_vertices[e - 1][0]); y[3] = float(track_vertices[e - 1][1])
|
||||||
x[4] = float(track_vertices[e][0]); y[4] = float(track_vertices[e][1])
|
x[4] = float(track_vertices[e][0]); y[4] = float(track_vertices[e][1])
|
||||||
x[5] = (x[1] + x[3]) / 2.0; y[5] = (y[1] + y[3]) / 2.0
|
x[5] = (x[1] + x[3]) / 2.0; y[5] = (y[1] + y[3]) / 2.0
|
||||||
self._draw_polygon_from_xy_carrot(x, y, self._carrot_colors[color_idx % 10], brake_valid, color_idx)
|
self._draw_two_quads_from_6pts_carrot(x, y, self._carrot_colors[color_idx % 10], brake_valid, color_idx)
|
||||||
color_n += 1
|
color_n += 1
|
||||||
if color_n > 6:
|
if color_n > 6:
|
||||||
color_n = 0
|
color_n = 0
|
||||||
@@ -1381,7 +1525,7 @@ class ModelRenderer(Widget):
|
|||||||
|
|
||||||
if draw_seg:
|
if draw_seg:
|
||||||
idx = color_n if mode in (4, 8) else color_idx % 10
|
idx = color_n if mode in (4, 8) else color_idx % 10
|
||||||
self._draw_polygon_from_xy_carrot(x, y, self._carrot_colors[idx], brake_valid, color_idx)
|
self._draw_two_quads_from_6pts_carrot(x, y, self._carrot_colors[idx], brake_valid, color_idx)
|
||||||
|
|
||||||
if i > 1:
|
if i > 1:
|
||||||
color_n += 1
|
color_n += 1
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
|
|||||||
AudibleAlert.audio9: ("audio_9.wav", None, MAX_VOLUME),
|
AudibleAlert.audio9: ("audio_9.wav", None, MAX_VOLUME),
|
||||||
AudibleAlert.audio10: ("audio_10.wav", None, MAX_VOLUME),
|
AudibleAlert.audio10: ("audio_10.wav", None, MAX_VOLUME),
|
||||||
}
|
}
|
||||||
if HARDWARE.get_device_type() == "tizi" or True:
|
if HARDWARE.get_device_type() == "tizi":
|
||||||
sound_list.update({
|
sound_list.update({
|
||||||
AudibleAlert.engage: ("engage_tizi.wav", 1, float(Params().get_int("SoundVolumeAdjustEngage"))/100.),
|
AudibleAlert.engage: ("engage_tizi.wav", 1, float(Params().get_int("SoundVolumeAdjustEngage"))/100.),
|
||||||
AudibleAlert.disengage: ("disengage_tizi.wav", 1, float(Params().get_int("SoundVolumeAdjustEngage"))/100.),
|
AudibleAlert.disengage: ("disengage_tizi.wav", 1, float(Params().get_int("SoundVolumeAdjustEngage"))/100.),
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ class FanController:
|
|||||||
self.last_ignition = ignition
|
self.last_ignition = ignition
|
||||||
|
|
||||||
return int(self.controller.update(
|
return int(self.controller.update(
|
||||||
error=(cur_temp - 75), # temperature setpoint in C
|
error=(cur_temp - 70), # temperature setpoint in C
|
||||||
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100])
|
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100])
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ from openpilot.system.ui.lib.multilang import multilang
|
|||||||
from openpilot.common.realtime import Ratekeeper
|
from openpilot.common.realtime import Ratekeeper
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60)))
|
#_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60)))
|
||||||
|
_DEFAULT_FPS = 20
|
||||||
FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops
|
FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops
|
||||||
FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning
|
FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning
|
||||||
FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions
|
FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions
|
||||||
|
|||||||
@@ -383,6 +383,9 @@ function op_switch() {
|
|||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
git submodule foreach git reset --hard
|
git submodule foreach git reset --hard
|
||||||
git submodule foreach git clean -df
|
git submodule foreach git clean -df
|
||||||
|
|
||||||
|
# remove openpilot update flag if present
|
||||||
|
rm -f .overlay_init
|
||||||
}
|
}
|
||||||
|
|
||||||
function op_start() {
|
function op_start() {
|
||||||
|
|||||||
Reference in New Issue
Block a user