juggle.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import platform
  5. import shutil
  6. import subprocess
  7. import tarfile
  8. import tempfile
  9. import requests
  10. import argparse
  11. from functools import partial
  12. from openpilot.common.basedir import BASEDIR
  13. from openpilot.tools.lib.helpers import save_log
  14. from openpilot.tools.lib.logreader import LogReader, ReadMode
  15. juggle_dir = os.path.dirname(os.path.realpath(__file__))
  16. DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"
  17. RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest"
  18. INSTALL_DIR = os.path.join(juggle_dir, "bin")
  19. PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler")
  20. MINIMUM_PLOTJUGGLER_VERSION = (3, 5, 2)
  21. MAX_STREAMING_BUFFER_SIZE = 1000
  22. def install():
  23. m = f"{platform.system()}-{platform.machine()}"
  24. supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64", "Darwin-x86_64")
  25. if m not in supported:
  26. raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}")
  27. if os.path.exists(INSTALL_DIR):
  28. shutil.rmtree(INSTALL_DIR)
  29. os.mkdir(INSTALL_DIR)
  30. url = os.path.join(RELEASES_URL, m + ".tar.gz")
  31. with requests.get(url, stream=True, timeout=10) as r, tempfile.NamedTemporaryFile() as tmp:
  32. r.raise_for_status()
  33. with open(tmp.name, 'wb') as tmpf:
  34. for chunk in r.iter_content(chunk_size=1024*1024):
  35. tmpf.write(chunk)
  36. with tarfile.open(tmp.name) as tar:
  37. tar.extractall(path=INSTALL_DIR)
  38. def get_plotjuggler_version():
  39. out = subprocess.check_output([PLOTJUGGLER_BIN, "-v"], encoding="utf-8").strip()
  40. version = out.split(" ")[1]
  41. return tuple(map(int, version.split(".")))
  42. def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None):
  43. env = os.environ.copy()
  44. env["BASEDIR"] = BASEDIR
  45. env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}"
  46. if dbc:
  47. env["DBC_NAME"] = dbc
  48. extra_args = ""
  49. if fn is not None:
  50. extra_args += f" -d {fn}"
  51. if layout is not None:
  52. extra_args += f" -l {layout}"
  53. if route_or_segment_name is not None:
  54. extra_args += f" --window_title \"{route_or_segment_name}\""
  55. cmd = f'{PLOTJUGGLER_BIN} --buffer_size {MAX_STREAMING_BUFFER_SIZE} --plugin_folders {INSTALL_DIR}{extra_args}'
  56. subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir)
  57. def process(can, lr):
  58. return [d for d in lr if can or d.which() not in ['can', 'sendcan']]
  59. def juggle_route(route_or_segment_name, can, layout, dbc=None):
  60. sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE)
  61. all_data = sr.run_across_segments(24, partial(process, can))
  62. # Infer DBC name from logs
  63. if dbc is None:
  64. for cp in [m for m in all_data if m.which() == 'carParams']:
  65. try:
  66. DBC = __import__(f"openpilot.selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC
  67. dbc = DBC[cp.carParams.carFingerprint]['pt']
  68. except Exception:
  69. pass
  70. break
  71. with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp:
  72. save_log(tmp.name, all_data, compress=False)
  73. del all_data
  74. start_juggler(tmp.name, dbc, layout, route_or_segment_name)
  75. if __name__ == "__main__":
  76. parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes",
  77. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  78. parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one")
  79. parser.add_argument("--can", action="store_true", help="Parse CAN data")
  80. parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode")
  81. parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout")
  82. parser.add_argument("--install", action="store_true", help="Install or update PlotJuggler + plugins")
  83. parser.add_argument("--dbc", help="Set the DBC name to load for parsing CAN data. If not set, the DBC will be automatically inferred from the logs.")
  84. parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot (cabana share URL accepted)")
  85. if len(sys.argv) == 1:
  86. parser.print_help()
  87. sys.exit()
  88. args = parser.parse_args()
  89. if args.install:
  90. install()
  91. sys.exit()
  92. if not os.path.exists(PLOTJUGGLER_BIN):
  93. print("PlotJuggler is missing. Downloading...")
  94. install()
  95. if get_plotjuggler_version() < MINIMUM_PLOTJUGGLER_VERSION:
  96. print("PlotJuggler is out of date. Installing update...")
  97. install()
  98. if args.stream:
  99. start_juggler(layout=args.layout)
  100. else:
  101. route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip()
  102. juggle_route(route_or_segment_name, args.can, args.layout, args.dbc)