mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-08-05 00:05:57 +08:00
move some tools back to root (#38235)
This commit is contained in:
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Forward all openpilot service ports
|
||||
while IFS=' ' read -r name port; do
|
||||
adb forward "tcp:${port}" "tcp:${port}" > /dev/null
|
||||
done < <(python3 - <<'PY'
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
|
||||
FNV_PRIME = 0x100000001b3
|
||||
FNV_OFFSET_BASIS = 0xcbf29ce484222325
|
||||
START_PORT = 8023
|
||||
MAX_PORT = 65535
|
||||
PORT_RANGE = MAX_PORT - START_PORT
|
||||
MASK = 0xffffffffffffffff
|
||||
|
||||
def fnv1a(endpoint: str) -> int:
|
||||
h = FNV_OFFSET_BASIS
|
||||
for b in endpoint.encode():
|
||||
h ^= b
|
||||
h = (h * FNV_PRIME) & MASK
|
||||
return h
|
||||
|
||||
ports = set()
|
||||
for name in SERVICE_LIST.keys():
|
||||
port = START_PORT + fnv1a(name) % PORT_RANGE
|
||||
ports.add((name, port))
|
||||
|
||||
for name, port in sorted(ports):
|
||||
print(f"{name} {port}")
|
||||
PY
|
||||
)
|
||||
|
||||
# Forward SSH port, finding a free local port if 2222 is taken.
|
||||
SSH_PORT=2222
|
||||
while ss -tln | grep -q ":${SSH_PORT} "; do
|
||||
SSH_PORT=$((SSH_PORT + 1))
|
||||
done
|
||||
adb forward tcp:${SSH_PORT} tcp:22
|
||||
|
||||
# SSH!
|
||||
ssh comma@localhost -p ${SSH_PORT} "$@"
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import binascii
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from tools.scripts.car.can_table import can_table
|
||||
from openpilot.tools.lib.logreader import LogIterable, LogReader
|
||||
|
||||
RED = '\033[91m'
|
||||
CLEAR = '\033[0m'
|
||||
|
||||
def update(msgs, bus, dat, low_to_high, high_to_low, quiet=False):
|
||||
for x in msgs:
|
||||
if x.which() != 'can':
|
||||
continue
|
||||
|
||||
for y in x.can:
|
||||
if y.src == bus:
|
||||
dat[y.address] = y.dat
|
||||
|
||||
i = int.from_bytes(y.dat, byteorder='big')
|
||||
l_h = low_to_high[y.address]
|
||||
h_l = high_to_low[y.address]
|
||||
|
||||
change = None
|
||||
if (i | l_h) != l_h:
|
||||
low_to_high[y.address] = i | l_h
|
||||
change = "+"
|
||||
|
||||
if (~i | h_l) != h_l:
|
||||
high_to_low[y.address] = ~i | h_l
|
||||
change = "-"
|
||||
|
||||
if change and not quiet:
|
||||
print(f"{time.monotonic():.2f}\t{hex(y.address)} ({y.address})\t{change}{binascii.hexlify(y.dat)}")
|
||||
|
||||
|
||||
def can_printer(bus=0, init_msgs=None, new_msgs=None, table=False):
|
||||
logcan = messaging.sub_sock('can', timeout=10)
|
||||
|
||||
dat = defaultdict(int)
|
||||
low_to_high = defaultdict(int)
|
||||
high_to_low = defaultdict(int)
|
||||
|
||||
if init_msgs is not None:
|
||||
update(init_msgs, bus, dat, low_to_high, high_to_low, quiet=True)
|
||||
|
||||
low_to_high_init = low_to_high.copy()
|
||||
high_to_low_init = high_to_low.copy()
|
||||
|
||||
if new_msgs is not None:
|
||||
update(new_msgs, bus, dat, low_to_high, high_to_low)
|
||||
else:
|
||||
# Live mode
|
||||
print(f"Waiting for messages on bus {bus}")
|
||||
try:
|
||||
while 1:
|
||||
can_recv = messaging.drain_sock(logcan)
|
||||
update(can_recv, bus, dat, low_to_high, high_to_low)
|
||||
time.sleep(0.02)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
print("\n\n")
|
||||
tables = ""
|
||||
for addr in sorted(dat.keys()):
|
||||
init = low_to_high_init[addr] & high_to_low_init[addr]
|
||||
now = low_to_high[addr] & high_to_low[addr]
|
||||
d = now & ~init
|
||||
if d == 0:
|
||||
continue
|
||||
b = d.to_bytes(len(dat[addr]), byteorder='big')
|
||||
|
||||
byts = ''.join([(c if c == '0' else f'{RED}{c}{CLEAR}') for c in str(binascii.hexlify(b))[2:-1]])
|
||||
header = f"{hex(addr).ljust(6)}({str(addr).ljust(4)})"
|
||||
print(header, byts)
|
||||
tables += f"{header}\n"
|
||||
tables += can_table(b) + "\n\n"
|
||||
|
||||
if table:
|
||||
print(tables)
|
||||
|
||||
if __name__ == "__main__":
|
||||
desc = """Collects messages and prints when a new bit transition is observed.
|
||||
This is very useful to find signals based on user triggered actions, such as blinkers and seatbelt.
|
||||
Leave the script running until no new transitions are seen, then perform the action."""
|
||||
parser = argparse.ArgumentParser(description=desc,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--bus", type=int, help="CAN bus to print out", default=0)
|
||||
parser.add_argument("--table", action="store_true", help="Print a cabana-like table")
|
||||
parser.add_argument("init", type=str, nargs='?', help="Route or segment to initialize with. Use empty quotes to compare against all zeros.")
|
||||
parser.add_argument("comp", type=str, nargs='?', help="Route or segment to compare against init")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
init_lr: LogIterable | None = None
|
||||
new_lr: LogIterable | None = None
|
||||
|
||||
if args.init:
|
||||
if args.init == '':
|
||||
init_lr = []
|
||||
else:
|
||||
init_lr = LogReader(args.init)
|
||||
if args.comp:
|
||||
new_lr = LogReader(args.comp)
|
||||
|
||||
can_printer(args.bus, init_msgs=init_lr, new_msgs=new_lr, table=args.table)
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import binascii
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
|
||||
|
||||
def can_printer(bus, max_msg, addr, ascii_decode):
|
||||
logcan = messaging.sub_sock('can', addr=addr)
|
||||
|
||||
start = time.monotonic()
|
||||
lp = time.monotonic()
|
||||
msgs = defaultdict(list)
|
||||
while 1:
|
||||
can_recv = messaging.drain_sock(logcan, wait_for_one=True)
|
||||
for x in can_recv:
|
||||
for y in x.can:
|
||||
if y.src == bus:
|
||||
msgs[y.address].append(y.dat)
|
||||
|
||||
if time.monotonic() - lp > 0.1:
|
||||
dd = chr(27) + "[2J"
|
||||
dd += f"{time.monotonic() - start:5.2f}\n"
|
||||
for _addr in sorted(msgs.keys()):
|
||||
a = f"\"{msgs[_addr][-1].decode('ascii', 'backslashreplace')}\"" if ascii_decode else ""
|
||||
x = binascii.hexlify(msgs[_addr][-1]).decode('ascii')
|
||||
freq = len(msgs[_addr]) / (time.monotonic() - start)
|
||||
if max_msg is None or _addr < max_msg:
|
||||
dd += f"{_addr:04X}({_addr:4d})({len(msgs[_addr]):6d})({freq:3}dHz) {x.ljust(20)} {a}\n"
|
||||
print(dd)
|
||||
lp = time.monotonic()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="simple CAN data viewer",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("--bus", type=int, help="CAN bus to print out", default=0)
|
||||
parser.add_argument("--max_msg", type=int, help="max addr")
|
||||
parser.add_argument("--ascii", action='store_true', help="decode as ascii")
|
||||
parser.add_argument("--addr", default="127.0.0.1")
|
||||
|
||||
args = parser.parse_args()
|
||||
can_printer(args.bus, args.max_msg, args.addr, args.ascii)
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import pandas as pd
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
|
||||
|
||||
def can_table(dat):
|
||||
rows = []
|
||||
for b in dat:
|
||||
r = list(bin(b).lstrip('0b').zfill(8))
|
||||
r += [hex(b)]
|
||||
rows.append(r)
|
||||
|
||||
df = pd.DataFrame(data=rows)
|
||||
df.columns = [str(n) for n in range(7, -1, -1)] + [' ']
|
||||
table = df.to_markdown(tablefmt='grid')
|
||||
return table
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Cabana-like table of bits for your terminal",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("addr", type=str, nargs=1)
|
||||
parser.add_argument("bus", type=int, default=0, nargs='?')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
addr = int(args.addr[0], 0)
|
||||
can = messaging.sub_sock('can', conflate=False, timeout=None)
|
||||
|
||||
print(f"waiting for {hex(addr)} ({addr}) on bus {args.bus}...")
|
||||
|
||||
latest = None
|
||||
while True:
|
||||
for msg in messaging.drain_sock(can, wait_for_one=True):
|
||||
for m in msg.can:
|
||||
if m.address == addr and m.src == args.bus:
|
||||
latest = m
|
||||
|
||||
if latest is None:
|
||||
continue
|
||||
|
||||
table = can_table(latest.dat)
|
||||
print(f"\n\n{hex(addr)} ({addr}) on bus {args.bus}\n{table}")
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import argparse
|
||||
from subprocess import check_output, CalledProcessError
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.uds import UdsClient, MessageTimeoutError, SESSION_TYPE, DTC_GROUP_TYPE
|
||||
from opendbc.car.structs import CarParams
|
||||
from panda import Panda
|
||||
|
||||
parser = argparse.ArgumentParser(description="clear DTC status")
|
||||
parser.add_argument("addr", type=lambda x: int(x,0), nargs="?", default=0x7DF) # default is functional (broadcast) address
|
||||
parser.add_argument("--bus", type=int, default=0)
|
||||
parser.add_argument('--debug', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
try:
|
||||
check_output(["pidof", "pandad"])
|
||||
print("pandad is running, please kill openpilot before running this script! (aborted)")
|
||||
sys.exit(1)
|
||||
except CalledProcessError as e:
|
||||
if e.returncode != 1: # 1 == no process found (pandad not running)
|
||||
raise e
|
||||
|
||||
panda = Panda()
|
||||
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||||
uds_client = UdsClient(panda, args.addr, bus=args.bus)
|
||||
print("extended diagnostic session ...")
|
||||
try:
|
||||
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||||
except MessageTimeoutError:
|
||||
# functional address isn't properly handled so a timeout occurs
|
||||
if args.addr != 0x7DF:
|
||||
raise
|
||||
print("clear diagnostic info ...")
|
||||
try:
|
||||
uds_client.clear_diagnostic_information(DTC_GROUP_TYPE.ALL)
|
||||
except MessageTimeoutError:
|
||||
# functional address isn't properly handled so a timeout occurs
|
||||
if args.addr != 0x7DF:
|
||||
pass
|
||||
print("")
|
||||
print("you may need to power cycle your vehicle now")
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from opendbc.car.disable_ecu import disable_ecu
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks
|
||||
|
||||
if __name__ == "__main__":
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
logcan = messaging.sub_sock('can')
|
||||
can_callbacks = can_comm_callbacks(logcan, sendcan)
|
||||
time.sleep(1)
|
||||
|
||||
# honda bosch radar disable
|
||||
disabled = disable_ecu(*can_callbacks, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03', timeout=0.5)
|
||||
print(f"disabled: {disabled}")
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import time
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.ecu_addrs import get_all_ecu_addrs
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks, obd_callback
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Get addresses of all ECUs')
|
||||
parser.add_argument('--debug', action='store_true')
|
||||
parser.add_argument('--bus', type=int, default=1)
|
||||
parser.add_argument('--no-obd', action='store_true')
|
||||
parser.add_argument('--timeout', type=float, default=1.0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
logcan = messaging.sub_sock('can')
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
can_callbacks = can_comm_callbacks(logcan, sendcan)
|
||||
|
||||
# Set up params for pandad
|
||||
params = Params()
|
||||
params.remove("FirmwareQueryDone")
|
||||
params.put_bool("IsOffroad", True, block=True)
|
||||
time.sleep(0.2) # thread is 10 Hz
|
||||
params.put_bool("IsOffroad", False, block=True)
|
||||
|
||||
obd_callback(params)(not args.no_obd)
|
||||
|
||||
print("Getting ECU addresses ...")
|
||||
ecu_addrs = get_all_ecu_addrs(*can_callbacks, args.bus, args.timeout)
|
||||
|
||||
print()
|
||||
print("Found ECUs on rx addresses:")
|
||||
for addr, subaddr, _ in ecu_addrs:
|
||||
msg = f" {hex(addr)}"
|
||||
if subaddr is not None:
|
||||
msg += f" (sub-address: {hex(subaddr)})"
|
||||
print(msg)
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import argparse
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from opendbc.car.structs import car
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.fw_versions import get_fw_versions, match_fw_to_car
|
||||
from opendbc.car.vin import get_vin
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks, obd_callback
|
||||
from typing import Any
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Get firmware version of ECUs')
|
||||
parser.add_argument('--scan', action='store_true')
|
||||
parser.add_argument('--debug', action='store_true')
|
||||
parser.add_argument('--brand', help='Only query addresses/with requests for this brand')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
logcan = messaging.sub_sock('can')
|
||||
pandaStates_sock = messaging.sub_sock('pandaStates')
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
can_callbacks = can_comm_callbacks(logcan, sendcan)
|
||||
|
||||
# Set up params for pandad
|
||||
params = Params()
|
||||
params.remove("FirmwareQueryDone")
|
||||
params.put_bool("IsOffroad", True, block=True)
|
||||
time.sleep(0.2) # thread is 10 Hz
|
||||
params.put_bool("IsOffroad", False, block=True)
|
||||
set_obd_multiplexing = obd_callback(params)
|
||||
|
||||
extra: Any = None
|
||||
if args.scan:
|
||||
extra = {}
|
||||
# Honda
|
||||
for i in range(256):
|
||||
extra[(Ecu.unknown, 0x18da00f1 + (i << 8), None)] = []
|
||||
extra[(Ecu.unknown, 0x700 + i, None)] = []
|
||||
extra[(Ecu.unknown, 0x750, i)] = []
|
||||
extra = {"any": {"debug": extra}}
|
||||
|
||||
t = time.monotonic()
|
||||
print("Getting vin...")
|
||||
set_obd_multiplexing(True)
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(*can_callbacks, (0, 1))
|
||||
print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}')
|
||||
print(f"Getting VIN took {time.monotonic() - t:.3f} s")
|
||||
print()
|
||||
|
||||
t = time.monotonic()
|
||||
fw_vers = get_fw_versions(*can_callbacks, set_obd_multiplexing, query_brand=args.brand, extra=extra, progress=True)
|
||||
_, candidates = match_fw_to_car(fw_vers, vin)
|
||||
|
||||
print()
|
||||
print("Found FW versions")
|
||||
print("{")
|
||||
padding = max([len(fw.brand) for fw in fw_vers] or [0])
|
||||
for version in fw_vers:
|
||||
subaddr = None if version.subAddress == 0 else hex(version.subAddress)
|
||||
print(f" Brand: {version.brand:{padding}}, bus: {version.bus}, OBD: {version.obdMultiplexing} - " +
|
||||
f"(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion!r}]")
|
||||
print("}")
|
||||
|
||||
print()
|
||||
print("Possible matches:", candidates)
|
||||
print(f"Getting fw took {time.monotonic() - t:.3f} s")
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Some Hyundai radars can be reconfigured to output (debug) radar points on bus 1.
|
||||
Reconfiguration is done over UDS by reading/writing to 0x0142 using the Read/Write Data By Identifier
|
||||
endpoints (0x22 & 0x2E). This script checks your radar firmware version against a list of known
|
||||
firmware versions. If you want to try on a new radar make sure to note the default config value
|
||||
in case it's different from the other radars and you need to revert the changes.
|
||||
|
||||
After changing the config the car should not show any faults when openpilot is not running.
|
||||
These config changes are persistent across car reboots. You need to run this script again
|
||||
to go back to the default values.
|
||||
|
||||
USE AT YOUR OWN RISK! Safety features, like AEB and FCW, might be affected by these changes."""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from typing import NamedTuple
|
||||
from subprocess import check_output, CalledProcessError
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.uds import UdsClient, SESSION_TYPE, DATA_IDENTIFIER_TYPE
|
||||
from opendbc.car.structs import CarParams
|
||||
from panda.python import Panda
|
||||
|
||||
class ConfigValues(NamedTuple):
|
||||
default_config: bytes
|
||||
tracks_enabled: bytes
|
||||
|
||||
# If your radar supports changing data identifier 0x0142 as well make a PR to
|
||||
# this file to add your firmware version. Make sure to post a drive as proof!
|
||||
# NOTE: these firmware versions do not match what openpilot uses
|
||||
# because this script uses a different diagnostic session type
|
||||
SUPPORTED_FW_VERSIONS = {
|
||||
# 2020 SONATA
|
||||
b"DN8_ SCC FHCUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
b"DN8_ SCC F-CUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2021 SONATA HYBRID
|
||||
b"DNhe SCC FHCUP 1.00 1.00 99110-L5000\x19\x04&\x13' ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2020 PALISADE
|
||||
b"LX2_ SCC FHCUP 1.00 1.04 99110-S8100\x19\x05\x02\x16V ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2022 PALISADE
|
||||
b"LX2_ SCC FHCUP 1.00 1.00 99110-S8110!\x04\x05\x17\x01 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2020 SANTA FE
|
||||
b"TM__ SCC F-CUP 1.00 1.03 99110-S2000\x19\x050\x13' ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2020 GENESIS G70
|
||||
b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2019 SANTA FE
|
||||
b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
b"TM__ SCC F-CUP 1.00 1.02 99110-S2000\x18\x07\x08\x18W ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2021 K5 HEV
|
||||
b"DLhe SCC FHCUP 1.00 1.02 99110-L7000 \x01 \x102 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='configure radar to output points (or reset to default)')
|
||||
parser.add_argument('--default', action="store_true", default=False, help='reset to default configuration (default: false)')
|
||||
parser.add_argument('--debug', action="store_true", default=False, help='enable debug output (default: false)')
|
||||
parser.add_argument('--bus', type=int, default=0, help='can bus to use (default: 0)')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
try:
|
||||
check_output(["pidof", "pandad"])
|
||||
print("pandad is running, please kill openpilot before running this script! (aborted)")
|
||||
sys.exit(1)
|
||||
except CalledProcessError as e:
|
||||
if e.returncode != 1: # 1 == no process found (pandad not running)
|
||||
raise e
|
||||
|
||||
confirm = input("power on the vehicle keeping the engine off (press start button twice) then type OK to continue: ").upper().strip()
|
||||
if confirm != "OK":
|
||||
print("\nyou didn't type 'OK! (aborted)")
|
||||
sys.exit(0)
|
||||
|
||||
panda = Panda()
|
||||
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||||
uds_client = UdsClient(panda, 0x7D0, bus=args.bus)
|
||||
|
||||
print("\n[START DIAGNOSTIC SESSION]")
|
||||
session_type : SESSION_TYPE = 0x07
|
||||
uds_client.diagnostic_session_control(session_type)
|
||||
|
||||
print("[HARDWARE/SOFTWARE VERSION]")
|
||||
fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100
|
||||
fw_version = uds_client.read_data_by_identifier(fw_version_data_id)
|
||||
print(fw_version)
|
||||
if fw_version not in SUPPORTED_FW_VERSIONS.keys():
|
||||
print("radar not supported! (aborted)")
|
||||
sys.exit(1)
|
||||
|
||||
print("[GET CONFIGURATION]")
|
||||
config_data_id : DATA_IDENTIFIER_TYPE = 0x0142
|
||||
current_config = uds_client.read_data_by_identifier(config_data_id)
|
||||
config_values = SUPPORTED_FW_VERSIONS[fw_version]
|
||||
new_config = config_values.default_config if args.default else config_values.tracks_enabled
|
||||
print(f"current config: 0x{current_config.hex()}")
|
||||
if current_config != new_config:
|
||||
print("[CHANGE CONFIGURATION]")
|
||||
print(f"new config: 0x{new_config.hex()}")
|
||||
uds_client.write_data_by_identifier(config_data_id, new_config)
|
||||
if not args.default and current_config != SUPPORTED_FW_VERSIONS[fw_version].default_config:
|
||||
print("\ncurrent config does not match expected default! (aborted)")
|
||||
sys.exit(1)
|
||||
|
||||
print("[DONE]")
|
||||
print("\nrestart your vehicle and ensure there are no faults")
|
||||
if not args.default:
|
||||
print("you can run this script again with --default to go back to the original (factory) settings")
|
||||
else:
|
||||
print("[DONE]")
|
||||
print("\ncurrent config is already the desired configuration")
|
||||
sys.exit(0)
|
||||
Executable
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from functools import partial
|
||||
from tqdm import tqdm
|
||||
from typing import NamedTuple
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
from openpilot.selfdrive.locationd.models.pose_kf import EARTH_G
|
||||
|
||||
RLOG_MIN_LAT_ACTIVE = 50
|
||||
RLOG_MIN_STEERING_UNPRESSED = 50
|
||||
RLOG_MIN_REQUESTING_MAX = 25 # sample many times after reaching max torque
|
||||
|
||||
QLOG_DECIMATION = 10
|
||||
|
||||
|
||||
class Event(NamedTuple):
|
||||
lateral_accel: float
|
||||
speed: float
|
||||
roll: float
|
||||
timestamp: float # relative to start of route (s)
|
||||
|
||||
|
||||
def find_events(lr: LogReader, extrapolate: bool = False, qlog: bool = False) -> list[Event]:
|
||||
min_lat_active = RLOG_MIN_LAT_ACTIVE // QLOG_DECIMATION if qlog else RLOG_MIN_LAT_ACTIVE
|
||||
min_steering_unpressed = RLOG_MIN_STEERING_UNPRESSED // QLOG_DECIMATION if qlog else RLOG_MIN_STEERING_UNPRESSED
|
||||
min_requesting_max = RLOG_MIN_REQUESTING_MAX // QLOG_DECIMATION if qlog else RLOG_MIN_REQUESTING_MAX
|
||||
|
||||
# if we test with driver torque safety, max torque can be slightly noisy
|
||||
steer_threshold = 0.7 if extrapolate else 0.95
|
||||
|
||||
events = []
|
||||
|
||||
# state tracking
|
||||
steering_unpressed = 0 # frames
|
||||
requesting_max = 0 # frames
|
||||
lat_active = 0 # frames
|
||||
|
||||
# current state
|
||||
curvature = 0
|
||||
v_ego = 0
|
||||
roll = 0
|
||||
out_torque = 0
|
||||
|
||||
start_ts = 0
|
||||
for msg in lr:
|
||||
if msg.which() == 'carControl':
|
||||
if start_ts == 0:
|
||||
start_ts = msg.logMonoTime
|
||||
|
||||
lat_active = lat_active + 1 if msg.carControl.latActive else 0
|
||||
|
||||
elif msg.which() == 'carOutput':
|
||||
out_torque = msg.carOutput.actuatorsOutput.torque
|
||||
requesting_max = requesting_max + 1 if abs(out_torque) > steer_threshold else 0
|
||||
|
||||
elif msg.which() == 'carState':
|
||||
steering_unpressed = steering_unpressed + 1 if not msg.carState.steeringPressed else 0
|
||||
v_ego = msg.carState.vEgo
|
||||
|
||||
elif msg.which() == 'controlsState':
|
||||
curvature = msg.controlsState.curvature
|
||||
|
||||
elif msg.which() == 'liveParameters':
|
||||
roll = msg.liveParameters.roll
|
||||
|
||||
if lat_active > min_lat_active and steering_unpressed > min_steering_unpressed and requesting_max > min_requesting_max:
|
||||
# TODO: record max lat accel at the end of the event, need to use the past lat accel as overriding can happen before we detect it
|
||||
requesting_max = 0
|
||||
|
||||
factor = 1 / abs(out_torque)
|
||||
current_lateral_accel = (curvature * v_ego ** 2 * factor) - roll * EARTH_G
|
||||
events.append(Event(current_lateral_accel, v_ego, roll, round((msg.logMonoTime - start_ts) * 1e-9, 2)))
|
||||
print(events[-1])
|
||||
|
||||
return events
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description="Find max lateral acceleration events",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("route", nargs='+')
|
||||
parser.add_argument("-e", "--extrapolate", action="store_true", help="Extrapolates max lateral acceleration events linearly. " +
|
||||
"This option can be far less accurate.")
|
||||
args = parser.parse_args()
|
||||
|
||||
events = []
|
||||
for route in tqdm(args.route):
|
||||
try:
|
||||
lr = LogReader(route, sort_by_time=True)
|
||||
except Exception:
|
||||
print(f'Skipping {route}')
|
||||
continue
|
||||
|
||||
qlog = route.endswith('/q')
|
||||
if qlog:
|
||||
print('WARNING: Treating route as qlog!')
|
||||
|
||||
print('Finding events...')
|
||||
events += lr.run_across_segments(8, partial(find_events, extrapolate=args.extrapolate, qlog=qlog), disable_tqdm=True)
|
||||
|
||||
print()
|
||||
print(f'Found {len(events)} events')
|
||||
|
||||
perc_left_accel = -np.percentile([-ev.lateral_accel for ev in events if ev.lateral_accel < 0] or [0], 90)
|
||||
perc_right_accel = np.percentile([ev.lateral_accel for ev in events if ev.lateral_accel > 0] or [0], 90)
|
||||
|
||||
CP = lr.first('carParams')
|
||||
|
||||
plt.ion()
|
||||
plt.clf()
|
||||
plt.suptitle(f'{CP.carFingerprint} - Max lateral acceleration events')
|
||||
plt.title(', '.join(args.route))
|
||||
plt.scatter([ev.speed for ev in events], [ev.lateral_accel for ev in events], label='max lateral accel events')
|
||||
|
||||
plt.plot([0, 35], [3, 3], c='r', label='ISO 11270 - 3 m/s^2')
|
||||
plt.plot([0, 35], [-3, -3], c='r')
|
||||
|
||||
plt.plot([0, 35], [perc_left_accel, perc_left_accel], c='g', linestyle='--', label='90th percentile left lateral accel')
|
||||
plt.plot([0, 35], [perc_right_accel, perc_right_accel], c='#ff7f0e', linestyle='--', label='90th percentile right lateral accel')
|
||||
plt.text(0.4, float(perc_left_accel + 0.4), f'{perc_left_accel:.2f} m/s^2', verticalalignment='center', fontsize=12)
|
||||
plt.text(0.4, float(perc_right_accel - 0.4), f'{perc_right_accel:.2f} m/s^2', verticalalignment='center', fontsize=12)
|
||||
|
||||
plt.xlim(0, 35)
|
||||
plt.ylim(-5, 5)
|
||||
plt.xlabel('speed (m/s)')
|
||||
plt.ylabel('lateral acceleration (m/s^2)')
|
||||
plt.legend()
|
||||
plt.show(block=True)
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import struct
|
||||
from collections import deque
|
||||
from statistics import mean
|
||||
|
||||
from openpilot.cereal import log
|
||||
import openpilot.cereal.messaging as messaging
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='Sniff a communication socket')
|
||||
parser.add_argument('--addr', default='127.0.0.1')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.addr != "127.0.0.1":
|
||||
os.environ["ZMQ"] = "1"
|
||||
messaging.reset_context()
|
||||
|
||||
poller = messaging.Poller()
|
||||
messaging.sub_sock('can', poller, addr=args.addr)
|
||||
|
||||
active = 0
|
||||
start_t = 0
|
||||
start_v = 0
|
||||
max_v = 0
|
||||
max_t = 0
|
||||
window = deque(maxlen=10)
|
||||
avg = 0
|
||||
while 1:
|
||||
polld = poller.poll(1000)
|
||||
for sock in polld:
|
||||
msg = sock.receive()
|
||||
with log.Event.from_bytes(msg) as log_evt:
|
||||
evt = log_evt
|
||||
|
||||
for item in evt.can:
|
||||
if item.address == 0xe4 and item.src == 128:
|
||||
torque_req = struct.unpack('!h', item.dat[0:2])[0]
|
||||
# print(torque_req)
|
||||
active = abs(torque_req) > 0
|
||||
if abs(torque_req) < 100:
|
||||
if max_v > 5:
|
||||
print(f'{start_v} -> {max_v} = {round(max_v - start_v, 2)} over {round(max_t - start_t, 2)}s')
|
||||
start_t = evt.logMonoTime / 1e9
|
||||
start_v = avg
|
||||
max_t = 0
|
||||
max_v = 0
|
||||
if item.address == 0x1ab and item.src == 0:
|
||||
motor_torque = ((item.dat[0] & 0x3) << 8) + item.dat[1]
|
||||
window.append(motor_torque)
|
||||
avg = mean(window)
|
||||
#print(f'{evt.logMonoTime}: {avg}')
|
||||
if active and avg > max_v + 0.5:
|
||||
max_v = avg
|
||||
max_t = evt.logMonoTime / 1e9
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import argparse
|
||||
from subprocess import check_output, CalledProcessError
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.uds import UdsClient, SESSION_TYPE, DTC_REPORT_TYPE, DTC_STATUS_MASK_TYPE, get_dtc_num_as_str, get_dtc_status_names
|
||||
from opendbc.car.structs import CarParams
|
||||
from panda import Panda
|
||||
|
||||
parser = argparse.ArgumentParser(description="read DTC status")
|
||||
parser.add_argument("addr", type=lambda x: int(x,0))
|
||||
parser.add_argument("--bus", type=int, default=0)
|
||||
parser.add_argument('--debug', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
try:
|
||||
check_output(["pidof", "pandad"])
|
||||
print("pandad is running, please kill openpilot before running this script! (aborted)")
|
||||
sys.exit(1)
|
||||
except CalledProcessError as e:
|
||||
if e.returncode != 1: # 1 == no process found (pandad not running)
|
||||
raise e
|
||||
|
||||
panda = Panda()
|
||||
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||||
uds_client = UdsClient(panda, args.addr, bus=args.bus)
|
||||
print("extended diagnostic session ...")
|
||||
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||||
print("read diagnostic codes ...")
|
||||
data = uds_client.read_dtc_information(DTC_REPORT_TYPE.DTC_BY_STATUS_MASK, DTC_STATUS_MASK_TYPE.ALL)
|
||||
print("status availability:", " ".join(get_dtc_status_names(data[0])))
|
||||
print("DTC status:")
|
||||
for i in range(1, len(data), 4):
|
||||
dtc_num = get_dtc_num_as_str(data[i:i+3])
|
||||
dtc_status = " ".join(get_dtc_status_names(data[i+3]))
|
||||
print(dtc_num, dtc_status)
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from sklearn import linear_model
|
||||
from opendbc.car.toyota.values import STEER_THRESHOLD
|
||||
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
MIN_SAMPLES = 30 * 100
|
||||
|
||||
|
||||
def to_signed(n, bits):
|
||||
if n >= (1 << max((bits - 1), 0)):
|
||||
n = n - (1 << max(bits, 0))
|
||||
return n
|
||||
|
||||
|
||||
def get_eps_factor(lr, plot=False):
|
||||
engaged = False
|
||||
steering_pressed = False
|
||||
torque_cmd, eps_torque = None, None
|
||||
cmds, eps = [], []
|
||||
|
||||
for msg in lr:
|
||||
if msg.which() != 'can':
|
||||
continue
|
||||
|
||||
for m in msg.can:
|
||||
if m.address == 0x2e4 and m.src == 128:
|
||||
engaged = bool(m.dat[0] & 1)
|
||||
torque_cmd = to_signed((m.dat[1] << 8) | m.dat[2], 16)
|
||||
elif m.address == 0x260 and m.src == 0:
|
||||
eps_torque = to_signed((m.dat[5] << 8) | m.dat[6], 16)
|
||||
steering_pressed = abs(to_signed((m.dat[1] << 8) | m.dat[2], 16)) > STEER_THRESHOLD
|
||||
|
||||
if engaged and torque_cmd is not None and eps_torque is not None and not steering_pressed:
|
||||
cmds.append(torque_cmd)
|
||||
eps.append(eps_torque)
|
||||
else:
|
||||
if len(cmds) > MIN_SAMPLES:
|
||||
break
|
||||
cmds, eps = [], []
|
||||
|
||||
if len(cmds) < MIN_SAMPLES:
|
||||
raise Exception("too few samples found in route")
|
||||
|
||||
lm = linear_model.LinearRegression(fit_intercept=False)
|
||||
lm.fit(np.array(cmds).reshape(-1, 1), eps)
|
||||
scale_factor = 1. / lm.coef_[0]
|
||||
|
||||
if plot:
|
||||
plt.plot(np.array(eps) * scale_factor)
|
||||
plt.plot(cmds)
|
||||
plt.show()
|
||||
return scale_factor
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
lr = LogReader(sys.argv[1])
|
||||
n = get_eps_factor(lr, plot="--plot" in sys.argv)
|
||||
print("EPS torque factor: ", n)
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import time
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.vin import get_vin
|
||||
from openpilot.selfdrive.car.card import can_comm_callbacks
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Get VIN of the car')
|
||||
parser.add_argument('--debug', action='store_true')
|
||||
parser.add_argument('--bus', type=int, default=1)
|
||||
parser.add_argument('--timeout', type=float, default=0.1)
|
||||
parser.add_argument('--retry', type=int, default=5)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
logcan = messaging.sub_sock('can')
|
||||
can_callbacks = can_comm_callbacks(logcan, sendcan)
|
||||
time.sleep(1)
|
||||
|
||||
vin_rx_addr, vin_rx_bus, vin = get_vin(*can_callbacks, (args.bus,), args.timeout, args.retry)
|
||||
print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}')
|
||||
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
from enum import IntEnum
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE,\
|
||||
DATA_IDENTIFIER_TYPE, ACCESS_TYPE
|
||||
from opendbc.car.structs import CarParams
|
||||
from panda import Panda
|
||||
from datetime import date
|
||||
|
||||
# TODO: extend UDS library to allow custom/vendor-defined data identifiers without ignoring type checks
|
||||
class VOLKSWAGEN_DATA_IDENTIFIER_TYPE(IntEnum):
|
||||
CODING = 0x0600
|
||||
|
||||
# TODO: extend UDS library security_access() to take an access level offset per ISO 14229-1:2020 10.4 and remove this
|
||||
class ACCESS_TYPE_LEVEL_1(IntEnum):
|
||||
REQUEST_SEED = ACCESS_TYPE.REQUEST_SEED + 2
|
||||
SEND_KEY = ACCESS_TYPE.SEND_KEY + 2
|
||||
|
||||
MQB_EPS_CAN_ADDR = 0x712
|
||||
RX_OFFSET = 0x6a
|
||||
|
||||
if __name__ == "__main__":
|
||||
desc_text = "Shows Volkswagen EPS software and coding info, and enables or disables Heading Control Assist " + \
|
||||
"(Lane Assist). Useful for enabling HCA on cars without factory Lane Assist that want to use " + \
|
||||
"openpilot integrated at the CAN gateway (J533)."
|
||||
epilog_text = "This tool is meant to run directly on a vehicle-installed comma three, with the " + \
|
||||
"openpilot/tmux processes stopped. It should also work on a separate PC with a USB-attached comma " + \
|
||||
"panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must " + \
|
||||
"turn ignition off and on again for any changes to take effect."
|
||||
parser = argparse.ArgumentParser(description=desc_text, epilog=epilog_text)
|
||||
parser.add_argument("--debug", action="store_true", help="enable ISO-TP/UDS stack debugging output")
|
||||
parser.add_argument("action", choices={"show", "enable", "disable"}, help="show or modify current EPS HCA config")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
carlog.setLevel('DEBUG')
|
||||
|
||||
panda = Panda()
|
||||
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||||
uds_client = UdsClient(panda, MQB_EPS_CAN_ADDR, MQB_EPS_CAN_ADDR + RX_OFFSET, 1, timeout=0.2)
|
||||
|
||||
try:
|
||||
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||||
except MessageTimeoutError:
|
||||
print("Timeout opening session with EPS")
|
||||
quit()
|
||||
|
||||
odx_file, current_coding = None, None
|
||||
try:
|
||||
hw_pn = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER).decode("utf-8")
|
||||
sw_pn = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER).decode("utf-8")
|
||||
sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8")
|
||||
component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8")
|
||||
odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8").rstrip('\x00')
|
||||
current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING)
|
||||
coding_text = current_coding.hex()
|
||||
|
||||
print("\nEPS diagnostic data\n")
|
||||
print(f" Part No HW: {hw_pn}")
|
||||
print(f" Part No SW: {sw_pn}")
|
||||
print(f" SW Version: {sw_ver}")
|
||||
print(f" Component: {component}")
|
||||
print(f" Coding: {coding_text}")
|
||||
print(f" ASAM Dataset: {odx_file}")
|
||||
except NegativeResponseError:
|
||||
print("Error fetching data from EPS")
|
||||
quit()
|
||||
except MessageTimeoutError:
|
||||
print("Timeout fetching data from EPS")
|
||||
quit()
|
||||
|
||||
coding_variant, current_coding_array, coding_byte, coding_bit = None, None, 0, 0
|
||||
coding_length = len(current_coding)
|
||||
|
||||
# EPS_MQB_ZFLS
|
||||
if odx_file in ("EV_SteerAssisMQB", "EV_SteerAssisMNB"):
|
||||
coding_variant = "ZFLS"
|
||||
coding_byte = 0
|
||||
coding_bit = 4
|
||||
|
||||
# MQB_PP_APA, MQB_VWBS_GEN2
|
||||
elif odx_file in ("EV_SteerAssisVWBSMQBA", "EV_SteerAssisVWBSMQBGen2"):
|
||||
coding_variant = "APA"
|
||||
coding_byte = 3
|
||||
coding_bit = 0
|
||||
|
||||
else:
|
||||
print("Configuration changes not yet supported on this EPS!")
|
||||
quit()
|
||||
|
||||
current_coding_array = struct.unpack(f"!{coding_length}B", current_coding)
|
||||
hca_enabled = (current_coding_array[coding_byte] & (1 << coding_bit) != 0)
|
||||
hca_text = ("DISABLED", "ENABLED")[hca_enabled]
|
||||
print(f" Lane Assist: {hca_text}")
|
||||
|
||||
try:
|
||||
params = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION).decode("utf-8")
|
||||
param_version_system_params = params[1:3]
|
||||
param_vehicle_type = params[3:5]
|
||||
param_index_char_curve = params[5:7]
|
||||
param_version_char_values = params[7:9]
|
||||
param_version_memory_map = params[9:11]
|
||||
print("\nEPS parameterization (per-vehicle calibration) data\n")
|
||||
print(f" Version of system parameters: {param_version_system_params}")
|
||||
print(f" Vehicle type: {param_vehicle_type}")
|
||||
print(f" Index of characteristic curve: {param_index_char_curve}")
|
||||
print(f" Version of characteristic values: {param_version_char_values}")
|
||||
print(f" Version of memory map: {param_version_memory_map}")
|
||||
except (NegativeResponseError, MessageTimeoutError):
|
||||
print("Error fetching parameterization data from EPS!")
|
||||
quit()
|
||||
|
||||
if args.action in ["enable", "disable"]:
|
||||
print("\nAttempting configuration update")
|
||||
|
||||
assert(coding_variant in ("ZFLS", "APA"))
|
||||
# ZFLS EPS config coding length can be anywhere from 1 to 4 bytes, but the
|
||||
# bit we care about is always in the same place in the first byte
|
||||
if args.action == "enable":
|
||||
new_byte = current_coding_array[coding_byte] | (1 << coding_bit)
|
||||
else:
|
||||
new_byte = current_coding_array[coding_byte] & ~(1 << coding_bit)
|
||||
new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:]
|
||||
|
||||
try:
|
||||
seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED)
|
||||
key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that
|
||||
uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key))
|
||||
except (NegativeResponseError, MessageTimeoutError):
|
||||
print("Security access failed!")
|
||||
print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)")
|
||||
quit()
|
||||
|
||||
try:
|
||||
# Programming date and tester number must be written before making
|
||||
# a change, or write to CODING will fail with request sequence error
|
||||
# Encoding on tester is unclear, it contains the workshop code in the
|
||||
# last two bytes, but not the VZ/importer or tester serial number
|
||||
# Can't seem to read it back, but we can read the calibration tester,
|
||||
# so fib a little and say that same tester did the programming
|
||||
current_date = date.today()
|
||||
formatted_date = current_date.strftime('%y-%m-%d')
|
||||
year, month, day = (int(part) for part in formatted_date.split('-'))
|
||||
prog_date = bytes([year, month, day])
|
||||
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date)
|
||||
tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER)
|
||||
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num)
|
||||
uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding)
|
||||
except (NegativeResponseError, MessageTimeoutError):
|
||||
print("Writing new configuration failed!")
|
||||
print("Make sure the comma processes are stopped: tmux kill-session -t comma")
|
||||
quit()
|
||||
|
||||
try:
|
||||
# Read back result just to make 100% sure everything worked
|
||||
current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex()
|
||||
print(f" New coding: {current_coding_text}")
|
||||
except (NegativeResponseError, MessageTimeoutError):
|
||||
print("Reading back updated coding failed!")
|
||||
quit()
|
||||
print("EPS configuration successfully updated")
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import math
|
||||
import datetime
|
||||
from collections import Counter
|
||||
from pprint import pprint
|
||||
from typing import cast
|
||||
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
from openpilot.selfdrive.test.process_replay.migration import migrate_all
|
||||
|
||||
if __name__ == "__main__":
|
||||
cnt_events: Counter = Counter()
|
||||
|
||||
cams = [s for s in SERVICE_LIST if s.endswith('CameraState')]
|
||||
cnt_cameras = dict.fromkeys(cams, 0)
|
||||
|
||||
events: list[tuple[float, set[str]]] = []
|
||||
alerts: list[tuple[float, str]] = []
|
||||
start_time = math.inf
|
||||
end_time = -math.inf
|
||||
ignition_off = None
|
||||
for msg in migrate_all(LogReader(sys.argv[1], ReadMode.QLOG)):
|
||||
t = (msg.logMonoTime - start_time) / 1e9
|
||||
end_time = max(end_time, msg.logMonoTime)
|
||||
start_time = min(start_time, msg.logMonoTime)
|
||||
|
||||
if msg.which() == 'onroadEvents':
|
||||
for e in msg.onroadEvents:
|
||||
cnt_events[e.name] += 1
|
||||
|
||||
ae = {str(e.name) for e in msg.onroadEvents if e.name not in ('pedalPressed', 'steerOverride', 'gasPressedOverride')}
|
||||
if len(events) == 0 or ae != events[-1][1]:
|
||||
events.append((t, ae))
|
||||
|
||||
elif msg.which() == 'selfdriveState':
|
||||
at = msg.selfdriveState.alertType
|
||||
if "/override" not in at or "lanechange" in at.lower():
|
||||
if len(alerts) == 0 or alerts[-1][1] != at:
|
||||
alerts.append((t, at))
|
||||
elif msg.which() == 'pandaStates':
|
||||
if ignition_off is None:
|
||||
ign = any(ps.ignitionLine or ps.ignitionCan for ps in msg.pandaStates)
|
||||
if not ign:
|
||||
ignition_off = msg.logMonoTime
|
||||
break
|
||||
elif msg.which() in cams:
|
||||
cnt_cameras[msg.which()] += 1
|
||||
|
||||
duration = (end_time - start_time) / 1e9
|
||||
|
||||
print("Events")
|
||||
pprint(cnt_events)
|
||||
|
||||
print("\n")
|
||||
print("Events")
|
||||
for t, evt in events:
|
||||
print(f"{t:8.2f} {evt}")
|
||||
|
||||
print("\n")
|
||||
print("Cameras")
|
||||
for k, v in cnt_cameras.items():
|
||||
s = SERVICE_LIST[k]
|
||||
expected_frames = int(s.frequency * duration / cast(float, s.decimation))
|
||||
print(" ", k.ljust(20), f"{v}, {v/expected_frames:.1%} of expected")
|
||||
|
||||
print("\n")
|
||||
print("Alerts")
|
||||
for t, a in alerts:
|
||||
print(f"{t:8.2f} {a}")
|
||||
|
||||
print("\n")
|
||||
if ignition_off is not None:
|
||||
ignition_off = round((ignition_off - start_time) / 1e9, 2)
|
||||
print("Ignition off at", ignition_off)
|
||||
print("Route duration", datetime.timedelta(seconds=duration))
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
System tools like top/htop can only show current cpu usage values, so I write this script to do statistics jobs.
|
||||
Features:
|
||||
Use psutil library to sample cpu usage(avergage for all cores) of openpilot processes, at a rate of 5 samples/sec.
|
||||
Do cpu usage statistics periodically, 5 seconds as a cycle.
|
||||
Calculate the average cpu usage within this cycle.
|
||||
Calculate minumium/maximum/accumulated_average cpu usage as long term inspections.
|
||||
Monitor multiple processes simuteneously.
|
||||
Sample usage:
|
||||
root@localhost:/data/openpilot$ python openpilot/tools/scripts/cpu_usage_stat.py pandad,ubloxd
|
||||
('Add monitored proc:', './pandad')
|
||||
('Add monitored proc:', 'python locationd/ubloxd.py')
|
||||
pandad: 1.96%, min: 1.96%, max: 1.96%, acc: 1.96%
|
||||
ubloxd.py: 0.39%, min: 0.39%, max: 0.39%, acc: 0.39%
|
||||
'''
|
||||
import psutil
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
import argparse
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
|
||||
# Do statistics every 5 seconds
|
||||
PRINT_INTERVAL = 5
|
||||
SLEEP_INTERVAL = 0.2
|
||||
|
||||
monitored_proc_names = [
|
||||
# android procs
|
||||
'SurfaceFlinger', 'sensors.qcom'
|
||||
] + list(managed_processes.keys())
|
||||
|
||||
cpu_time_names = ['user', 'system', 'children_user', 'children_system']
|
||||
|
||||
|
||||
def get_arg_parser():
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("proc_names", nargs="?", default='',
|
||||
help="Process names to be monitored, comma separated")
|
||||
parser.add_argument("--list_all", action='store_true',
|
||||
help="Show all running processes' cmdline")
|
||||
parser.add_argument("--detailed_times", action='store_true',
|
||||
help="show cpu time details (split by user, system, child user, child system)")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = get_arg_parser().parse_args(sys.argv[1:])
|
||||
if args.list_all:
|
||||
for p in psutil.process_iter():
|
||||
print('cmdline', p.cmdline(), 'name', p.name())
|
||||
sys.exit(0)
|
||||
|
||||
if len(args.proc_names) > 0:
|
||||
monitored_proc_names = args.proc_names.split(',')
|
||||
monitored_procs = []
|
||||
stats = {}
|
||||
for p in psutil.process_iter():
|
||||
if p == psutil.Process():
|
||||
continue
|
||||
matched = any(l for l in p.cmdline() if any(pn for pn in monitored_proc_names if re.match(fr'.*{pn}.*', l, re.M | re.I)))
|
||||
if matched:
|
||||
k = ' '.join(p.cmdline())
|
||||
print('Add monitored proc:', k)
|
||||
stats[k] = {'cpu_samples': defaultdict(list), 'min': defaultdict(lambda: None), 'max': defaultdict(lambda: None),
|
||||
'avg': defaultdict(float), 'last_cpu_times': None, 'last_sys_time': None}
|
||||
stats[k]['last_sys_time'] = time.monotonic()
|
||||
stats[k]['last_cpu_times'] = p.cpu_times()
|
||||
monitored_procs.append(p)
|
||||
i = 0
|
||||
interval_int = int(PRINT_INTERVAL / SLEEP_INTERVAL)
|
||||
while True:
|
||||
for p in monitored_procs:
|
||||
k = ' '.join(p.cmdline())
|
||||
cur_sys_time = time.monotonic()
|
||||
cur_cpu_times = p.cpu_times()
|
||||
cpu_times = np.subtract(cur_cpu_times, stats[k]['last_cpu_times']) / (cur_sys_time - stats[k]['last_sys_time'])
|
||||
stats[k]['last_sys_time'] = cur_sys_time
|
||||
stats[k]['last_cpu_times'] = cur_cpu_times
|
||||
cpu_percent = 0
|
||||
for num, name in enumerate(cpu_time_names):
|
||||
stats[k]['cpu_samples'][name].append(cpu_times[num])
|
||||
cpu_percent += cpu_times[num]
|
||||
stats[k]['cpu_samples']['total'].append(cpu_percent)
|
||||
time.sleep(SLEEP_INTERVAL)
|
||||
i += 1
|
||||
if i % interval_int == 0:
|
||||
l = []
|
||||
for k, stat in stats.items():
|
||||
if len(stat['cpu_samples']) <= 0:
|
||||
continue
|
||||
for name, samples in stat['cpu_samples'].items():
|
||||
samples = np.array(samples)
|
||||
avg = samples.mean()
|
||||
c = samples.size
|
||||
min_cpu = np.amin(samples)
|
||||
max_cpu = np.amax(samples)
|
||||
if stat['min'][name] is None or min_cpu < stat['min'][name]:
|
||||
stat['min'][name] = min_cpu
|
||||
if stat['max'][name] is None or max_cpu > stat['max'][name]:
|
||||
stat['max'][name] = max_cpu
|
||||
stat['avg'][name] = (stat['avg'][name] * (i - c) + avg * c) / (i)
|
||||
stat['cpu_samples'][name] = []
|
||||
|
||||
msg = f"avg: {stat['avg']['total']:.2%}, min: {stat['min']['total']:.2%}, max: {stat['max']['total']:.2%} {os.path.basename(k)}"
|
||||
if args.detailed_times:
|
||||
for stat_type in ['avg', 'min', 'max']:
|
||||
msg += f"\n {stat_type}: {[(name + ':' + str(round(stat[stat_type][name] * 100, 2))) for name in cpu_time_names]}"
|
||||
l.append((os.path.basename(k), stat['avg']['total'], msg))
|
||||
l.sort(key=lambda x: -x[1])
|
||||
for x in l:
|
||||
print(x[2])
|
||||
print('avg sum: {:.2%} over {} samples {} seconds\n'.format(
|
||||
sum(stat['avg']['total'] for k, stat in stats.items()), i, i * SLEEP_INTERVAL
|
||||
))
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import random
|
||||
|
||||
from openpilot.cereal import log
|
||||
from opendbc.car.structs import car
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.selfdrived.events import ET, Events
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import AlertManager
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
def randperc() -> float:
|
||||
return 100. * random.random()
|
||||
|
||||
def cycle_alerts(duration=200, is_metric=False):
|
||||
# all alerts
|
||||
#alerts = list(EVENTS.keys())
|
||||
|
||||
# this plays each type of audible alert
|
||||
alerts = [
|
||||
(EventName.buttonEnable, ET.ENABLE),
|
||||
(EventName.buttonCancel, ET.USER_DISABLE),
|
||||
(EventName.wrongGear, ET.NO_ENTRY),
|
||||
|
||||
(EventName.locationdTemporaryError, ET.SOFT_DISABLE),
|
||||
(EventName.paramsdTemporaryError, ET.SOFT_DISABLE),
|
||||
(EventName.accFaulted, ET.IMMEDIATE_DISABLE),
|
||||
|
||||
# DM sequence
|
||||
(EventName.driverDistracted1, ET.WARNING),
|
||||
(EventName.driverDistracted2, ET.WARNING),
|
||||
(EventName.driverDistracted3, ET.WARNING),
|
||||
]
|
||||
|
||||
# debug alerts
|
||||
alerts = [
|
||||
#(EventName.highCpuUsage, ET.NO_ENTRY),
|
||||
#(EventName.lowMemory, ET.PERMANENT),
|
||||
#(EventName.overheat, ET.PERMANENT),
|
||||
#(EventName.outOfSpace, ET.PERMANENT),
|
||||
#(EventName.modeldLagging, ET.PERMANENT),
|
||||
#(EventName.processNotRunning, ET.NO_ENTRY),
|
||||
#(EventName.commIssue, ET.NO_ENTRY),
|
||||
#(EventName.calibrationInvalid, ET.PERMANENT),
|
||||
(EventName.cameraMalfunction, ET.PERMANENT),
|
||||
(EventName.cameraFrameRate, ET.PERMANENT),
|
||||
]
|
||||
|
||||
cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState']
|
||||
|
||||
CS = car.CarState.new_message()
|
||||
CP = CarInterface.get_non_essential_params("HONDA_CIVIC")
|
||||
sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration',
|
||||
'driverMonitoringState', 'longitudinalPlan', 'livePose',
|
||||
'managerState'] + cameras)
|
||||
|
||||
pm = messaging.PubMaster(['selfdriveState', 'pandaStates', 'deviceState'])
|
||||
|
||||
events = Events()
|
||||
AM = AlertManager()
|
||||
|
||||
frame = 0
|
||||
while True:
|
||||
for alert, et in alerts:
|
||||
events.clear()
|
||||
events.add(alert)
|
||||
|
||||
sm['deviceState'].freeSpacePercent = randperc()
|
||||
sm['deviceState'].memoryUsagePercent = int(randperc())
|
||||
sm['deviceState'].cpuTempC = [randperc() for _ in range(3)]
|
||||
sm['deviceState'].gpuTempC = [randperc() for _ in range(3)]
|
||||
sm['deviceState'].cpuUsagePercent = [int(randperc()) for _ in range(8)]
|
||||
sm['modelV2'].frameDropPerc = randperc()
|
||||
|
||||
if random.random() > 0.25:
|
||||
sm['modelV2'].velocity.x = [random.random(), ]
|
||||
if random.random() > 0.25:
|
||||
CS.vEgo = random.random()
|
||||
|
||||
procs = [p.get_process_state_msg() for p in managed_processes.values()]
|
||||
random.shuffle(procs)
|
||||
for i in range(random.randint(0, 10)):
|
||||
procs[i].shouldBeRunning = True
|
||||
sm['managerState'].processes = procs
|
||||
|
||||
sm['liveCalibration'].rpyCalib = [-1 * random.random() for _ in range(random.randint(0, 3))]
|
||||
|
||||
for s in sm.data.keys():
|
||||
prob = 0.3 if s in cameras else 0.08
|
||||
sm.alive[s] = random.random() > prob
|
||||
sm.valid[s] = random.random() > prob
|
||||
sm.freq_ok[s] = random.random() > prob
|
||||
|
||||
a = events.create_alerts([et, ], [CP, CS, sm, is_metric, 0])
|
||||
AM.add_many(frame, a)
|
||||
alert = AM.process_alerts(frame, [])
|
||||
print(alert)
|
||||
for _ in range(duration):
|
||||
dat = messaging.new_message('selfdriveState')
|
||||
dat.selfdriveState.enabled = False
|
||||
|
||||
if alert:
|
||||
dat.selfdriveState.alertText1 = alert.alert_text_1
|
||||
dat.selfdriveState.alertText2 = alert.alert_text_2
|
||||
dat.selfdriveState.alertSize = alert.alert_size
|
||||
dat.selfdriveState.alertStatus = alert.alert_status
|
||||
dat.selfdriveState.alertType = alert.alert_type
|
||||
dat.selfdriveState.alertSound = alert.audible_alert
|
||||
pm.send('selfdriveState', dat)
|
||||
|
||||
dat = messaging.new_message('deviceState')
|
||||
dat.deviceState.started = True
|
||||
pm.send('deviceState', dat)
|
||||
|
||||
dat = messaging.new_message('pandaStates', 1)
|
||||
dat.pandaStates[0].ignitionLine = True
|
||||
dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno
|
||||
pm.send('pandaStates', dat)
|
||||
|
||||
frame += 1
|
||||
time.sleep(DT_CTRL)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cycle_alerts()
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from opendbc.car import uds
|
||||
from openpilot.tools.lib.live_logreader import live_logreader
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
|
||||
|
||||
def main(route: str | None, addrs: list[int], rxoffset: int | None):
|
||||
"""
|
||||
TODO:
|
||||
- highlight TX vs RX clearly
|
||||
- disambiguate sendcan and can (useful to know if something sent on sendcan made it to the bus on can->128)
|
||||
- print as fixed width table, easier to read
|
||||
"""
|
||||
|
||||
if route is None:
|
||||
lr = live_logreader()
|
||||
else:
|
||||
lr = LogReader(route, default_mode=ReadMode.RLOG, sort_by_time=True)
|
||||
|
||||
start_mono_time = None
|
||||
prev_mono_time = 0
|
||||
|
||||
# include rx addresses
|
||||
addrs = addrs + [uds.get_rx_addr_for_tx_addr(addr, rxoffset) for addr in addrs]
|
||||
|
||||
for msg in lr:
|
||||
if msg.which() == 'can':
|
||||
if start_mono_time is None:
|
||||
start_mono_time = msg.logMonoTime
|
||||
|
||||
if msg.which() in ("can", 'sendcan'):
|
||||
for can in getattr(msg, msg.which()):
|
||||
if can.address in addrs or not len(addrs):
|
||||
if msg.logMonoTime != prev_mono_time:
|
||||
print()
|
||||
prev_mono_time = msg.logMonoTime
|
||||
print(f"{msg.which():>7}: rxaddr={can.address}, bus={str(can.src) + ',':<4} {round((msg.logMonoTime - start_mono_time) * 1e-6)} ms, " +
|
||||
f"0x{can.dat.hex()}, {can.dat}, {len(can.dat)=}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='View back and forth ISO-TP communication between various ECUs given an address')
|
||||
parser.add_argument('route', nargs='?', help='Route name, live if not specified')
|
||||
parser.add_argument('--addrs', nargs='*', default=[], help='List of tx address to view (0x7e0 for engine)')
|
||||
parser.add_argument('--rxoffset', default='0x8')
|
||||
args = parser.parse_args()
|
||||
|
||||
addrs = [int(addr, base=16) if addr.startswith('0x') else int(addr) for addr in args.addrs]
|
||||
rxoffset = int(args.rxoffset, base=16) if args.rxoffset else None
|
||||
main(args.route, addrs, rxoffset)
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import shlex
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
|
||||
def build_rsync_cmd(args) -> list[str]:
|
||||
ssh = [
|
||||
"ssh",
|
||||
"-o", "ControlMaster=auto",
|
||||
"-o", f"ControlPath=/tmp/devsync-{args.ip}.ctl",
|
||||
"-o", "ControlPersist=10m",
|
||||
"-o", "StrictHostKeyChecking=accept-new",
|
||||
]
|
||||
if args.identity:
|
||||
ssh += ["-i", args.identity]
|
||||
|
||||
return [
|
||||
"rsync", "-az",
|
||||
"--files-from=-", "--from0",
|
||||
"-e", " ".join(shlex.quote(p) for p in ssh),
|
||||
"--out-format=%n",
|
||||
BASEDIR + "/", f"comma@{args.ip}:{args.remote}/",
|
||||
]
|
||||
|
||||
|
||||
def git_tracked_files() -> bytes:
|
||||
return subprocess.check_output(
|
||||
["git", "-C", BASEDIR, "ls-files", "--recurse-submodules", "-z"]
|
||||
)
|
||||
|
||||
|
||||
class Handler(FileSystemEventHandler):
|
||||
def __init__(self, sync_fn):
|
||||
self.dirty = threading.Event()
|
||||
self.sync_fn = sync_fn
|
||||
|
||||
def on_any_event(self, event):
|
||||
if not event.is_directory:
|
||||
self.dirty.set()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
time.sleep(1)
|
||||
if self.dirty.is_set():
|
||||
self.dirty.clear()
|
||||
self.sync_fn()
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("ip", help="device IP / hostname")
|
||||
p.add_argument("--remote", default="/data/openpilot", help="remote path on device")
|
||||
p.add_argument("-i", "--identity", default=None, help="ssh identity file")
|
||||
args = p.parse_args()
|
||||
|
||||
print(f"[devsync] watching {BASEDIR}")
|
||||
print(f"[devsync] target comma@{args.ip}:{args.remote}")
|
||||
|
||||
def run_sync():
|
||||
file_list = git_tracked_files()
|
||||
cmd = build_rsync_cmd(args)
|
||||
t0 = time.monotonic()
|
||||
r = subprocess.run(cmd, input=file_list, capture_output=True)
|
||||
dt = time.monotonic() - t0
|
||||
if r.returncode:
|
||||
print(f"[devsync] ERR rc={r.returncode} in {dt:.2f}s")
|
||||
return
|
||||
files = [ln for ln in r.stdout.decode().splitlines() if ln.strip()]
|
||||
msg = f"{len(files)} files: {', '.join(files)}" if files else "no changes"
|
||||
print(f"[devsync] {dt:.2f}s · {msg}")
|
||||
|
||||
run_sync()
|
||||
|
||||
handler = Handler(run_sync)
|
||||
obs = Observer()
|
||||
obs.schedule(handler, BASEDIR, recursive=True)
|
||||
obs.start()
|
||||
handler.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n[devsync] stopping")
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import codecs
|
||||
|
||||
from openpilot.cereal import log
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
from openpilot.tools.lib.live_logreader import raw_live_logreader
|
||||
|
||||
|
||||
codecs.register_error("strict", codecs.backslashreplace_errors)
|
||||
|
||||
def hexdump(msg):
|
||||
m = str.upper(msg.hex())
|
||||
m = [m[i:i+2] for i in range(0,len(m),2)]
|
||||
m = [m[i:i+16] for i in range(0,len(m),16)]
|
||||
for row,dump in enumerate(m):
|
||||
addr = '%08X:' % (row*16)
|
||||
raw = ' '.join(dump[:8]) + ' ' + ' '.join(dump[8:])
|
||||
space = ' ' * (48 - len(raw))
|
||||
asci = ''.join(chr(int(x,16)) if 0x20 <= int(x,16) <= 0x7E else '.' for x in dump)
|
||||
print(f'{addr} {raw} {space} {asci}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='Dump communication sockets. See openpilot/cereal/services.py for a complete list of available sockets.')
|
||||
parser.add_argument('--pipe', action='store_true')
|
||||
parser.add_argument('--raw', action='store_true')
|
||||
parser.add_argument('--json', action='store_true')
|
||||
parser.add_argument('--dump-json', action='store_true')
|
||||
parser.add_argument('--no-print', action='store_true')
|
||||
parser.add_argument('--addr', default='127.0.0.1')
|
||||
parser.add_argument('--values', help='values to monitor (instead of entire event)')
|
||||
parser.add_argument("socket", type=str, nargs='*', default=list(SERVICE_LIST.keys()), help="socket names to dump. defaults to all services defined in cereal")
|
||||
args = parser.parse_args()
|
||||
|
||||
lr = raw_live_logreader(args.socket, args.addr)
|
||||
|
||||
values = None
|
||||
if args.values:
|
||||
values = [s.strip().split(".") for s in args.values.split(",")]
|
||||
|
||||
for msg in lr:
|
||||
with log.Event.from_bytes(msg) as evt:
|
||||
if not args.no_print:
|
||||
if args.pipe:
|
||||
sys.stdout.write(str(msg))
|
||||
sys.stdout.flush()
|
||||
elif args.raw:
|
||||
hexdump(msg)
|
||||
elif args.json:
|
||||
print(json.loads(msg))
|
||||
elif args.dump_json:
|
||||
print(json.dumps(evt.to_dict()))
|
||||
elif values:
|
||||
print(f"logMonotime = {evt.logMonoTime}")
|
||||
for value in values:
|
||||
if hasattr(evt, value[0]):
|
||||
item = evt
|
||||
for key in value:
|
||||
item = getattr(item, key)
|
||||
print(f"{'.'.join(value)} = {item}")
|
||||
print("")
|
||||
else:
|
||||
try:
|
||||
print(evt)
|
||||
except UnicodeDecodeError:
|
||||
w = evt.which()
|
||||
s = f"( logMonoTime {evt.logMonoTime} \n {w} = "
|
||||
s += str(evt.__getattr__(w))
|
||||
s += f"\n valid = {evt.valid} )"
|
||||
print(s)
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import wave
|
||||
import argparse
|
||||
import numpy as np
|
||||
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
|
||||
|
||||
def extract_audio(route_or_segment_name, output_file=None, play=False):
|
||||
lr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE)
|
||||
audio_messages = list(lr.filter("rawAudioData"))
|
||||
if not audio_messages:
|
||||
print("No rawAudioData messages found in logs")
|
||||
return
|
||||
sample_rate = audio_messages[0].sampleRate
|
||||
|
||||
audio_chunks = []
|
||||
total_frames = 0
|
||||
for msg in audio_messages:
|
||||
audio_array = np.frombuffer(msg.data, dtype=np.int16)
|
||||
audio_chunks.append(audio_array)
|
||||
total_frames += len(audio_array)
|
||||
full_audio = np.concatenate(audio_chunks)
|
||||
|
||||
print(f"Found {total_frames} frames from {len(audio_messages)} audio messages at {sample_rate} Hz")
|
||||
|
||||
if output_file:
|
||||
if write_wav_file(output_file, full_audio, sample_rate):
|
||||
print(f"Audio written to {output_file}")
|
||||
else:
|
||||
print("Audio extraction canceled.")
|
||||
if play:
|
||||
play_audio(full_audio, sample_rate)
|
||||
|
||||
|
||||
def write_wav_file(filename, audio_data, sample_rate):
|
||||
if os.path.exists(filename):
|
||||
if input(f"File '{filename}' exists. Overwrite? (y/N): ").lower() not in ['y', 'yes']:
|
||||
return False
|
||||
|
||||
with wave.open(filename, 'wb') as wav_file:
|
||||
wav_file.setnchannels(1) # Mono
|
||||
wav_file.setsampwidth(2) # 16-bit
|
||||
wav_file.setframerate(sample_rate)
|
||||
wav_file.writeframes(audio_data.tobytes())
|
||||
return True
|
||||
|
||||
|
||||
def play_audio(audio_data, sample_rate):
|
||||
try:
|
||||
import sounddevice as sd
|
||||
|
||||
print("Playing audio... Press Ctrl+C to stop")
|
||||
sd.play(audio_data, sample_rate)
|
||||
sd.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("\nPlayback stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Extract audio data from openpilot logs")
|
||||
parser.add_argument("-o", "--output", help="Output WAV file path")
|
||||
parser.add_argument("--play", action="store_true", help="Play audio with sounddevice")
|
||||
parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name")
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
args = parser.parse_args()
|
||||
|
||||
output_file = args.output
|
||||
if not args.output and not args.play:
|
||||
output_file = "extracted_audio.wav"
|
||||
|
||||
extract_audio(args.route_or_segment_name.strip(), output_file, args.play)
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
LEVELS = {
|
||||
"DEBUG": 10,
|
||||
"INFO": 20,
|
||||
"WARNING": 30,
|
||||
"ERROR": 40,
|
||||
"CRITICAL": 50,
|
||||
}
|
||||
|
||||
OPERATING_SYSTEM_LOG_SOURCE = {
|
||||
0: "MAIN",
|
||||
1: "RADIO",
|
||||
2: "EVENTS",
|
||||
3: "SYSTEM",
|
||||
4: "CRASH",
|
||||
5: "KERNEL",
|
||||
}
|
||||
|
||||
|
||||
def print_logmessage(t, msg, min_level):
|
||||
try:
|
||||
log = json.loads(msg)
|
||||
if log['levelnum'] >= min_level:
|
||||
print(f"[{t / 1e9:.6f}] {log['filename']}:{log.get('lineno', '')} - {log.get('funcname', '')}: {log['msg']}")
|
||||
if 'exc_info' in log:
|
||||
print(log['exc_info'])
|
||||
except json.decoder.JSONDecodeError:
|
||||
print(f"[{t / 1e9:.6f}] decode error: {msg}")
|
||||
|
||||
|
||||
def print_operating_system_log(t, msg):
|
||||
source = msg.tag or OPERATING_SYSTEM_LOG_SOURCE.get(msg.id, "SYSTEM")
|
||||
try:
|
||||
m = json.loads(msg.message)['MESSAGE']
|
||||
except Exception:
|
||||
m = msg.message
|
||||
|
||||
print(f"[{t / 1e9:.6f}] {source} {msg.pid} {msg.tag} - {m}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--absolute', action='store_true')
|
||||
parser.add_argument('--level', default='DEBUG')
|
||||
parser.add_argument('--addr', default='127.0.0.1')
|
||||
parser.add_argument("route", type=str, nargs='*', help="route name + segment number for offline usage")
|
||||
args = parser.parse_args()
|
||||
|
||||
min_level = LEVELS[args.level]
|
||||
|
||||
if args.route:
|
||||
st = None if not args.absolute else 0
|
||||
for route in args.route:
|
||||
lr = LogReader(route, sort_by_time=True)
|
||||
for m in lr:
|
||||
if st is None:
|
||||
st = m.logMonoTime
|
||||
if m.which() == 'logMessage':
|
||||
print_logmessage(m.logMonoTime-st, m.logMessage, min_level)
|
||||
elif m.which() == 'errorLogMessage':
|
||||
print_logmessage(m.logMonoTime-st, m.errorLogMessage, min_level)
|
||||
elif m.which() == 'operatingSystemLog':
|
||||
print_operating_system_log(m.logMonoTime-st, m.operatingSystemLog)
|
||||
else:
|
||||
sm = messaging.SubMaster(['logMessage', 'operatingSystemLog'], addr=args.addr)
|
||||
while True:
|
||||
sm.update()
|
||||
|
||||
if sm.updated['logMessage']:
|
||||
print_logmessage(sm.logMonoTime['logMessage'], sm['logMessage'], min_level)
|
||||
|
||||
if sm.updated['operatingSystemLog']:
|
||||
print_operating_system_log(sm.logMonoTime['operatingSystemLog'], sm['operatingSystemLog'])
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
|
||||
|
||||
def get_fingerprint(lr):
|
||||
# TODO: make this a nice tool for car ports. should also work with qlogs for FW
|
||||
|
||||
fw = None
|
||||
vin = None
|
||||
msgs = {}
|
||||
for msg in lr:
|
||||
if msg.which() == 'carParams':
|
||||
fw = msg.carParams.carFw
|
||||
vin = msg.carParams.carVin
|
||||
elif msg.which() == 'can':
|
||||
for c in msg.can:
|
||||
# read also msgs sent by EON on CAN bus 0x80 and filter out the
|
||||
# addr with more than 11 bits
|
||||
if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
# show CAN fingerprint
|
||||
fingerprint = ', '.join(f"{v[0]}: {v[1]}" for v in sorted(msgs.items()))
|
||||
print(f"\nfound {len(msgs)} messages. CAN fingerprint:\n")
|
||||
print(fingerprint)
|
||||
|
||||
# TODO: also print the fw fingerprint merged with the existing ones
|
||||
# show FW fingerprint
|
||||
if fw:
|
||||
print("\nFW fingerprint:\n")
|
||||
for f in fw:
|
||||
print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [")
|
||||
print(f" {f.fwVersion},")
|
||||
print(" ],")
|
||||
print()
|
||||
print(f"VIN: {vin}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: ./fingerprint_from_route.py <route>")
|
||||
sys.exit(1)
|
||||
|
||||
lr = LogReader(sys.argv[1], ReadMode.QLOG)
|
||||
get_fingerprint(lr)
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
from collections import defaultdict
|
||||
|
||||
from tqdm import tqdm
|
||||
from opendbc.car.fw_versions import match_fw_to_car_fuzzy
|
||||
from opendbc.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS
|
||||
from opendbc.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS
|
||||
from opendbc.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS
|
||||
from opendbc.car.volkswagen.values import FW_VERSIONS as VW_FW_VERSIONS
|
||||
|
||||
|
||||
FWS = {}
|
||||
FWS.update(TOYOTA_FW_VERSIONS)
|
||||
FWS.update(HONDA_FW_VERSIONS)
|
||||
FWS.update(HYUNDAI_FW_VERSIONS)
|
||||
FWS.update(VW_FW_VERSIONS)
|
||||
|
||||
if __name__ == "__main__":
|
||||
total = 0
|
||||
match = 0
|
||||
wrong_match = 0
|
||||
confusions = defaultdict(set)
|
||||
|
||||
for _ in tqdm(range(1000)):
|
||||
for candidate, fws in FWS.items():
|
||||
fw_dict = {}
|
||||
for (_, addr, subaddr), fw_list in fws.items():
|
||||
fw_dict[(addr, subaddr)] = [random.choice(fw_list)]
|
||||
|
||||
matches = match_fw_to_car_fuzzy(fw_dict, log=False, exclude=candidate)
|
||||
|
||||
total += 1
|
||||
if len(matches) == 1:
|
||||
if list(matches)[0] == candidate:
|
||||
match += 1
|
||||
else:
|
||||
confusions[candidate] |= matches
|
||||
wrong_match += 1
|
||||
|
||||
print()
|
||||
for candidate, wrong_matches in sorted(confusions.items()):
|
||||
print(candidate, wrong_matches)
|
||||
|
||||
print()
|
||||
print(f"Total fuzz cases: {total}")
|
||||
print(f"Correct matches: {match}")
|
||||
print(f"Wrong matches: {wrong_match}")
|
||||
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# simple script to get a vehicle fingerprint.
|
||||
|
||||
# Instructions:
|
||||
# - connect to a Panda
|
||||
# - run openpilot/selfdrive/pandad/pandad
|
||||
# - launching this script
|
||||
# Note: it's very important that the car is in stock mode, in order to collect a complete fingerprint
|
||||
# - since some messages are published at low frequency, keep this script running for at least 30s,
|
||||
# until all messages are received at least once
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
|
||||
logcan = messaging.sub_sock('can')
|
||||
msgs = {}
|
||||
while True:
|
||||
lc = messaging.recv_sock(logcan, True)
|
||||
if lc is None:
|
||||
continue
|
||||
|
||||
for c in lc.can:
|
||||
# read also msgs sent by EON on CAN bus 0x80 and filter out the
|
||||
# addr with more than 11 bits
|
||||
if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8):
|
||||
msgs[c.address] = len(c.dat)
|
||||
|
||||
fingerprint = ', '.join(f"{v[0]}: {v[1]}" for v in sorted(msgs.items()))
|
||||
|
||||
print(f"number of messages {len(msgs)}:")
|
||||
print(f"fingerprint {fingerprint}")
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import numpy as np
|
||||
import capnp
|
||||
from collections import defaultdict
|
||||
|
||||
from openpilot.cereal.messaging import SubMaster
|
||||
|
||||
def cputime_total(ct):
|
||||
return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq
|
||||
|
||||
|
||||
def cputime_busy(ct):
|
||||
return ct.user + ct.nice + ct.system + ct.irq + ct.softirq
|
||||
|
||||
|
||||
def proc_cputime_total(ct):
|
||||
return ct.cpuUser + ct.cpuSystem + ct.cpuChildrenUser + ct.cpuChildrenSystem
|
||||
|
||||
|
||||
def proc_name(proc):
|
||||
name = proc.name
|
||||
if len(proc.cmdline):
|
||||
name = proc.cmdline[0]
|
||||
if len(proc.exe):
|
||||
name = proc.exe + " - " + name
|
||||
|
||||
return name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--mem', action='store_true')
|
||||
parser.add_argument('--cpu', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
sm = SubMaster(['deviceState', 'procLog'])
|
||||
|
||||
last_temp = 0.0
|
||||
last_mem = 0.0
|
||||
total_times = [0.]*8
|
||||
busy_times = [0.]*8
|
||||
|
||||
prev_proclog: capnp._DynamicStructReader | None = None
|
||||
prev_proclog_t: int | None = None
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
|
||||
if sm.updated['deviceState']:
|
||||
t = sm['deviceState']
|
||||
last_temp = np.mean(t.cpuTempC)
|
||||
last_mem = t.memoryUsagePercent
|
||||
|
||||
if sm.updated['procLog']:
|
||||
m = sm['procLog']
|
||||
|
||||
cores = [0.]*8
|
||||
total_times_new = [0.]*8
|
||||
busy_times_new = [0.]*8
|
||||
|
||||
for c in m.cpuTimes:
|
||||
n = c.cpuNum
|
||||
total_times_new[n] = cputime_total(c)
|
||||
busy_times_new[n] = cputime_busy(c)
|
||||
|
||||
for n in range(8):
|
||||
t_busy = busy_times_new[n] - busy_times[n]
|
||||
t_total = total_times_new[n] - total_times[n]
|
||||
cores[n] = t_busy / t_total
|
||||
|
||||
total_times = total_times_new[:]
|
||||
busy_times = busy_times_new[:]
|
||||
|
||||
print(f"CPU {100.0 * np.mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C")
|
||||
|
||||
if args.cpu and prev_proclog is not None and prev_proclog_t is not None:
|
||||
procs: dict[str, float] = defaultdict(float)
|
||||
dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9
|
||||
for proc in m.procs:
|
||||
try:
|
||||
name = proc_name(proc)
|
||||
prev_proc = [p for p in prev_proclog.procs if proc.pid == p.pid][0]
|
||||
cpu_time = proc_cputime_total(proc) - proc_cputime_total(prev_proc)
|
||||
cpu_usage = cpu_time / dt * 100.
|
||||
procs[name] += cpu_usage
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
print("Top CPU usage:")
|
||||
for k, v in sorted(procs.items(), key=lambda item: item[1], reverse=True)[:10]:
|
||||
print(f"{k.rjust(70)} {v:.2f} %")
|
||||
print()
|
||||
|
||||
if args.mem:
|
||||
mems = {}
|
||||
for proc in m.procs:
|
||||
name = proc_name(proc)
|
||||
mems[name] = float(proc.memRss) / 1e6
|
||||
print("Top memory usage:")
|
||||
for k, v in sorted(mems.items(), key=lambda item: item[1], reverse=True)[:10]:
|
||||
print(f"{k.rjust(70)} {v:.2f} MB")
|
||||
print()
|
||||
|
||||
prev_proclog = m
|
||||
prev_proclog_t = sm.logMonoTime['procLog']
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from openpilot.selfdrive.test.mem_usage import DEMO_ROUTE, print_report
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Analyze memory usage from route logs")
|
||||
parser.add_argument("route", nargs="?", default=None, help="route ID or local rlog path")
|
||||
parser.add_argument("--demo", action="store_true", help=f"use demo route ({DEMO_ROUTE})")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.demo:
|
||||
route = DEMO_ROUTE
|
||||
elif args.route:
|
||||
route = args.route
|
||||
else:
|
||||
parser.error("provide a route or use --demo")
|
||||
|
||||
print(f"Reading logs from: {route}")
|
||||
|
||||
proc_logs = []
|
||||
device_states = []
|
||||
for msg in LogReader(route):
|
||||
if msg.which() == 'procLog':
|
||||
proc_logs.append(msg)
|
||||
elif msg.which() == 'deviceState':
|
||||
device_states.append(msg)
|
||||
|
||||
print_report(proc_logs, device_states)
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
from opendbc.car.values import BRANDS
|
||||
|
||||
for brand in BRANDS:
|
||||
all_flags = set()
|
||||
for platform in brand:
|
||||
if platform.config.flags != 0:
|
||||
all_flags |= set(platform.config.flags)
|
||||
|
||||
if len(all_flags):
|
||||
print(brand.__module__.split('.')[-2].upper() + ':')
|
||||
for flag in sorted(all_flags):
|
||||
print(f' {flag.name:<24}:', {platform.name for platform in brand.with_flags(flag)})
|
||||
print()
|
||||
@@ -0,0 +1 @@
|
||||
clpeak/
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
cd $DIR
|
||||
|
||||
if [ ! -d "$DIR/clpeak" ]; then
|
||||
git clone https://github.com/krrishnarraj/clpeak.git
|
||||
|
||||
cd clpeak
|
||||
git fetch
|
||||
git checkout ec2d3e70e1abc7738b81f9277c7af79d89b2133b
|
||||
git reset --hard origin/master
|
||||
git submodule update --init --recursive --remote
|
||||
|
||||
git apply ../run_continuously.patch
|
||||
fi
|
||||
|
||||
cd clpeak
|
||||
mkdir build || true
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
@@ -0,0 +1,39 @@
|
||||
diff --git a/src/logger.cpp b/src/logger.cpp
|
||||
index a63c6dd..a1d9860 100644
|
||||
--- a/src/logger.cpp
|
||||
+++ b/src/logger.cpp
|
||||
@@ -24,34 +24,22 @@ logger::~logger()
|
||||
|
||||
void logger::print(string str)
|
||||
{
|
||||
- cout << str;
|
||||
- cout.flush();
|
||||
}
|
||||
|
||||
void logger::print(double val)
|
||||
{
|
||||
- cout << setprecision(2) << fixed;
|
||||
- cout << val;
|
||||
- cout.flush();
|
||||
}
|
||||
|
||||
void logger::print(float val)
|
||||
{
|
||||
- cout << setprecision(2) << fixed;
|
||||
- cout << val;
|
||||
- cout.flush();
|
||||
}
|
||||
|
||||
void logger::print(int val)
|
||||
{
|
||||
- cout << val;
|
||||
- cout.flush();
|
||||
}
|
||||
|
||||
void logger::print(unsigned int val)
|
||||
{
|
||||
- cout << val;
|
||||
- cout.flush();
|
||||
}
|
||||
|
||||
void logger::xmlOpenTag(string tag)
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/src/clpeak.cpp b/src/clpeak.cpp
|
||||
index 8cb192b..b6fe6f5 100644
|
||||
--- a/src/clpeak.cpp
|
||||
+++ b/src/clpeak.cpp
|
||||
@@ -47,7 +47,7 @@ int clPeak::runAll()
|
||||
|
||||
log->xmlOpenTag("clpeak");
|
||||
log->xmlAppendAttribs("os", OS_NAME);
|
||||
- for (size_t p = 0; p < platforms.size(); p++)
|
||||
+ for (size_t p = 0; p < platforms.size(); (p+1 % platforms.size()))
|
||||
{
|
||||
if (forcePlatform && (p != specifiedPlatform))
|
||||
continue;
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd /sys/kernel/tracing
|
||||
|
||||
echo 1 > tracing_on
|
||||
echo boot > trace_clock
|
||||
echo 1000 > buffer_size_kb
|
||||
|
||||
# /sys/kernel/tracing/available_events
|
||||
echo 0 > events/enable
|
||||
#echo 1 > events/irq/enable
|
||||
#echo 1 > events/sched/enable
|
||||
#echo 1 > events/kgsl/enable
|
||||
#echo 1 > events/camera/enable
|
||||
echo 1 > events/workqueue/enable
|
||||
|
||||
echo > trace
|
||||
sleep 2
|
||||
echo 0 > tracing_on
|
||||
|
||||
cp trace /tmp/trace
|
||||
chown comma: /tmp/trace
|
||||
echo /tmp/trace
|
||||
@@ -0,0 +1,2 @@
|
||||
palanteer/
|
||||
viewer
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $DIR
|
||||
|
||||
if [ ! -d palanteer ]; then
|
||||
git clone https://github.com/dfeneyrou/palanteer
|
||||
pip install wheel
|
||||
sudo apt install libunwind-dev libdw-dev
|
||||
fi
|
||||
|
||||
cd palanteer
|
||||
git pull
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j$(nproc)
|
||||
|
||||
pip install --force-reinstall python/dist/palanteer*.whl
|
||||
|
||||
cp bin/palanteer $DIR/viewer
|
||||
@@ -0,0 +1,7 @@
|
||||
trace_*
|
||||
|
||||
tracebox
|
||||
trace_processor
|
||||
|
||||
perfetto/
|
||||
configs/
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ ! -d perfetto ]; then
|
||||
git clone https://android.googlesource.com/platform/external/perfetto/
|
||||
fi
|
||||
|
||||
cd perfetto
|
||||
|
||||
tools/install-build-deps --linux-arm
|
||||
tools/gn gen --args='is_debug=false target_os="linux" target_cpu="arm64"' out/linux
|
||||
tools/ninja -C out/linux tracebox traced traced_probes perfetto
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEST=tici:/data/openpilot/selfdrive/debug/profiling/perfetto
|
||||
|
||||
scp -r perfetto/out/linux/tracebox $DEST
|
||||
scp -r perfetto/test/configs $DEST
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||
cd $DIR
|
||||
|
||||
OUT=trace_
|
||||
sudo ./tracebox -o $OUT --txt -c configs/scheduling.cfg
|
||||
sudo chown $USER:$USER $OUT
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
curl -LO https://get.perfetto.dev/trace_processor
|
||||
chmod +x ./trace_processor
|
||||
|
||||
./trace_processor --httpd
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEST=tici:/data/openpilot/selfdrive/debug/profiling/perfetto
|
||||
|
||||
scp tici:/data/openpilot/selfdrive/debug/profiling/perfetto/trace_* .
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# find process with name passed in (excluding this process)
|
||||
for PID in $(pgrep -f $1); do
|
||||
if [ "$PID" != "$$" ]; then
|
||||
ps -p $PID -o args
|
||||
TRACE_PID=$PID
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$TRACE_PID" ]; then
|
||||
echo "could not find PID for $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo env PATH=$PATH py-spy record -d 5 -o /tmp/perf$TRACE_PID.svg -p $TRACE_PID &&
|
||||
google-chrome /tmp/perf$TRACE_PID.svg
|
||||
@@ -0,0 +1 @@
|
||||
SnapdragonProfiler/
|
||||
@@ -0,0 +1,13 @@
|
||||
snapdragon profiler
|
||||
--------
|
||||
|
||||
|
||||
* download from https://developer.qualcomm.com/software/snapdragon-profiler/tools-archive (need a qc developer account)
|
||||
* choose v2021.5 (verified working with 24.04 dev environment)
|
||||
* unzip to openpilot/selfdrive/debug/profiling/snapdragon/SnapdragonProfiler
|
||||
* run ```./setup-profiler.sh```
|
||||
* run ```./setup-agnos.sh```
|
||||
* run ```openpilot/selfdrive/debug/adb.sh``` on device
|
||||
* run the ```adb connect xxx``` command that was given to you on local pc
|
||||
* cd to SnapdragonProfiler and run ```./run_sdp.sh```
|
||||
* connect to device -> choose device you just setup
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# TODO: there's probably a better way to do this
|
||||
|
||||
cd SnapdragonProfiler/service
|
||||
mv android real_android
|
||||
ln -s agl/ android
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# install depends
|
||||
sudo apt update
|
||||
sudo apt-get install libc++1 libc++abi1 default-jre android-tools-adb gtk-sharp2
|
||||
|
||||
# setup mono
|
||||
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
|
||||
sudo apt install apt-transport-https ca-certificates
|
||||
echo "deb https://download.mono-project.com/repo/ubuntu stable-xenial main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
|
||||
sudo apt update
|
||||
sudo apt-get install -y mono-complete
|
||||
|
||||
echo "Setup successful, you should now be able to run the profiler with cd SnapdragonProfiler and ./run_sdp.sh"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
RUBYOPT="-W0" irqtop -d1 -R
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import zstandard as zstd
|
||||
from collections import defaultdict
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
from openpilot.common.utils import LOG_COMPRESSION_LEVEL
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
from tqdm import tqdm
|
||||
|
||||
MIN_SIZE = 0.5 # Percent size of total to show as separate entry
|
||||
|
||||
|
||||
def make_pie(msgs, typ):
|
||||
msgs_by_type = defaultdict(list)
|
||||
for m in msgs:
|
||||
msgs_by_type[m.which()].append(m.as_builder().to_bytes())
|
||||
|
||||
total = len(zstd.compress(b"".join([m.as_builder().to_bytes() for m in msgs]), LOG_COMPRESSION_LEVEL))
|
||||
uncompressed_total = len(b"".join([m.as_builder().to_bytes() for m in msgs]))
|
||||
|
||||
length_by_type = {k: len(b"".join(v)) for k, v in msgs_by_type.items()}
|
||||
# calculate compressed size by calculating diff when removed from the segment
|
||||
compressed_length_by_type = {}
|
||||
for k in tqdm(msgs_by_type.keys(), desc="Compressing"):
|
||||
compressed_length_by_type[k] = total - len(zstd.compress(b"".join([m.as_builder().to_bytes() for m in msgs if m.which() != k]), LOG_COMPRESSION_LEVEL))
|
||||
|
||||
sizes = sorted(compressed_length_by_type.items(), key=lambda kv: kv[1])
|
||||
|
||||
print("name - comp. size (uncomp. size)")
|
||||
for (name, sz) in sizes:
|
||||
print(f"{name:<22} - {sz / 1024:.2f} kB ({length_by_type[name] / 1024:.2f} kB)")
|
||||
print()
|
||||
print(f"{typ} - Real total {total / 1024:.2f} kB")
|
||||
print(f"{typ} - Breakdown total {sum(compressed_length_by_type.values()) / 1024:.2f} kB")
|
||||
print(f"{typ} - Uncompressed total {uncompressed_total / 1024 / 1024:.2f} MB")
|
||||
|
||||
sizes_large = [(k, sz) for (k, sz) in sizes if sz >= total * MIN_SIZE / 100]
|
||||
sizes_large += [('other', sum(sz for (_, sz) in sizes if sz < total * MIN_SIZE / 100))]
|
||||
|
||||
labels, sizes = zip(*sizes_large, strict=True)
|
||||
|
||||
plt.figure()
|
||||
plt.title(f"{typ}")
|
||||
plt.pie(sizes, labels=labels, autopct='%1.1f%%')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='View log size breakdown by message type')
|
||||
parser.add_argument('route', help='route to use')
|
||||
parser.add_argument('--as-qlog', action='store_true', help='decimate rlog using latest decimation factors')
|
||||
args = parser.parse_args()
|
||||
|
||||
msgs = list(LogReader(args.route))
|
||||
|
||||
if args.as_qlog:
|
||||
new_msgs = []
|
||||
msg_cnts: dict[str, int] = defaultdict(int)
|
||||
for msg in msgs:
|
||||
msg_which = msg.which()
|
||||
if msg.which() in ("initData", "sentinel"):
|
||||
new_msgs.append(msg)
|
||||
continue
|
||||
|
||||
if msg_which not in SERVICE_LIST:
|
||||
continue
|
||||
|
||||
decimation = SERVICE_LIST[msg_which].decimation
|
||||
if decimation is not None and msg_cnts[msg_which] % decimation == 0:
|
||||
new_msgs.append(msg)
|
||||
msg_cnts[msg_which] += 1
|
||||
|
||||
msgs = new_msgs
|
||||
|
||||
make_pie(msgs, 'qlog')
|
||||
plt.show()
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
|
||||
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process
|
||||
from openpilot.selfdrive.test.process_replay.test_processes import EXCLUDED_PROCS
|
||||
from openpilot.tools.lib.logreader import LogReader, save_log
|
||||
|
||||
ALLOW_PROCS = {c.proc_name for c in CONFIGS}
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Run process on route and create new logs",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("route", help="The route name to use")
|
||||
parser.add_argument("--fingerprint", help="The fingerprint to use")
|
||||
parser.add_argument("--whitelist-procs", nargs='*', default=ALLOW_PROCS, help="Whitelist given processes (e.g. controlsd)")
|
||||
parser.add_argument("--blacklist-procs", nargs='*', default=EXCLUDED_PROCS, help="Blacklist given processes (e.g. controlsd)")
|
||||
args = parser.parse_args()
|
||||
|
||||
allowed_procs = set(args.whitelist_procs) - set(args.blacklist_procs)
|
||||
cfgs = [c for c in CONFIGS if c.proc_name in allowed_procs]
|
||||
|
||||
inputs = list(LogReader(args.route))
|
||||
outputs = replay_process(cfgs, inputs, fingerprint=args.fingerprint)
|
||||
|
||||
# Remove message generated by the process under test and merge in the new messages
|
||||
produces = {o.which() for o in outputs}
|
||||
inputs = [i for i in inputs if i.which() not in produces]
|
||||
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
|
||||
|
||||
fn = f"{args.route.replace('/', '_')}_{'_'.join(allowed_procs)}.zst"
|
||||
print(f"Saving log to {fn}")
|
||||
save_log(fn, outputs)
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
while true; do
|
||||
if ls /dev/serial/by-id/usb-FTDI_FT230X* 2> /dev/null; then
|
||||
sudo screen /dev/serial/by-id/usb-FTDI_FT230X* 115200
|
||||
fi
|
||||
sleep 0.005
|
||||
done
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
from opendbc.car.structs import car
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.tools.lib.route import Route
|
||||
from openpilot.tools.lib.logreader import LogReader
|
||||
|
||||
if __name__ == "__main__":
|
||||
CP = None
|
||||
if len(sys.argv) > 1:
|
||||
r = Route(sys.argv[1])
|
||||
cps = [m for m in LogReader(r.qlog_paths()[0]) if m.which() == 'carParams']
|
||||
CP = cps[0].carParams.as_builder()
|
||||
else:
|
||||
CP = car.CarParams.new_message()
|
||||
CP.openpilotLongitudinalControl = True
|
||||
CP.alphaLongitudinalAvailable = False
|
||||
|
||||
cp_bytes = CP.to_bytes()
|
||||
for p in ("CarParams", "CarParamsCache", "CarParamsPersistent"):
|
||||
Params().put(p, cp_bytes, block=True)
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
from openpilot.common.params import Params
|
||||
import sys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"{sys.argv[0]} <github username>")
|
||||
exit(1)
|
||||
|
||||
username = sys.argv[1]
|
||||
keys = requests.get(f"https://github.com/{username}.keys", timeout=10)
|
||||
|
||||
if keys.status_code == 200:
|
||||
params = Params()
|
||||
params.put_bool("SshEnabled", True, block=True)
|
||||
params.put("GithubSshKeys", keys.text, block=True)
|
||||
params.put("GithubUsername", username, block=True)
|
||||
print("Set up ssh keys successfully")
|
||||
else:
|
||||
print("Error getting public keys from github")
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import re
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.tools.lib.auth_config import get_token
|
||||
from openpilot.tools.lib.api import CommaApi
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="A helper for connecting to devices over the comma prime SSH proxy.\
|
||||
Adding your SSH key to your SSH config is recommended for more convenient use; see https://docs.comma.ai/how-to/connect-to-comma/.")
|
||||
parser.add_argument("device", help="device name or dongle id")
|
||||
parser.add_argument("--host", help="ssh jump server host", default="ssh.comma.ai")
|
||||
parser.add_argument("--port", help="ssh jump server port", default=22, type=int)
|
||||
parser.add_argument("--key", help="ssh key", default=os.path.join(BASEDIR, "openpilot/common/hardware/tici/id_rsa"))
|
||||
parser.add_argument("--debug", help="enable debug output", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
r = CommaApi(get_token()).get("v1/me/devices")
|
||||
devices = {x['dongle_id']: x['alias'] for x in r}
|
||||
|
||||
if not re.match("[0-9a-zA-Z]{16}", args.device):
|
||||
user_input = args.device.replace(" ", "").lower()
|
||||
matches = { k: v for k, v in devices.items() if isinstance(v, str) and user_input in v.replace(" ", "").lower() }
|
||||
if len(matches) == 1:
|
||||
dongle_id = list(matches.keys())[0]
|
||||
else:
|
||||
print(f"failed to look up dongle id for \"{args.device}\"", file=sys.stderr)
|
||||
if len(matches) > 1:
|
||||
print("found multiple matches:", file=sys.stderr)
|
||||
for k, v in matches.items():
|
||||
print(f" \"{v}\" ({k})", file=sys.stderr)
|
||||
exit(1)
|
||||
else:
|
||||
dongle_id = args.device
|
||||
|
||||
name = dongle_id
|
||||
if dongle_id in devices:
|
||||
name = f"{devices[dongle_id]} ({dongle_id})"
|
||||
print(f"connecting to {name} through {args.host}:{args.port} ...")
|
||||
|
||||
command = [
|
||||
"ssh",
|
||||
"-i", args.key,
|
||||
"-o", f"ProxyCommand=ssh -i {args.key} -W %h:%p -p %p %h@{args.host}",
|
||||
"-p", str(args.port),
|
||||
]
|
||||
if args.debug:
|
||||
command += ["-v"]
|
||||
command += [
|
||||
f"comma@comma-{dongle_id}",
|
||||
]
|
||||
if args.debug:
|
||||
print(" ".join([f"'{c}'" if " " in c else c for c in command]))
|
||||
os.execvp(command[0], command)
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from collections import defaultdict
|
||||
import argparse
|
||||
import os
|
||||
import traceback
|
||||
from tqdm import tqdm
|
||||
from opendbc.car.car_helpers import interface_names
|
||||
from opendbc.car.fingerprints import MIGRATION
|
||||
from opendbc.car.fw_versions import VERSIONS, match_fw_to_car
|
||||
from openpilot.tools.lib.logreader import LogReader, ReadMode
|
||||
from openpilot.tools.lib.route import SegmentRange
|
||||
|
||||
|
||||
NO_API = "NO_API" in os.environ
|
||||
SUPPORTED_BRANDS = VERSIONS.keys()
|
||||
SUPPORTED_CARS = [brand for brand in SUPPORTED_BRANDS for brand in interface_names[brand]]
|
||||
UNKNOWN_BRAND = "unknown"
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Run FW fingerprint on Qlog of route or list of routes')
|
||||
parser.add_argument('route', help='Route or file with list of routes')
|
||||
parser.add_argument('--car', help='Force comparison fingerprint to known car')
|
||||
args = parser.parse_args()
|
||||
|
||||
if os.path.exists(args.route):
|
||||
routes = list(open(args.route))
|
||||
else:
|
||||
routes = [args.route]
|
||||
|
||||
mismatches = defaultdict(list)
|
||||
|
||||
not_fingerprinted = 0
|
||||
solved_by_fuzzy = 0
|
||||
|
||||
good_exact = 0
|
||||
wrong_fuzzy = 0
|
||||
good_fuzzy = 0
|
||||
|
||||
dongles = []
|
||||
for route in tqdm(routes):
|
||||
sr = SegmentRange(route)
|
||||
dongle_id = sr.dongle_id
|
||||
|
||||
if dongle_id in dongles:
|
||||
continue
|
||||
|
||||
if sr.slice == '' and sr.selector is None:
|
||||
route += '/0'
|
||||
|
||||
lr = LogReader(route, default_mode=ReadMode.QLOG)
|
||||
|
||||
try:
|
||||
dongles.append(dongle_id)
|
||||
|
||||
CP = None
|
||||
for msg in lr:
|
||||
if msg.which() == "pandaStates":
|
||||
if msg.pandaStates[0].pandaType in ('unknown', 'whitePanda', 'greyPanda', 'pedal'):
|
||||
print("wrong panda type")
|
||||
break
|
||||
|
||||
elif msg.which() == "carParams":
|
||||
CP = msg.carParams
|
||||
car_fw = [fw for fw in CP.carFw if not fw.logging]
|
||||
if len(car_fw) == 0:
|
||||
print("WARNING: no fw")
|
||||
|
||||
live_fingerprint = CP.carFingerprint
|
||||
live_fingerprint = MIGRATION.get(live_fingerprint, live_fingerprint)
|
||||
|
||||
if args.car is not None:
|
||||
live_fingerprint = args.car
|
||||
|
||||
if live_fingerprint not in SUPPORTED_CARS:
|
||||
print("not in supported cars")
|
||||
break
|
||||
|
||||
_, exact_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=True, allow_fuzzy=False)
|
||||
_, fuzzy_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=False, allow_fuzzy=True)
|
||||
|
||||
if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint):
|
||||
good_exact += 1
|
||||
print(f"Correct! Live: {live_fingerprint} - Fuzzy: {fuzzy_matches}")
|
||||
|
||||
# Check if fuzzy match was correct
|
||||
if len(fuzzy_matches) == 1:
|
||||
if list(fuzzy_matches)[0] != live_fingerprint:
|
||||
wrong_fuzzy += 1
|
||||
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
|
||||
else:
|
||||
good_fuzzy += 1
|
||||
break
|
||||
|
||||
print("Old style:", live_fingerprint, "Vin", CP.carVin)
|
||||
print("New style (exact):", exact_matches)
|
||||
print("New style (fuzzy):", fuzzy_matches)
|
||||
|
||||
padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw] + [0])
|
||||
for version in sorted(car_fw, key=lambda fw: fw.brand):
|
||||
subaddr = None if version.subAddress == 0 else hex(version.subAddress)
|
||||
print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - " +
|
||||
f"(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],")
|
||||
|
||||
print("Mismatches")
|
||||
found = False
|
||||
for brand in SUPPORTED_BRANDS:
|
||||
car_fws = VERSIONS[brand]
|
||||
if live_fingerprint in car_fws:
|
||||
found = True
|
||||
expected = car_fws[live_fingerprint]
|
||||
for (_, expected_addr, expected_sub_addr), v in expected.items():
|
||||
for version in car_fw:
|
||||
if version.brand != brand and len(version.brand):
|
||||
continue
|
||||
sub_addr = None if version.subAddress == 0 else version.subAddress
|
||||
addr = version.address
|
||||
|
||||
if (addr, sub_addr) == (expected_addr, expected_sub_addr):
|
||||
if version.fwVersion not in v:
|
||||
print(f"({hex(addr)}, {'None' if sub_addr is None else hex(sub_addr)}) - {version.fwVersion}")
|
||||
|
||||
# Add to global list of mismatches
|
||||
mismatch = (addr, sub_addr, version.fwVersion)
|
||||
if mismatch not in mismatches[live_fingerprint]:
|
||||
mismatches[live_fingerprint].append(mismatch)
|
||||
|
||||
# No FW versions for this car yet, add them all to mismatch list
|
||||
if not found:
|
||||
for version in car_fw:
|
||||
sub_addr = None if version.subAddress == 0 else version.subAddress
|
||||
addr = version.address
|
||||
mismatch = (addr, sub_addr, version.fwVersion)
|
||||
if mismatch not in mismatches[live_fingerprint]:
|
||||
mismatches[live_fingerprint].append(mismatch)
|
||||
|
||||
print()
|
||||
not_fingerprinted += 1
|
||||
|
||||
if len(fuzzy_matches) == 1:
|
||||
if list(fuzzy_matches)[0] == live_fingerprint:
|
||||
solved_by_fuzzy += 1
|
||||
else:
|
||||
wrong_fuzzy += 1
|
||||
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
|
||||
|
||||
break
|
||||
|
||||
if CP is None:
|
||||
print("no CarParams in logs")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
print()
|
||||
# Print FW versions that need to be added separated out by car and address
|
||||
for car, m in sorted(mismatches.items()):
|
||||
print(car)
|
||||
addrs = defaultdict(list)
|
||||
for (addr, sub_addr, version) in m:
|
||||
addrs[(addr, sub_addr)].append(version)
|
||||
|
||||
for (addr, sub_addr), versions in addrs.items():
|
||||
print(f" ({hex(addr)}, {'None' if sub_addr is None else hex(sub_addr)}): [")
|
||||
for v in versions:
|
||||
print(f" {v},")
|
||||
print(" ]")
|
||||
print()
|
||||
|
||||
print()
|
||||
print(f"Number of dongle ids checked: {len(dongles)}")
|
||||
print(f"Fingerprinted: {good_exact}")
|
||||
print(f"Not fingerprinted: {not_fingerprinted}")
|
||||
print(f" of which had a fuzzy match: {solved_by_fuzzy}")
|
||||
|
||||
print()
|
||||
print(f"Correct fuzzy matches: {good_fuzzy}")
|
||||
print(f"Wrong fuzzy matches: {wrong_fuzzy}")
|
||||
print()
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
|
||||
from openpilot.cereal import log, messaging
|
||||
from opendbc.car.structs import car
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
|
||||
if __name__ == "__main__":
|
||||
CP = car.CarParams(notCar=True, wheelbase=1, steerRatio=10)
|
||||
Params().put("CarParams", CP.to_bytes(), block=True)
|
||||
|
||||
procs = ['camerad', 'ui', 'modeld', 'calibrationd', 'plannerd', 'dmonitoringmodeld', 'dmonitoringd']
|
||||
for p in procs:
|
||||
managed_processes[p].start()
|
||||
|
||||
pm = messaging.PubMaster(['controlsState', 'deviceState', 'pandaStates', 'carParams'])
|
||||
|
||||
msgs = {s: messaging.new_message(s) for s in ['controlsState', 'deviceState', 'carParams']}
|
||||
msgs['deviceState'].deviceState.started = True
|
||||
msgs['deviceState'].deviceState.deviceType = HARDWARE.get_device_type()
|
||||
msgs['carParams'].carParams.openpilotLongitudinalControl = True
|
||||
|
||||
msgs['pandaStates'] = messaging.new_message('pandaStates', 1)
|
||||
msgs['pandaStates'].pandaStates[0].ignitionLine = True
|
||||
msgs['pandaStates'].pandaStates[0].pandaType = log.PandaState.PandaType.uno
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1 / 100) # continually send, rate doesn't matter
|
||||
for s in msgs:
|
||||
pm.send(s, msgs[s])
|
||||
except KeyboardInterrupt:
|
||||
for p in procs:
|
||||
managed_processes[p].stop()
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import datetime
|
||||
import time
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import numpy as np
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceTiming:
|
||||
times: list[float] = field(default_factory=list)
|
||||
window: deque[float] = field(default_factory=lambda: deque(maxlen=100))
|
||||
valids: deque[bool] = field(default_factory=lambda: deque(maxlen=100))
|
||||
lag_events: list[tuple[float, float]] = field(default_factory=list)
|
||||
|
||||
def add(self, mono_time: float, valid: bool, expected_interval: float | None, lag_threshold: float) -> None:
|
||||
if self.times:
|
||||
dt = mono_time - self.times[-1]
|
||||
self.window.append(dt)
|
||||
if expected_interval is not None and dt > lag_threshold * expected_interval:
|
||||
self.lag_events.append((mono_time, dt))
|
||||
|
||||
self.times.append(mono_time)
|
||||
self.valids.append(valid)
|
||||
|
||||
def intervals(self, latest_only: bool) -> np.ndarray:
|
||||
if latest_only:
|
||||
return np.array(self.window)
|
||||
return np.diff(self.times)
|
||||
|
||||
|
||||
def format_row(name: str, timing: ServiceTiming, latest_only: bool) -> str:
|
||||
dts = timing.intervals(latest_only)
|
||||
if len(dts) == 0:
|
||||
return f"{name:25} waiting for messages"
|
||||
|
||||
mean = np.mean(dts)
|
||||
hz = 1.0 / mean if mean > 0 else 0.0
|
||||
valid = all(timing.valids) if timing.valids else False
|
||||
return f"{name:25} {hz:8.2f}Hz {mean * 1e3:8.2f}ms {np.std(dts) * 1e3:8.2f}ms {np.max(dts) * 1e3:8.2f}ms {np.min(dts) * 1e3:8.2f}ms valid={valid}"
|
||||
|
||||
|
||||
def print_lag_events(name: str, timing: ServiceTiming, printed_lags: dict[str, int]) -> None:
|
||||
start = printed_lags.get(name, 0)
|
||||
for mono_time, dt in timing.lag_events[start:]:
|
||||
print(f"{mono_time:.3f} {name} lag {dt:.3f}s", flush=True)
|
||||
printed_lags[name] = len(timing.lag_events)
|
||||
|
||||
|
||||
def monitor_services(socket_names: list[str], print_interval: float, lag_threshold: float, lag_only: bool) -> None:
|
||||
sockets = {name: messaging.sub_sock(name, conflate=False) for name in socket_names}
|
||||
timings = {name: ServiceTiming() for name in socket_names}
|
||||
printed_lags: dict[str, int] = {}
|
||||
|
||||
start_time = time.monotonic()
|
||||
last_print = start_time
|
||||
|
||||
try:
|
||||
while True:
|
||||
for name, sock in sockets.items():
|
||||
for msg in messaging.drain_sock(sock):
|
||||
expected_interval = 1.0 / SERVICE_LIST[name].frequency if name in SERVICE_LIST else None
|
||||
timings[name].add(msg.logMonoTime / 1e9, msg.valid, expected_interval, lag_threshold)
|
||||
|
||||
now = time.monotonic()
|
||||
if now - last_print < print_interval:
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
|
||||
if not lag_only:
|
||||
print(flush=True)
|
||||
print(f"{'service':25} {'freq':>10} {'mean':>10} {'std':>10} {'max':>10} {'min':>10} valid", flush=True)
|
||||
for name in socket_names:
|
||||
print(format_row(name, timings[name], latest_only=True), flush=True)
|
||||
|
||||
for name in socket_names:
|
||||
print_lag_events(name, timings[name], printed_lags)
|
||||
|
||||
last_print = now
|
||||
except KeyboardInterrupt:
|
||||
print("\n", flush=True)
|
||||
print("=" * 5, "timing summary", "=" * 5, flush=True)
|
||||
print(f"{'service':25} {'freq':>10} {'mean':>10} {'std':>10} {'max':>10} {'min':>10} valid", flush=True)
|
||||
for name in socket_names:
|
||||
print(format_row(name, timings[name], latest_only=False), flush=True)
|
||||
print("=" * 5, datetime.timedelta(seconds=time.monotonic() - start_time), "=" * 5, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Check live service timing, frequency, validity, and lag")
|
||||
parser.add_argument("socket", nargs="*", default=["carState"], help="service/socket name")
|
||||
parser.add_argument("--lag-threshold", type=float, default=10.0, help="report intervals above this multiple of the expected service interval")
|
||||
parser.add_argument("--lag-only", action="store_true", help="only print lag events")
|
||||
parser.add_argument("--print-interval", type=float, default=1.0, help="seconds between table updates")
|
||||
args = parser.parse_args()
|
||||
|
||||
monitor_services(args.socket, args.print_interval, args.lag_threshold, args.lag_only)
|
||||
Reference in New Issue
Block a user