A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `a2a0ccea32023010/2023-07-27--13-01-19/0:1`, `https://connect.comma.ai/a2a0ccea32023010/2023-07-27--13-01-19/`)
- The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider.
- The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached).
- There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/"
- You can drag and drop any numeric/boolean field from the timeseries list into a timeseries panel.
- You can create more panels with the split buttons (buttons with two rectangles, either horizontal or vertical). You can resize the panels by dragging the grip in between any panel.
- You can load and save layouts with the corresponding buttons. Layouts will save all tabs, panels, titles, timeseries, etc.
## Layouts
If you create a layout that's useful for others, consider upstreaming it.
## Plot Interaction Controls
- **Left click and drag within the plot area** to pan X
- Left click and drag on an axis to pan an individual axis (disabled for Y-axis)
- **Scroll in the plot area** to zoom in X axes, Y-axis is autofit
- Scroll on an axis to zoom an individual axis
- **Right click and drag** to select data and zoom into the selected data
- Left click while box selecting to cancel the selection
- **Double left click** to fit all visible data
- Double left click on an axis to fit the individual axis (disabled for Y-axis, always autofit)
- **Double right click** to open the plot context menu
- **Click legend label icons** to show/hide plot items
mouse_x,mouse_y=pyautogui.position()# TODO: find better way of creating the window where the user is (default dpg behavior annoying on multiple displays)
mouse_x,mouse_y=pyautogui.position()# TODO: find better way of creating the window where the user is (default dpg behavior annoying on multiple displays)
@ -223,6 +337,14 @@ def main(route_to_load=None):
controller=MainController(scale=scale)
controller=MainController(scale=scale)
controller.setup_ui()
controller.setup_ui()
iflayout_to_load:
try:
controller.load_layout_from_yaml(layout_to_load)
print(f"Loaded layout from {layout_to_load}")
exceptExceptionase:
print(f"Failed to load layout from {layout_to_load}: {e}")
cloudlog.exception(f"Error loading layout from {layout_to_load}")
ifroute_to_load:
ifroute_to_load:
dpg.set_value("route_input",route_to_load)
dpg.set_value("route_input",route_to_load)
controller.load_route()
controller.load_route()
@ -241,7 +363,8 @@ def main(route_to_load=None):
if__name__=="__main__":
if__name__=="__main__":
parser=argparse.ArgumentParser(description="A tool for visualizing openpilot logs.")
parser=argparse.ArgumentParser(description="A tool for visualizing openpilot logs.")
parser.add_argument("--demo",action="store_true",help="Use the demo route instead of providing one")
parser.add_argument("--demo",action="store_true",help="Use the demo route instead of providing one")
parser.add_argument("--layout",type=str,help="Path to YAML layout file to load on startup")
parser.add_argument("route",nargs='?',default=None,help="Optional route name to load on startup.")
parser.add_argument("route",nargs='?',default=None,help="Optional route name to load on startup.")
<librarycode="--[[ Helper function to create a series from arrays

 new_series: a series previously created with ScatterXY.new(name)
 prefix: prefix of the timeseries, before the index of the array
 suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.
 suffix_Y: suffix to complete the name of the series containing the Y value
 timestamp: usually the tracker_time variable
 
 Example:
 
 Assuming we have multiple series in the form:
 
 /trajectory/node.{X}/position/x
 /trajectory/node.{X}/position/y
 
 where {N} is the index of the array (integer). We can create a reactive series from the array with:
 
 new_series = ScatterXY.new("my_trajectory") 
 CreateSeriesFromArray( new_series, "/trajectory/node", "position/x", "position/y", tracker_time );
--]]

function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )
 
 --- clear previous values
 new_series:clear()
 
 --- Append points to new_series
 index = 0
 while(true) do

 x = index;
 -- if not nil, get the X coordinate from a series
 if suffix_X ~= nil then 
 series_x = TimeseriesView.find( string.format( "%s.%d/%s", prefix, index, suffix_X) )
 if series_x == nil then break end
 x = series_x:atTime(timestamp)	 
 end
 
 series_y = TimeseriesView.find( string.format( "%s.%d/%s", prefix, index, suffix_Y) )
 if series_y == nil then break end 
 y = series_y:atTime(timestamp)
 
 new_series:push_back(x,y)
 index = index+1
 end
end

--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]

function GetSeriesNamesByPrefix(prefix)
 -- GetSeriesNames(9 is a built-in function
 all_names = GetSeriesNames()
 filtered_names = {}
 for i, name in ipairs(all_names) do
 -- check the prefix
 if name:find(prefix, 1, #prefix) then
 table.insert(filtered_names, name);
 end
 end
 return filtered_names
end

--[[ Modify an existing series, applying offsets to all their X and Y values

 series: an existing timeseries, obtained with TimeseriesView.find(name)
 delta_x: offset to apply to each x value
 delta_y: offset to apply to each y value 
 
--]]

function ApplyOffsetInPlace(series, delta_x, delta_y)
 -- use C++ indeces, not Lua indeces
 for index=0, series:size()-1 do
 x,y = series:at(index)
 series:set(index, x + delta_x, y + delta_y)
 end
end
"/>