Compare commits

..

4 Commits

Author SHA1 Message Date
royjr e0a5c99588 Update opendbc_repo 2026-07-01 22:54:58 -04:00
Jason Wen 31dc4d8e52 ci: fix cereal validation for upstream directory restructure (#1869) 2026-06-28 22:56:17 -04:00
Jason Wen da6313dbe9 Update CHANGELOG.md 2026-06-26 21:33:25 -04:00
Jason Wen 01a843e0ac ui: reset Enforce Torque Control and NNLC if both are enabled (#1863)
* ui: reset Enforce Torque Control and NNLC if both are enabled

* block
2026-06-09 02:15:42 -04:00
22 changed files with 111 additions and 3962 deletions
-1
View File
@@ -2,6 +2,5 @@ Wen
REGIST
PullRequest
cancelled
indeces
FOF
NoO
+22 -3
View File
@@ -35,18 +35,36 @@ jobs:
- name: Init sunnypilot opendbc submodule
run: git submodule update --init --depth 1 opendbc_repo
- name: Checkout upstream openpilot cereal
- name: Checkout upstream openpilot
uses: actions/checkout@v6
with:
repository: 'commaai/openpilot'
path: upstream_openpilot
sparse-checkout: cereal
ref: "refs/heads/master"
- name: Init upstream opendbc submodule
working-directory: upstream_openpilot
run: git submodule update --init --depth 1 opendbc_repo
- name: Locate upstream capnp paths
id: locate-capnp
run: |
CEREAL_DIR=$(find upstream_openpilot -maxdepth 4 -name log.capnp -path '*/cereal/log.capnp' -printf '%h\n' -quit)
if [ -z "$CEREAL_DIR" ]; then
echo "::error::Could not locate cereal/log.capnp in upstream openpilot"
exit 1
fi
echo "cereal_dir=$CEREAL_DIR" >> "$GITHUB_OUTPUT"
echo "Found upstream cereal at: $CEREAL_DIR"
IMPORT_ARGS=""
CAR_CAPNP=$(find upstream_openpilot -maxdepth 5 -name car.capnp -path '*/opendbc/car/car.capnp' -printf '%h\n' -quit)
if [ -n "$CAR_CAPNP" ]; then
IMPORT_ARGS="-I $CAR_CAPNP"
echo "Found car.capnp at: $CAR_CAPNP"
fi
echo "import_args=$IMPORT_ARGS" >> "$GITHUB_OUTPUT"
- name: Install uv
run: pip install uv
@@ -62,4 +80,5 @@ jobs:
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
-r -f /tmp/sp_schema.json --cereal-dir upstream_openpilot/cereal
-r -f /tmp/sp_schema.json --cereal-dir ${{ steps.locate-capnp.outputs.cereal_dir }} \
${{ steps.locate-capnp.outputs.import_args }}
-7
View File
@@ -21,12 +21,5 @@
</clean>
</configuration>
</target>
<target id="f2590b2b-9b93-49f9-8510-da3f3724a2ae" name="replay" defaultType="TOOL">
<configuration id="d475264f-6f4c-4092-9b4e-6773309f38b7" name="replay" toolchainName="Default">
<build type="TOOL">
<tool actionId="Tool_External Tools_uv build tools replay" />
</build>
</configuration>
</target>
</component>
</project>
-7
View File
@@ -20,11 +20,4 @@
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
<tool name="uv build tools replay" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
<exec>
<option name="COMMAND" value="bash" />
<option name="PARAMETERS" value="-c &quot;source .venv/bin/activate &amp;&amp; scons -u -j$(nproc) tools/replay/&quot;" />
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
</exec>
</tool>
</toolSet>
+1 -1
View File
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
<envs>
<env name="QT_DBL_CLICK_DIST" value="150" />
</envs>
-27
View File
@@ -1,27 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Debug Route Controls" type="PythonConfigurationType" factoryName="Python">
<module name="openpilot-special" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="FINGERPRINT" value="KIA_EV9" />
<env name="SKIP_FW_QUERY" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/selfdrive/car" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/selfdrive/car/card.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="true" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Replay for controls + ui" type="Multirun" separateTabs="false" reuseTabsWithFailures="false" startOneByOne="true" markFailedProcess="true" hideSuccessProcess="false" delayTime="0.0">
<runConfiguration name="replay for controls" type="Native Application" />
<runConfiguration name="Build Debug" type="Custom Build Application" />
<method v="2" />
</configuration>
</component>
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="replay for controls" type="CLionNativeAppRunConfigurationType" focusToolWindowBeforeRun="true" PROGRAM_PARAMS="&quot;$Prompt$&quot; --block &quot;sendcan,carState,carParams,carOutput,liveTracks,carParamsSP,carStateSP,bookmarkButton&quot;" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="true" WORKING_DIR="file://$ProjectFileDir$/tools/replay" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="replay" CONFIG_NAME="replay" version="1" RUN_PATH="replay">
<method v="2">
<option name="CLION.COMPOUND.BUILD" enabled="true" />
</method>
</configuration>
</component>
+30 -1
View File
@@ -1,5 +1,34 @@
sunnypilot Version 2026.002.000 (2026-xx-xx)
sunnypilot Version 2026.002.000 (2026-06-28)
========================
* What's Changed (sunnypilot/sunnypilot)
* ui: update gates for certain toggles by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1830
* release: ignore upstream IsReleaseBranch by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1831
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1833
* sunnylink: fix max time offroad values by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1835
* ui: show default model name by @nayan8teen in https://github.com/sunnypilot/sunnypilot/pull/1837
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1839
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1842
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1849
* modeld_v2: safe model validation by @Discountchubbs in https://github.com/sunnypilot/sunnypilot/pull/1855
* Revert "deprecate `carState.brake`" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/sunnypilot/pull/1860
* sunnylink: deprecate legacy params metadata by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1862
* ui: reset Enforce Torque Control and NNLC if both are enabled by @sunnyhaibin in https://github.com/sunnypilot/sunnypilot/pull/1863
* What's Changed (sunnypilot/opendbc)
* Rivian: suppress ACM hold-the-wheel warning during MADS-only lateral by @lukasloetkolben in https://github.com/sunnypilot/opendbc/pull/465
* Sync: `commaai/opendbc:master``sunnypilot/opendbc:master` by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/479
* safety: add option to ignore frequency check for RX checks by @sunnyhaibin in https://github.com/sunnypilot/opendbc/pull/480
* Revert "deprecate carState.brake" for Honda Gas Interceptor by @mvl-boston in https://github.com/sunnypilot/opendbc/pull/481
* New Contributors (sunnypilot/sunnypilot)
* @mvl-boston made their first contribution in https://github.com/sunnypilot/sunnypilot/pull/1860
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2026.001.007...v2026.002.000
************************
* Synced with commaai's openpilot (v0.11.1)
* master commit 69e2c321e49760e52f7983eaa0a5f77cb95de637 (June 02, 2026)
* New driver monitoring model
* Improved image processing pipeline for driver camera
* Improved thermal policy for comma four
* Acura MDX 2022-24 support thanks to mvl-boston!
* Rivian R1S and R1T 2025 support thanks to lukasloetkolben!
sunnypilot Version 2026.001.000 (2026-05-06)
========================
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
"""Schema-level cereal compat check between sunnypilot and upstream openpilot.
"""Validate sunnypilot routes are parseable by stock commaai/openpilot.
Rules (per struct matched across sides by typeId):
R1 shared ordinal must reference the same type.
R2 sunnypilot-only ordinal in a union -> FAIL (unknown discriminant upstream).
R3 sunnypilot-only ordinal on a regular field -> OK (additive struct evolution).
R4 upstream-only ordinal -> OK.
R5 sunnypilot-only struct referenced via an upstream-shared field -> FAIL.
Cap'n Proto is wire-compatible across renames, type relocations, and
additive fields. The only breaking change is a union variant that
upstream doesn't recognize — an unknown discriminant makes the entire
union unreadable.
This script checks: for every struct with a union that exists in both
schemas, does sunnypilot introduce union variants upstream doesn't have?
"""
from __future__ import annotations
@@ -24,46 +25,19 @@ def hex_id(value: int) -> str:
return f"0x{value:016x}"
def encode_type(type_node: Any) -> dict:
which = type_node.which()
if which == "struct":
return {"kind": "struct", "typeId": hex_id(type_node.struct.typeId)}
if which == "enum":
return {"kind": "enum", "typeId": hex_id(type_node.enum.typeId)}
if which == "interface":
return {"kind": "interface", "typeId": hex_id(type_node.interface.typeId)}
if which == "list":
return {"kind": "list", "element": encode_type(type_node.list.elementType)}
if which == "anyPointer":
return {"kind": "anyPointer"}
return {"kind": which}
def encode_field(name: str, field: Any) -> dict:
proto = field.proto
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
if proto.which() == "group":
type_desc = {"kind": "group", "typeId": hex_id(proto.group.typeId)}
else:
type_desc = encode_type(proto.slot.type)
return {
"name": name,
"ordinal": ordinal,
"discriminant": discriminant,
"type": type_desc,
}
def encode_struct(schema: Any) -> dict:
node = schema.node
fields = []
for name, field in schema.fields.items():
proto = field.proto
ordinal = proto.ordinal.explicit if proto.ordinal.which() == "explicit" else None
discriminant = proto.discriminantValue if proto.discriminantValue != NO_DISCRIMINANT else None
fields.append({"name": name, "ordinal": ordinal, "discriminant": discriminant})
return {
"typeId": hex_id(node.id),
"displayName": node.displayName,
"hasUnion": node.struct.discriminantCount > 0,
"fields": [encode_field(name, field) for name, field in schema.fields.items()],
"fields": fields,
}
@@ -105,15 +79,16 @@ def collect_schema(root: Any) -> dict[str, dict]:
return structs
def load_log(cereal_dir: str) -> Any:
def load_log(cereal_dir: str, extra_imports: list[str] | None = None) -> Any:
import capnp
cereal_dir = os.path.abspath(cereal_dir)
capnp.remove_import_hook()
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=[cereal_dir])
imports = [cereal_dir] + [os.path.abspath(p) for p in (extra_imports or [])]
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=imports)
def dump_schema(cereal_dir: str, path: str) -> None:
log = load_log(cereal_dir)
def dump_schema(cereal_dir: str, path: str, extra_imports: list[str] | None = None) -> None:
log = load_log(cereal_dir, extra_imports)
payload = {
"root": hex_id(log.Event.schema.node.id),
"structs": collect_schema(log.Event.schema),
@@ -123,100 +98,37 @@ def dump_schema(cereal_dir: str, path: str) -> None:
print(f"wrote schema dump with {len(payload['structs'])} structs to {path}")
def types_equal(a: dict, b: dict) -> bool:
if a.get("kind") != b.get("kind"):
return False
kind = a["kind"]
if kind in ("struct", "enum", "interface", "group"):
return a.get("typeId") == b.get("typeId")
if kind == "list":
return types_equal(a["element"], b["element"])
return True
def type_repr(t: dict) -> str:
kind = t.get("kind", "?")
if kind in ("struct", "enum", "interface", "group"):
return f"{kind}({t.get('typeId')})"
if kind == "list":
return f"list<{type_repr(t['element'])}>"
return kind
def field_is_union_variant(field: dict) -> bool:
return field.get("discriminant") is not None
def index_fields_by_ordinal(struct: dict) -> dict[int, dict]:
indexed: dict[int, dict] = {}
for field in struct["fields"]:
ordinal = field.get("ordinal")
if ordinal is None:
continue
indexed[ordinal] = field
return indexed
def compare(sunnypilot_dump: dict, upstream_dump: dict) -> list[str]:
violations: list[str] = []
sunnypilot_structs: dict[str, dict] = sunnypilot_dump["structs"]
upstream_structs: dict[str, dict] = upstream_dump["structs"]
sunnypilot_structs = sunnypilot_dump["structs"]
upstream_structs = upstream_dump["structs"]
sunnypilot_struct_referenced_from_shared: set[str] = set()
for type_id, sunnypilot_struct in sunnypilot_structs.items():
upstream_struct = upstream_structs.get(type_id)
if upstream_struct is None:
for type_id, sp_struct in sunnypilot_structs.items():
if not sp_struct["hasUnion"]:
continue
up_struct = upstream_structs.get(type_id)
if up_struct is None:
continue
sunnypilot_fields = index_fields_by_ordinal(sunnypilot_struct)
upstream_fields = index_fields_by_ordinal(upstream_struct)
display = sunnypilot_struct["displayName"]
up_ordinals = {f["ordinal"] for f in up_struct["fields"] if f.get("discriminant") is not None}
display = sp_struct["displayName"]
for ordinal, sunnypilot_field in sunnypilot_fields.items():
upstream_field = upstream_fields.get(ordinal)
if upstream_field is None:
if field_is_union_variant(sunnypilot_field):
violations.append(
f"[R2] {display} @{ordinal} ('{sunnypilot_field['name']}', {type_repr(sunnypilot_field['type'])}): "
f"union variant not present upstream. upstream cannot parse this discriminant."
)
for field in sp_struct["fields"]:
if field.get("discriminant") is None:
continue
if not types_equal(sunnypilot_field["type"], upstream_field["type"]):
if field["ordinal"] not in up_ordinals:
violations.append(
f"[R1] {display} @{ordinal}: type mismatch. "
f"sunnypilot='{sunnypilot_field['name']}' {type_repr(sunnypilot_field['type'])} vs "
f"upstream='{upstream_field['name']}' {type_repr(upstream_field['type'])}."
f"{display} @{field['ordinal']} '{field['name']}': "
f"union variant not present upstream (discriminant={field['discriminant']})"
)
continue
cursor = sunnypilot_field["type"]
while cursor.get("kind") == "list":
cursor = cursor["element"]
if cursor.get("kind") in ("struct", "group", "interface") and cursor.get("typeId"):
sunnypilot_struct_referenced_from_shared.add(cursor["typeId"])
for type_id, sunnypilot_struct in sunnypilot_structs.items():
if type_id in upstream_structs:
continue
if type_id in sunnypilot_struct_referenced_from_shared:
violations.append(
f"[R5] struct {sunnypilot_struct['displayName']} ({type_id}) exists only on sunnypilot "
f"but is referenced from an upstream-shared field. upstream cannot resolve this type."
)
return violations
def load_peer(path: str) -> dict:
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
def run_read(cereal_dir: str, peer_path: str) -> int:
log = load_log(cereal_dir)
peer_dump = load_peer(peer_path)
def run_read(cereal_dir: str, peer_path: str, extra_imports: list[str] | None = None) -> int:
log = load_log(cereal_dir, extra_imports)
with open(peer_path, "r", encoding="utf-8") as f:
peer_dump = json.load(f)
local_dump = {
"root": hex_id(log.Event.schema.node.id),
"structs": collect_schema(log.Event.schema),
@@ -224,32 +136,29 @@ def run_read(cereal_dir: str, peer_path: str) -> int:
violations = compare(sunnypilot_dump=peer_dump, upstream_dump=local_dump)
if not violations:
print("cereal compat OK: upstream openpilot can parse sunnypilot routes "
"(no leaked structs, no ordinal collisions).")
print("cereal compat OK: upstream can parse sunnypilot routes.")
return 0
print(f"cereal compat FAIL: upstream openpilot would misparse sunnypilot routes "
f"({len(violations)} violation(s)):")
print(f"cereal compat FAIL ({len(violations)} leaked union variant(s)):")
for v in violations:
print(f" {v}")
return 1
def main() -> int:
parser = argparse.ArgumentParser(
description="sunnypilot <-> upstream cereal compatibility validator (schema-level)."
)
parser = argparse.ArgumentParser(description="sunnypilot cereal upstream compat check")
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
mode.add_argument("-r", "--read", action="store_true", help="load peer JSON and diff against local")
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path (default: schema.json)")
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory containing log.capnp")
mode.add_argument("-r", "--read", action="store_true", help="validate against peer schema")
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path")
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory")
parser.add_argument("-I", "--import-path", action="append", default=[], help="extra capnp import paths")
args = parser.parse_args()
if args.generate:
dump_schema(args.cereal_dir, args.file)
dump_schema(args.cereal_dir, args.file, args.import_path)
return 0
return run_read(args.cereal_dir, args.file)
return run_read(args.cereal_dir, args.file, args.import_path)
if __name__ == "__main__":
-1
View File
@@ -226,7 +226,6 @@ exclude = [
"teleoprtc/",
"teleoprtc_repo/",
"third_party/",
"**/*.ipynb",
]
[tool.ty.rules]
+1 -1
View File
@@ -13,7 +13,7 @@ cd $ROOT
FAILED=0
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md|layouts\/.*\.xml|.*\.ipynb"
IGNORED_FILES="uv\.lock|docs\/CARS.md|LICENSE\.md"
IGNORED_DIRS="^msgq.*|^msgq_repo.*|^opendbc.*|^opendbc_repo.*|^cereal.*|^panda.*|^rednose.*|^rednose_repo.*|^tinygrad.*|^tinygrad_repo.*|^teleoprtc.*|^teleoprtc_repo.*|^third_party.*"
function run() {
@@ -134,6 +134,11 @@ class SteeringLayout(Widget):
enforce_torque_enabled = self._torque_control_toggle.action_item.get_state()
nnlc_enabled = self._nnlc_toggle.action_item.get_state()
if enforce_torque_enabled and nnlc_enabled:
self._torque_control_toggle.action_item.set_state(False)
self._nnlc_toggle.action_item.set_state(False)
enforce_torque_enabled = False
nnlc_enabled = False
self._nnlc_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not enforce_torque_enabled)
self._torque_control_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not nnlc_enabled)
self._torque_customization_button.action_item.set_enabled(self._torque_control_toggle.action_item.get_state())
+4
View File
@@ -179,6 +179,10 @@ class UIStateSP:
CP = self.CP
if CP is not None:
if self.params.get_bool("EnforceTorqueControl") and self.params.get_bool("NeuralNetworkLateralControl"):
self.params.put_bool("EnforceTorqueControl", False, block=True)
self.params.put_bool("NeuralNetworkLateralControl", False, block=True)
# Angle steering: no torque-based lateral controls
if CP.steerControlType == car.CarParams.SteerControlType.angle:
self.params.remove("EnforceTorqueControl")
-2
View File
@@ -28,8 +28,6 @@ SP_BRANCH_MIGRATIONS = {
("tizi", "release3-staging"): "release-tizi-staging",
("mici", "release3"): "release-mici",
("mici", "release3-staging"): "release-mici-staging",
("tici", "hkg-angle-steering-2025"): "hkg-angle-steering-2025-tici",
("tici", "hkg-angle-steering-2025-prebuilt"): "hkg-angle-steering-2025-tici-prebuilt"
}
BUILD_METADATA_FILENAME = "build.json"
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,183 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab containers="1" tab_name="actuator data">
<Container>
<DockSplitter orientation="-" sizes="0.25;0.25;0.25;0.25" count="4">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.100000" left="301.990881" top="0.100000" right="462.962504"/>
<limitY/>
<curve name="/carControl/actuators/torque" color="#17becf"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-130.920132" left="301.990881" top="103.993176" right="462.962504"/>
<limitY/>
<curve name="/carControl/actuators/steeringAngleDeg" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.604818" left="301.990881" top="24.797527" right="462.962504"/>
<limitY/>
<curve name="/carState/vEgoRaw" color="#ff7f0e"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.037812" left="301.990881" top="0.047261" right="462.962504"/>
<limitY/>
<curve name="/carControl/actuators/curvature" color="#f14cc1"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="steering messages (CAN)">
<Container>
<DockSplitter orientation="-" sizes="0.142857;0.142857;0.142857;0.142857;0.142857;0.142857;0.142857" count="7">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/can/128/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-128.740000" left="301.990881" top="103.940000" right="462.962504"/>
<limitY/>
<curve name="/can/128/LKAS_ALT/ADAS_StrAnglReqVal" color="#29d627"/>
<curve name="/can/192/LKAS_ALT/ADAS_StrAnglReqVal" color="#b41f32"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/carState/steeringPressed" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="0.975000" left="301.990881" top="2.025000" right="462.962504"/>
<limitY/>
<curve name="/sendcan/0/LKAS_ALT/LKAS_ANGLE_ACTIVE" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/carState/steerFaultTemporary" color="#d62728"/>
<curve name="/carControl/latActive" color="#1ac938"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/can/1/CCNC_0x161/DAW_ICON" color="#f14cc1"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-14.375000" left="301.990881" top="589.375000" right="462.962504"/>
<limitY/>
<curve name="/pandaStates/0/safetyTxBlocked" color="#1f77b4"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="Understanding Torque">
<Container>
<DockSplitter orientation="-" sizes="0.5;0.5" count="2">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-6.175000" left="429.901019" top="253.175000" right="590.696728"/>
<limitY/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-24.190000" left="301.990881" top="21.590000" right="462.962504"/>
<limitY/>
<curve name="/carState/steeringTorqueEps" color="#1ac938"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="tab2">
<Container>
<DockSplitter orientation="-" sizes="0.25;0.25;0.25;0.25" count="4">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Dots">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/sendcan/0/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#9467bd"/>
<curve name="/can/1/LFA_ALT/ADAS_ACIAnglTqRedcGainVal" color="#17becf"/>
<curve name="/can/128/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#1f77b4"/>
<curve name="/can/192/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Dots">
<range bottom="0.975000" left="301.990881" top="2.025000" right="462.962504"/>
<limitY/>
<curve name="/can/128/LKAS_ALT/LKAS_ANGLE_ACTIVE" color="#b8c91a"/>
<curve name="/can/192/LKAS_ALT/LKAS_ANGLE_ACTIVE" color="#ff7f0e"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range bottom="-0.025000" left="301.990881" top="1.025000" right="462.962504"/>
<limitY/>
<curve name="/can/1/CCNC_0x161/DAW_ICON" color="#f14cc1"/>
<curve name="/carState/steerFaultTemporary" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Dots">
<range bottom="-14.375000" left="301.990881" top="589.375000" right="462.962504"/>
<limitY/>
<curve name="/pandaStates/0/safetyTxBlocked" color="#bcbd22"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="3"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,702 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget name="Main Window" parent="main_window">
<Tab containers="1" tab_name="actuator data">
<Container>
<DockSplitter sizes="0.25;0.25;0.25;0.25" count="4" orientation="-">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="0.100000" bottom="-0.100000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carControl/actuators/torque" color="#17becf"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="21.512506" bottom="-143.133163" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carControl/actuators/steeringAngleDeg" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="17.919758" bottom="7.125814" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carState/vEgoRaw" color="#ff7f0e"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="0.055486" bottom="-0.008455" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carControl/actuators/curvature" color="#f14cc1"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="steering messages (CAN)">
<Container>
<DockSplitter sizes="0.25;0.25;0.25;0.25" count="4" orientation="-">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="1.020200" bottom="0.171800" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/sendcan/0/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="3.417986" bottom="-1.676466" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="IMU_LatAccelVal_ms^2_roll_compensated" color="#ff7f0e"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="1.025000" bottom="-0.025000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carState/steeringPressed" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="0.100000" bottom="-0.100000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/can/1/CCNC_0x161/DAW_ICON" color="#f14cc1"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="Understanding Torque">
<Container>
<DockSplitter sizes="0.5;0.5" count="2" orientation="-">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="378.825000" bottom="-1125.825000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carState/steeringTorque" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="17.092500" bottom="-32.992499" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carState/steeringTorqueEps" color="#1ac938"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="Angle Error and Saturation TQ">
<Container>
<DockSplitter sizes="0.166667;0.166667;0.166667;0.166667;0.166667;0.166667" count="6" orientation="-">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="21.512506" bottom="-143.133163" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carControl/actuators/steeringAngleDeg" color="#d62728"/>
<curve name="/carState/steeringAngleDeg" color="#1ac938"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="48.052530" bottom="-34.508142" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="Angle Error" color="#ff7f0e"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="1.020200" bottom="0.171800" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/sendcan/0/LKAS_ALT/ADAS_ACIAnglTqRedcGainVal" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="1.025000" bottom="-0.025000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="Angle Staturation" color="#f14cc1"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="1.025000" bottom="-0.025000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="/carControl/latActive" color="#9467bd"/>
<curve name="/carState/steeringPressed" color="#17becf"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="64.446359" bottom="25.681587" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="carState.vEgo kmh" color="#1f77b4"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="Smoothing and Torque celings">
<Container>
<DockSplitter sizes="0.5;0.5" count="2" orientation="-">
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="4.075000" bottom="0.925000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="subject_to_angle_smoothing" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot mode="TimeSeries" flip_x="false" flip_y="false" style="Lines">
<range top="3.025000" bottom="1.975000" left="256.448992" right="302.818689"/>
<limitY/>
<curve name="base_ceiling_bracket" color="#1f77b4"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="4"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations>
<snippet name="subject_to_angle_smoothing">
<global></global>
<function>if v1 == 0 then
return 0
end
if value &lt; 8.5 then
return 1
elseif value &lt; 11 then
return 2
elseif value &lt;= 13.8 then
return 3
elseif value &lt;= 18 then
return 4
end
return 0</function>
<linked_source>/carState/vEgo</linked_source>
<additional_sources>
<v1>/carControl/latActive</v1>
</additional_sources>
</snippet>
<snippet name="base_ceiling_bracket">
<global></global>
<function>if v1 == 0 then
return 0
end
if value &lt; 20 then
return 1
elseif value &lt; 40 then
return 2
elseif value &lt;= 120 then
return 3
end
return 0</function>
<linked_source>carState.vEgo kmh</linked_source>
<additional_sources>
<v1>/carControl/latActive</v1>
</additional_sources>
</snippet>
<snippet name="max torque lj adj">
<global>min=0
max=250
max_from_speed=96
k1=200
k2=30
k3=1
k4=1
k5=10
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end</global>
<function>return 250 - value * 20</function>
<linked_source>desired lateral jark</linked_source>
</snippet>
<snippet name="Angle Staturation">
<global></global>
<function>if value > .3 then
return 1
end
return 0</function>
<linked_source>Angle Error</linked_source>
</snippet>
<snippet name="ang_cmd rate">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^3">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^2">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return value * -9.8
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2_roll_compensated">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return (value * -9.8) - (v3 * 9.81)
end
--return 0
return (value * -9.8) - (v3 * 9.81)</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
<v3>/liveParameters/roll</v3>
</additional_sources>
</snippet>
<snippet name="abs(ang_cmd)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
<snippet name="zero">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>return 0</function>
<linked_source>/carState/aEgo</linked_source>
</snippet>
<snippet name="engaged curvature vehicle model">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>curvature = value
pressed = v1
enabled = v2
if (pressed == 1 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return value
else
return 0
end</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/enabled</v2>
</additional_sources>
</snippet>
<snippet name="engaged curvature plan">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>curvature = value
pressed = v1
enabled = v2
if (pressed == 1 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return value
else
return 0
end</function>
<linked_source>/lateralPlan/curvatures/0</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/enabled</v2>
</additional_sources>
</snippet>
<snippet name="desired lateral jark">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="carState.vEgo kmh">
<global></global>
<function>return value * 3.6</function>
<linked_source>/carState/vEgo</linked_source>
</snippet>
<snippet name="engaged_accel_plan">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>accel = value
brake = v1
gas = v2
enabled = v3
if (brake ~= 0 or gas ~= 0 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return value
else
return 0
end</function>
<linked_source>/longitudinalPlan/accels/0</linked_source>
<additional_sources>
<v1>/carState/brakePressed</v1>
<v2>/carState/gasPressed</v2>
<v3>/carControl/enabled</v3>
</additional_sources>
</snippet>
<snippet name="Angle Error">
<global>last_angle_requested = 0</global>
<function>angle_error = last_angle_requested - v1
last_angle_requested = value
return angle_error</function>
<linked_source>/carControl/actuators/steeringAngleDeg</linked_source>
<additional_sources>
<v1>/carState/steeringAngleDeg</v1>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="desired lat accel">
<global></global>
<function>return value * v1^2</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
</additional_sources>
</snippet>
<snippet name="engaged_accel_actuator">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>accel = value
brake = v1
gas = v2
enabled = v3
if (brake ~= 0 or gas ~= 0 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return value
else
return 0
end</function>
<linked_source>/carControl/actuators/accel</linked_source>
<additional_sources>
<v1>/carState/brakePressed</v1>
<v2>/carState/gasPressed</v2>
<v3>/carControl/enabled</v3>
</additional_sources>
</snippet>
<snippet name="engaged_accel_actual">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>accel = value
brake = v1
gas = v2
enabled = v3
if (brake ~= 0 or gas ~= 0 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return value
else
return 0
end</function>
<linked_source>/carState/aEgo</linked_source>
<additional_sources>
<v1>/carState/brakePressed</v1>
<v2>/carState/gasPressed</v2>
<v3>/carControl/enabled</v3>
</additional_sources>
</snippet>
<snippet name="roll compensated lateral acceleration">
<global></global>
<function>if (v3 == 0 and v4 == 1) then
return (value * v1 ^ 2) - (v2 * 9.81)
end
return 0</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
<v3>/carState/steeringPressed</v3>
<v4>/carControl/latActive</v4>
</additional_sources>
</snippet>
<snippet name="Zero">
<global></global>
<function>return (0)</function>
<linked_source>/carState/canValid</linked_source>
</snippet>
<snippet name="abs(des la accel)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="Desired lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="Actual lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="max torque(calc)">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>la = apply_deadzone(v2, la_deadzone)
lj = v3
if la == 0.0 then
lj = 0.0
end
fla = math.min(math.abs(k1 * la)^k3, max)
flj = math.min(math.abs(k2 * lj)^k4, max)
out = fla
flv = math.min(max_from_speed, k5 * v4)
out = out + flv
out = math.max(math.min(out, max), min)
if sign(la) == sign(lj) then
out = out - flj
else
out = out + flj
end
if v5 == 1.0 then
out = 0.0
end
out = math.max(math.min(out, max), min)
out = apply_rate_limit(old, out, rate_lim)
old = out
return out</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
<additional_sources>
<v1>ang_cmd rate</v1>
<v2>desired lat accel</v2>
<v3>desired lateral jark</v3>
<v4>/carState/vEgo</v4>
<v5>/can/1/LFA_ALT/LKAS_ANGLE_ACTIVE</v5>
</additional_sources>
</snippet>
<snippet name="carState.vEgo mph">
<global></global>
<function>return value * 2.23694</function>
<linked_source>/carState/vEgo</linked_source>
</snippet>
<snippet name="engaged curvature yaw">
<global>engage_delay = 5
last_bad_time = -engage_delay</global>
<function>curvature = value / v3
pressed = v1
enabled = v2
if (pressed == 1 or enabled == 0) then
last_bad_time = time
end
if (time > last_bad_time + engage_delay) then
return curvature
else
return 0
end</function>
<linked_source>/liveLocationKalman/angularVelocityCalibrated/value/2</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/enabled</v2>
<v3>/liveLocationKalman/velocityCalibrated/value/0</v3>
</additional_sources>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,274 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget name="Main Window" parent="main_window">
<Tab containers="1" tab_name="tab1">
<Container>
<DockSplitter sizes="0.500397;0.499603" count="2" orientation="-">
<DockArea name="...">
<plot flip_x="false" flip_y="false" mode="TimeSeries" style="Lines">
<range top="256.250000" left="0.000000" right="421.102293" bottom="-6.250000"/>
<limitY/>
<curve color="#1ac938" name="/can/1/LFA_ALT/LKAS_ANGLE_MAX_TORQUE"/>
<curve color="#17becf" name="max torque(calc)">
<transform name="Moving Average" alias="max torque(calc)[Moving Average]">
<options compensate_offset="true" value="10"/>
</transform>
</curve>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" flip_y="false" mode="TimeSeries" style="Lines">
<range top="2.776497" left="0.000000" right="421.102293" bottom="-2.918548"/>
<limitY/>
<curve color="#f14cc1" name="desired lat accel"/>
<curve color="#888888" name="zero"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<customMathEquations>
<snippet name="zero">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>return 0</function>
<linked_source>/carState/aEgo</linked_source>
</snippet>
<snippet name="max torque lj adj">
<global>min=0
max=250
max_from_speed=96
k1=200
k2=30
k3=1
k4=1
k5=10
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end</global>
<function>return 250 - value * 20</function>
<linked_source>desired lateral jark</linked_source>
</snippet>
<snippet name="ang_cmd rate">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
<snippet name="max torque(calc)">
<global>min=0
max=250
max_from_speed=96
rate_lim = 500
la_deadzone = 0.38
k1=200
k2=20
k3=1.0
k4=1
k5=10
old = 0
function sign(number)
return number > 0 and 1 or (number == 0 and 0 or -1)
end
function apply_rate_limit(old, new, limit)
return math.min(math.max(new, old - limit), old + limit)
end
function apply_deadzone(val, deadzone)
if math.abs(val) &lt;= deadzone then
return 0.0
elseif val &lt; 0.0 then
return val + deadzone
else
return val - deadzone
end
end</global>
<function>la = apply_deadzone(v2, la_deadzone)
lj = v3
if la == 0.0 then
lj = 0.0
end
fla = math.min(math.abs(k1 * la)^k3, max)
flj = math.min(math.abs(k2 * lj)^k4, max)
out = fla
flv = math.min(max_from_speed, k5 * v4)
out = out + flv
out = math.max(math.min(out, max), min)
if sign(la) == sign(lj) then
out = out - flj
else
out = out + flj
end
if v5 == 1.0 then
out = 0.0
end
out = math.max(math.min(out, max), min)
out = apply_rate_limit(old, out, rate_lim)
old = out
return out</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
<additional_sources>
<v1>ang_cmd rate</v1>
<v2>desired lat accel</v2>
<v3>desired lateral jark</v3>
<v4>/carState/vEgo</v4>
<v5>/can/1/LFA_ALT/LKAS_ANGLE_ACTIVE</v5>
</additional_sources>
</snippet>
<snippet name="desired lateral jark">
<global>firstX = 0
firstY = 0
is_first = true
secondX = 0
secondY = 0
is_second = false</global>
<function>-- Wait for initial values
if (is_first) then
is_first = false
is_second = true
firstX = time
firstY = value
end
if (is_second) then
is_second = false
secondX = time
secondY = value
end
-- Central derivative: dy/dx ~= f(x+delta_x)-f(x-delta_x)/(2*delta_x)
dx = time - firstX
dy = value - firstY
-- Increment
firstX = secondX
firstY = secondY
secondX = time
secondY = value
return dy/dx</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="abs(des la accel)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>desired lat accel</linked_source>
</snippet>
<snippet name="desired lat accel">
<global></global>
<function>return value * v1^2</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
</additional_sources>
</snippet>
<snippet name="abs(ang_cmd)">
<global></global>
<function>return math.abs(value)</function>
<linked_source>/can/1/LFA_ALT/LKAS_ANGLE_CMD</linked_source>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
@@ -1,175 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab tab_name="tab1" containers="1">
<Container>
<DockSplitter sizes="0.25;0.25;0.25;0.25" count="4" orientation="-">
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-3.582051" right="1431.113121" top="5.314632" left="5.322399"/>
<limitY/>
<curve name="Actual lateral accel (roll compensated)" color="#1ac938"/>
<curve name="Desired lateral accel (roll compensated)" color="#ff7f0e"/>
<curve name="IMU_LatAccelVal_ms^2" color="#f14cc1"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-3.741271" right="1431.113121" top="3.756006" left="5.322399"/>
<limitY/>
<curve name="roll compensated lateral acceleration" color="#ff7f0e"/>
<curve name="IMU_LatAccelVal_Ms^2" color="#1ac938"/>
<curve name="IMU_LatAccelVal_ms^2_roll_compensated" color="#9467bd"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-0.025000" right="1431.113121" top="1.025000" left="5.322399"/>
<limitY/>
<curve name="/carState/steeringPressed" color="#0097ff"/>
<curve name="/carOutput/actuatorsOutput/torque" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-1.660728" right="1431.113121" top="67.942958" left="5.322399"/>
<limitY/>
<curve name="/carState/vEgo" color="#f14cc1">
<transform name="Scale/Offset" alias="/carState/vEgo[Scale/Offset]">
<options value_scale="2.23694" time_offset="0" value_offset="0"/>
</transform>
</curve>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab tab_name="tab2" containers="1">
<Container>
<DockSplitter sizes="0.5;0.5" count="2" orientation="-">
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="30595.900000" right="1431.113121" top="34884.100000" left="5.322399"/>
<limitY/>
<curve name="/can/1/IMU_01_10ms/IMU_RollRtVal" color="#17becf"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" style="Lines" flip_y="false" mode="TimeSeries">
<range bottom="-0.058795" right="1431.113121" top="0.072448" left="5.322399"/>
<limitY/>
<curve name="/liveParameters/roll" color="#bcbd22"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="1"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad CSV">
<default time_axis="" delimiter="0"/>
</plugin>
<plugin ID="DataLoad MCAP"/>
<plugin ID="DataLoad Rlog"/>
<plugin ID="DataLoad ULog"/>
<plugin ID="Cereal Subscriber"/>
<plugin ID="UDP Server"/>
<plugin ID="WebSocket Server"/>
<plugin ID="ZMQ Subscriber"/>
<plugin ID="Fast Fourier Transform"/>
<plugin ID="Quaternion to RPY"/>
<plugin ID="Reactive Script Editor">
<library code="--[[ Helper function to create a series from arrays&#xa;&#xa; new_series: a series previously created with ScatterXY.new(name)&#xa; prefix: prefix of the timeseries, before the index of the array&#xa; suffix_X: suffix to complete the name of the series containing the X value. If [nil], use the index of the array.&#xa; suffix_Y: suffix to complete the name of the series containing the Y value&#xa; timestamp: usually the tracker_time variable&#xa; &#xa; Example:&#xa; &#xa; Assuming we have multiple series in the form:&#xa; &#xa; /trajectory/node.{X}/position/x&#xa; /trajectory/node.{X}/position/y&#xa; &#xa; where {N} is the index of the array (integer). We can create a reactive series from the array with:&#xa; &#xa; new_series = ScatterXY.new(&quot;my_trajectory&quot;) &#xa; CreateSeriesFromArray( new_series, &quot;/trajectory/node&quot;, &quot;position/x&quot;, &quot;position/y&quot;, tracker_time );&#xa;--]]&#xa;&#xa;function CreateSeriesFromArray( new_series, prefix, suffix_X, suffix_Y, timestamp )&#xa; &#xa; --- clear previous values&#xa; new_series:clear()&#xa; &#xa; --- Append points to new_series&#xa; index = 0&#xa; while(true) do&#xa;&#xa; x = index;&#xa; -- if not nil, get the X coordinate from a series&#xa; if suffix_X ~= nil then &#xa; series_x = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_X) )&#xa; if series_x == nil then break end&#xa; x = series_x:atTime(timestamp)&#x9; &#xa; end&#xa; &#xa; series_y = TimeseriesView.find( string.format( &quot;%s.%d/%s&quot;, prefix, index, suffix_Y) )&#xa; if series_y == nil then break end &#xa; y = series_y:atTime(timestamp)&#xa; &#xa; new_series:push_back(x,y)&#xa; index = index+1&#xa; end&#xa;end&#xa;&#xa;--[[ Similar to the built-in function GetSeriesNames(), but select only the names with a give prefix. --]]&#xa;&#xa;function GetSeriesNamesByPrefix(prefix)&#xa; -- GetSeriesNames(9 is a built-in function&#xa; all_names = GetSeriesNames()&#xa; filtered_names = {}&#xa; for i, name in ipairs(all_names) do&#xa; -- check the prefix&#xa; if name:find(prefix, 1, #prefix) then&#xa; table.insert(filtered_names, name);&#xa; end&#xa; end&#xa; return filtered_names&#xa;end&#xa;&#xa;--[[ Modify an existing series, applying offsets to all their X and Y values&#xa;&#xa; series: an existing timeseries, obtained with TimeseriesView.find(name)&#xa; delta_x: offset to apply to each x value&#xa; delta_y: offset to apply to each y value &#xa; &#xa;--]]&#xa;&#xa;function ApplyOffsetInPlace(series, delta_x, delta_y)&#xa; -- use C++ indeces, not Lua indeces&#xa; for index=0, series:size()-1 do&#xa; x,y = series:at(index)&#xa; series:set(index, x + delta_x, y + delta_y)&#xa; end&#xa;end&#xa;"/>
<scripts/>
</plugin>
<plugin ID="CSV Exporter"/>
</Plugins>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<customMathEquations>
<snippet name="IMU_LatAccelVal_ms^2">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="roll compensated lateral acceleration">
<global></global>
<function>if (v3 == 0 and v4 == 1) then
return (value * v1 ^ 2) - (v2 * 9.81)
end
return 0</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
<v3>/carState/steeringPressed</v3>
<v4>/carControl/latActive</v4>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^2">
<global></global>
<function>if (v1 == 0 and v2 == 1) then
return value * -9.8
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_Ms^3">
<global></global>
<function>return value * -9.8</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
</additional_sources>
</snippet>
<snippet name="IMU_LatAccelVal_ms^2_roll_compensated">
<global></global>
<function>
if (v1 == 0 and v2 == 1) then
return (value * -9.8) - (v3 * 9.81)
end
return 0</function>
<linked_source>/can/1/IMU_01_10ms/IMU_LatAccelVal</linked_source>
<additional_sources>
<v1>/carState/steeringPressed</v1>
<v2>/carControl/latActive</v2>
<v3>/liveParameters/roll</v3>
</additional_sources>
</snippet>
<snippet name="Actual lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/curvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
<snippet name="Desired lateral accel (roll compensated)">
<global></global>
<function>return (value * v1 ^ 2) - (v2 * 9.81)</function>
<linked_source>/controlsState/desiredCurvature</linked_source>
<additional_sources>
<v1>/carState/vEgo</v1>
<v2>/liveParameters/roll</v2>
</additional_sources>
</snippet>
</customMathEquations>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>