mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-06-19 21:12:04 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 401a9ea75a | |||
| 857d58fcf8 | |||
| dab1c5b7e0 | |||
| e975fdcd6c | |||
| 29beffdd30 | |||
| ef7cd06332 | |||
| 7950dee9a1 | |||
| 6928314c89 |
@@ -0,0 +1,18 @@
|
||||
**/.git
|
||||
.DS_Store
|
||||
*.dylib
|
||||
*.DSYM
|
||||
*.d
|
||||
*.pyc
|
||||
*.pyo
|
||||
.*.swp
|
||||
.*.swo
|
||||
.*.un~
|
||||
*.tmp
|
||||
*.o
|
||||
*.o-*
|
||||
*.os
|
||||
*.os-*
|
||||
|
||||
venv/
|
||||
.venv/
|
||||
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,pyx,pxd}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Bug report
|
||||
description: For issues with running openpilot on your comma device
|
||||
labels: ["bug"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before creating a **bug report**, please check the following:
|
||||
* If the issue likely only affects your car model or make, go back and open a **car bug report** instead.
|
||||
* If the issue is related to the driving or driver monitoring models, you should open a [discussion](https://github.com/commaai/openpilot/discussions/categories/model-feedback) instead.
|
||||
* Ensure you're running the latest openpilot release.
|
||||
* Ensure you're using officially supported hardware. Issues running on PCs have a different issue template.
|
||||
* Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.
|
||||
* Ensure you're running stock openpilot. We cannot look into bug reports from forks.
|
||||
|
||||
If you're unsure whether you've hit a bug, check out the #installation-help channel in the [community Discord server](https://discord.comma.ai).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Also include a description of how to reproduce the bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: route
|
||||
attributes:
|
||||
label: Provide a route where the issue occurs
|
||||
description: Ensure the route is fully uploaded at https://useradmin.comma.ai. We cannot look into issues without routes, or at least a Dongle ID.
|
||||
placeholder: 77611a1fac303767|2020-05-11--16-37-07
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: openpilot version
|
||||
description: If you're not on release, provide the commit hash
|
||||
placeholder: 0.8.10
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Car bug report
|
||||
url: https://github.com/commaai/opendbc/issues/new
|
||||
about: For issues with a particular car make or model
|
||||
- name: Join the Discord
|
||||
url: https://discord.comma.ai
|
||||
about: The community Discord is for both openpilot development and experience discussion
|
||||
- name: Report driving behavior feedback
|
||||
url: https://discord.com/channels/469524606043160576/1254834193066623017
|
||||
about: Feedback for the driving and driver monitoring models goes in the #driving-feedback in Discord
|
||||
- name: Community Wiki
|
||||
url: https://github.com/commaai/openpilot/wiki
|
||||
about: Check out our community wiki
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: For openpilot enhancement suggestions
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: PC bug report
|
||||
description: For issues with running openpilot on PC
|
||||
labels: ["PC"]
|
||||
body:
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Before creating a **bug report**, please check the following:
|
||||
* Ensure you're running the latest openpilot release.
|
||||
* Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.
|
||||
* Ensure you're running stock openpilot. We cannot look into bug reports from forks.
|
||||
|
||||
If you're unsure whether you've hit a bug, check out the #installation-help channel in the [community Discord server](https://discord.comma.ai).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Also include a description of how to reproduce the bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: OS Version
|
||||
placeholder: Ubuntu 24.04
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: openpilot version or commit
|
||||
placeholder: bd36f2ec8d3559909678eff2690c10a520938367
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
CI / testing:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: "{.github/**,**/test_*,**/test/**,Jenkinsfile}"
|
||||
|
||||
car:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
|
||||
|
||||
simulation:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'tools/sim/**'
|
||||
|
||||
ui:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: '{selfdrive/assets/**,selfdrive/ui/**,system/ui/**}'
|
||||
|
||||
tools:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'tools/**'
|
||||
|
||||
multilanguage:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: 'selfdrive/ui/translations/**'
|
||||
|
||||
autonomy:
|
||||
- changed-files:
|
||||
- any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit}"
|
||||
@@ -0,0 +1,68 @@
|
||||
<!-- Please copy and paste the relevant template -->
|
||||
|
||||
<!--- ***** Template: Fingerprint *****
|
||||
|
||||
**Car**
|
||||
Which car (make, model, year) this fingerprint is for
|
||||
|
||||
**Route**
|
||||
A route with the fingerprint
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Car Bugfix *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the bug and the fix. Also link the issue if it exists.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested this bug fix.
|
||||
|
||||
**Route**
|
||||
|
||||
Route: [a route with the bug fix]
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Bugfix *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the bug and the fix. Also link the issue if it exists.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested this bug fix.
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Car Port *****
|
||||
|
||||
**Checklist**
|
||||
|
||||
- [ ] added entry to CAR in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs
|
||||
- [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py)
|
||||
- [ ] route with openpilot:
|
||||
- [ ] route with stock system:
|
||||
- [ ] car harness used (if comma doesn't sell it, put N/A):
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<!--- ***** Template: Refactor *****
|
||||
|
||||
**Description**
|
||||
|
||||
A description of the refactor, including the goals it accomplishes.
|
||||
|
||||
**Verification**
|
||||
|
||||
Explain how you tested the refactor for regressions.
|
||||
|
||||
|
||||
-->
|
||||
|
||||
+42
-29
@@ -1,4 +1,6 @@
|
||||
venv/
|
||||
.venv/
|
||||
.ci_cache
|
||||
.env
|
||||
.clang-format
|
||||
.DS_Store
|
||||
@@ -8,11 +10,13 @@ venv/
|
||||
.overlay_init
|
||||
.overlay_consistent
|
||||
.sconsign.dblite
|
||||
.vscode*
|
||||
model2.png
|
||||
a.out
|
||||
.hypothesis
|
||||
.cache/
|
||||
|
||||
/docs_site/
|
||||
|
||||
*.mp4
|
||||
*.dylib
|
||||
*.DSYM
|
||||
*.d
|
||||
@@ -32,36 +36,22 @@ a.out
|
||||
*.class
|
||||
*.pyxbldc
|
||||
*.vcd
|
||||
*.mo
|
||||
*_pyx.cpp
|
||||
*.stats
|
||||
config.json
|
||||
clcache
|
||||
compile_commands.json
|
||||
compare_runtime*.html
|
||||
|
||||
persist
|
||||
board/obj/
|
||||
selfdrive/boardd/boardd
|
||||
selfdrive/logcatd/logcatd
|
||||
selfdrive/mapd/default_speeds_by_region.json
|
||||
system/proclogd/proclogd
|
||||
selfdrive/ui/_ui
|
||||
selfdrive/test/longitudinal_maneuvers/out
|
||||
selfdrive/visiond/visiond
|
||||
selfdrive/sensord/_gpsd
|
||||
selfdrive/sensord/_sensord
|
||||
selfdrive/pandad/pandad
|
||||
cereal/services.h
|
||||
cereal/gen
|
||||
cereal/messaging/bridge
|
||||
selfdrive/ui/translations/tmp
|
||||
selfdrive/car/tests/cars_dump
|
||||
system/camerad/camerad
|
||||
system/camerad/test/ae_gray_test
|
||||
selfdrive/modeld/_modeld
|
||||
selfdrive/modeld/_navmodeld
|
||||
selfdrive/modeld/_dmonitoringmodeld
|
||||
/src/
|
||||
|
||||
one
|
||||
openpilot
|
||||
notebooks
|
||||
xx
|
||||
yy
|
||||
hyperthneed
|
||||
panda_jungle
|
||||
provisioning
|
||||
|
||||
.coverage*
|
||||
coverage.xml
|
||||
@@ -74,14 +64,37 @@ flycheck_*
|
||||
cppcheck_report.txt
|
||||
comma*.sh
|
||||
|
||||
selfdrive/modeld/thneed/compile
|
||||
selfdrive/modeld/models/*.thneed
|
||||
selfdrive/modeld/models/*.pkl
|
||||
|
||||
# openpilot log files
|
||||
*.bz2
|
||||
*.zst
|
||||
|
||||
build/
|
||||
|
||||
!**/.gitkeep
|
||||
|
||||
poetry.toml
|
||||
selfdrive/ui/_ui_nonav
|
||||
Pipfile
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# rick - keep panda_tici standalone
|
||||
panda_tici/
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"ms-vscode.cpptools",
|
||||
"elagil.pre-commit-helper",
|
||||
"charliermarsh.ruff",
|
||||
"JamiTech.simply-blame",
|
||||
"k--kato.intellij-idea-keybindings",
|
||||
"trinm1709.dracula-theme-from-intellij"
|
||||
]
|
||||
}
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"inputs": [
|
||||
{
|
||||
"id": "python_process",
|
||||
"type": "pickString",
|
||||
"description": "Select the process to debug",
|
||||
"options": [
|
||||
"selfdrive/controls/controlsd.py",
|
||||
"system/timed/timed.py",
|
||||
"tools/sim/run_bridge.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cpp_process",
|
||||
"type": "pickString",
|
||||
"description": "Select the process to debug",
|
||||
"options": [
|
||||
"selfdrive/ui/ui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "args",
|
||||
"description": "Arguments to pass to the process",
|
||||
"type": "promptString"
|
||||
},
|
||||
{
|
||||
"id": "replayArg",
|
||||
"type": "promptString",
|
||||
"description": "Enter route or segment to replay."
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: openpilot Process",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${input:python_process}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": "${input:args}"
|
||||
},
|
||||
{
|
||||
"name": "C++: openpilot Process",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/${input:cpp_process}",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Attach LLDB to Replay drive",
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"pid": "${command:pickMyProcess}",
|
||||
"initCommands": [
|
||||
"script import time; time.sleep(3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Replay drive",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/opendbc/safety/tests/safety_replay/replay_drive.py",
|
||||
"args": [
|
||||
"${input:replayArg}"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}"
|
||||
},
|
||||
"subProcess": true,
|
||||
"stopOnEntry": false
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Replay drive + Safety LLDB",
|
||||
"configurations": [
|
||||
"Replay drive",
|
||||
"Attach LLDB to Replay drive"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.renderWhitespace": "trailing",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.defaultProfile.linux": "dragonpilot",
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"dragonpilot": {
|
||||
"path": "bash",
|
||||
"args": ["-c", "distrobox enter dp"]
|
||||
}
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.venv": true,
|
||||
"**/__pycache__": true,
|
||||
"msgq_repo/": true,
|
||||
"rednose/": true,
|
||||
"rednose_repo/": true,
|
||||
"openpilot/": true,
|
||||
"teleoprtc_repo/": true,
|
||||
"tinygrad/": true,
|
||||
"tinygrad_repo/": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.venv": true,
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"python.analysis.exclude": [
|
||||
"**/.git",
|
||||
"**/.venv",
|
||||
"**/__pycache__",
|
||||
// exclude directories that should be using the symlinked version
|
||||
"common/**",
|
||||
"selfdrive/**",
|
||||
"system/**",
|
||||
"third_party/**",
|
||||
"tools/**",
|
||||
]
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
# ALKA (Always-on Lane Keeping Assist) Design v3
|
||||
|
||||
## Overview
|
||||
|
||||
ALKA enables lateral control (steering) when ACC Main is ON, without requiring cruise to be engaged. This allows lane keeping assist to function independently of longitudinal control.
|
||||
|
||||
**Simplified Behavior (v3):**
|
||||
- All brands use direct tracking: `lkas_on = acc_main_on`
|
||||
- No button/toggle tracking (removed TJA, LKAS button, LKAS HUD)
|
||||
- ACC Main ON = ALKA enabled, ACC Main OFF = ALKA disabled
|
||||
|
||||
---
|
||||
|
||||
## Per-Brand Summary
|
||||
|
||||
| Brand | Status | ACC Main Source | Notes |
|
||||
|-------|--------|-----------------|-------|
|
||||
| Body | Disabled | - | No steering capability |
|
||||
| Chrysler | Disabled | - | Needs special handling |
|
||||
| Ford | Enabled | EngBrakeData (0x165) CcStat | |
|
||||
| GM | Disabled | - | No ACC Main signal |
|
||||
| Honda Nidec | Enabled | SCM_FEEDBACK (0x326) MAIN_ON | |
|
||||
| Honda Bosch | Enabled | SCM_FEEDBACK (0x326) MAIN_ON | |
|
||||
| Hyundai | Enabled | SCC11 (0x420) bit 0 | |
|
||||
| Hyundai CAN-FD | Enabled | SCC_CONTROL (0x1A0) bit 66 | |
|
||||
| Hyundai Legacy | Enabled | SCC11 (0x420) bit 0 | |
|
||||
| Mazda | Enabled | CRZ_CTRL (0x21C) bit 17 | |
|
||||
| Nissan | Enabled | CRUISE_THROTTLE (0x239) bit 17 | |
|
||||
| PSA | Disabled | - | Not implemented |
|
||||
| Rivian | Disabled | - | Different architecture |
|
||||
| Subaru | Enabled | CruiseControl (0x240) bit 40 | |
|
||||
| Subaru Preglobal | Enabled | CruiseControl (0x144) bit 48 | |
|
||||
| Tesla | Disabled | - | Different architecture |
|
||||
| Toyota | Enabled | PCM_CRUISE_2 (0x1D3) bit 15 | |
|
||||
| Toyota (UNSUPPORTED_DSU) | Enabled | DSU_CRUISE (0x365) bit 0 | |
|
||||
| VW MQB | Enabled | TSK_06 TSK_Status (>=2) | |
|
||||
| VW PQ | Enabled | Motor_5 (0x480) bit 50 (long) | |
|
||||
|
||||
---
|
||||
|
||||
## Permission Model
|
||||
|
||||
Lateral control requires checks at both layers. Normal path uses `controls_allowed`, ALKA path uses additional checks.
|
||||
|
||||
| Check | Panda | openpilot | Notes |
|
||||
|-------|:-----:|:---------:|-------|
|
||||
| **Normal Path** |
|
||||
| `controls_allowed` (cruise engaged) | ✓ | ✓ | Either this OR ALKA path |
|
||||
| **ALKA Path** |
|
||||
| `alka_allowed` (brand supports) | ✓ | ✓ | Set per brand in safety init |
|
||||
| `ALT_EXP_ALKA` (user enabled) | ✓ | ✓ | alternativeExperience flag |
|
||||
| `lkas_on` (ACC Main ON) | ✓ | ✓ | Tracked via CAN messages |
|
||||
| `vehicle_moving` / `!standstill` | ✓ | ✓ | |
|
||||
| **openpilot Additional** |
|
||||
| `gear_ok` (not P/N/R) | ✗ | ✓ | Python layer only |
|
||||
| `calibrated` | ✗ | ✓ | Python layer only |
|
||||
| `seatbelt latched` | ✗ | ✓ | Python layer only |
|
||||
| `doors closed` | ✗ | ✓ | Python layer only |
|
||||
| `!steerFaultTemporary` | ✗ | ✓ | Python layer only |
|
||||
| `!steerFaultPermanent` | ✗ | ✓ | Python layer only |
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ CAN Bus │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
|
||||
│ Safety Layer (panda C code) │ │ Python Layer │
|
||||
│ │ │ │
|
||||
│ rx_hook: │ │ carstate.py: │
|
||||
│ - Parse ACC Main signal │ │ - Parse cruiseState.available │
|
||||
│ - Set lkas_on = acc_main_on │ │ - Set self.lkas_on │
|
||||
│ │ │ │
|
||||
│ lat_control_allowed(): │ └─────────────┬───────────────────┘
|
||||
│ - Check lkas_on + other flags │ │
|
||||
│ - Gate steering commands │ ▼
|
||||
└─────────────────────────────────┘ ┌─────────────────────────────────┐
|
||||
│ card.py: │
|
||||
│ - Publish carStateExt.lkasOn │
|
||||
└─────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ controlsd.py: │
|
||||
│ - Read carStateExt.lkasOn │
|
||||
│ - Check ALKA conditions │
|
||||
│ - Set CC.latActive │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `custom.capnp` | Defines `CarStateExt` struct with `lkasOn` field |
|
||||
| `log.capnp` | Includes `carStateExt` in event union |
|
||||
| `interfaces.py` | Defines `self.lkas_on = False` default in `CarStateBase` |
|
||||
| `carstate.py` (per brand) | Tracks `lkas_on` based on ACC Main |
|
||||
| `card.py` | Publishes `carStateExt.lkasOn` from `CI.CS.lkas_on` |
|
||||
| `controlsd.py` | Reads `carStateExt.lkasOn` to determine `alka_active` |
|
||||
|
||||
---
|
||||
|
||||
## ACC Main Tracking
|
||||
|
||||
All brands use simple direct tracking:
|
||||
|
||||
```c
|
||||
// Panda (C code)
|
||||
if (alka_allowed && (alternative_experience & ALT_EXP_ALKA)) {
|
||||
lkas_on = acc_main_on; // or GET_BIT(msg, bit_position)
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python carstate.py
|
||||
self.lkas_on = ret.cruiseState.available
|
||||
```
|
||||
|
||||
This guard ensures:
|
||||
1. Brand supports ALKA (`alka_allowed`)
|
||||
2. User enabled ALKA (`ALT_EXP_ALKA`)
|
||||
|
||||
Without both conditions, no ACC Main tracking occurs, and ALKA remains disabled.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Safety tests verify:
|
||||
- `alka_allowed` flag set correctly per brand
|
||||
- ACC Main tracking updates `lkas_on` directly
|
||||
- `lat_control_allowed()` returns true only when all conditions met
|
||||
- Steering TX blocked when ALKA conditions not met
|
||||
- Bus routing variants (camera_scc, unsupported_dsu)
|
||||
-1098
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
FROM ghcr.io/commaai/openpilot-base:latest
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV OPENPILOT_PATH=/home/batman/openpilot
|
||||
|
||||
RUN mkdir -p ${OPENPILOT_PATH}
|
||||
WORKDIR ${OPENPILOT_PATH}
|
||||
|
||||
COPY . ${OPENPILOT_PATH}/
|
||||
|
||||
ENV UV_BIN="/home/batman/.local/bin/"
|
||||
ENV PATH="$UV_BIN:$PATH"
|
||||
RUN UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV uv run scons --cache-readonly -j$(nproc)
|
||||
@@ -0,0 +1,81 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
|
||||
COPY tools/install_ubuntu_dependencies.sh /tmp/tools/
|
||||
RUN /tmp/tools/install_ubuntu_dependencies.sh && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
cd /usr/lib/gcc/arm-none-eabi/* && \
|
||||
rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp
|
||||
|
||||
# Add OpenCL
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
apt-utils \
|
||||
alien \
|
||||
unzip \
|
||||
tar \
|
||||
curl \
|
||||
xz-utils \
|
||||
dbus \
|
||||
gcc-arm-none-eabi \
|
||||
tmux \
|
||||
vim \
|
||||
libx11-6 \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /tmp/opencl-driver-intel && \
|
||||
cd /tmp/opencl-driver-intel && \
|
||||
wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \
|
||||
mkdir -p /etc/OpenCL/vendors && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \
|
||||
cd /opt/intel && \
|
||||
tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \
|
||||
mkdir -p /etc/ld.so.conf.d && \
|
||||
echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \
|
||||
cd / && \
|
||||
rm -rf /tmp/opencl-driver-intel
|
||||
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute
|
||||
ENV QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
|
||||
RUN dbus-uuidgen > /etc/machine-id
|
||||
|
||||
ARG USER=batman
|
||||
ARG USER_UID=1001
|
||||
RUN useradd -m -s /bin/bash -u $USER_UID $USER
|
||||
RUN usermod -aG sudo $USER
|
||||
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
USER $USER
|
||||
|
||||
COPY --chown=$USER pyproject.toml uv.lock /home/$USER
|
||||
COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/
|
||||
|
||||
ENV VIRTUAL_ENV=/home/$USER/.venv
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN cd /home/$USER && \
|
||||
tools/install_python_dependencies.sh && \
|
||||
rm -rf tools/ pyproject.toml uv.lock .cache
|
||||
|
||||
USER root
|
||||
RUN sudo git config --global --add safe.directory /tmp/openpilot
|
||||
Vendored
+269
@@ -0,0 +1,269 @@
|
||||
def retryWithDelay(int maxRetries, int delay, Closure body) {
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return body()
|
||||
} catch (Exception e) {
|
||||
sleep(delay)
|
||||
}
|
||||
}
|
||||
throw Exception("Failed after ${maxRetries} retries")
|
||||
}
|
||||
|
||||
def device(String ip, String step_label, String cmd) {
|
||||
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||
def ssh_cmd = """
|
||||
ssh -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' exec /usr/bin/bash <<'END'
|
||||
|
||||
set -e
|
||||
|
||||
export TERM=xterm-256color
|
||||
|
||||
shopt -s huponexit # kill all child processes when the shell exits
|
||||
|
||||
export CI=1
|
||||
export PYTHONWARNINGS=error
|
||||
export LOGPRINT=debug
|
||||
export TEST_DIR=${env.TEST_DIR}
|
||||
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||
export CI_ARTIFACTS_TOKEN=${env.CI_ARTIFACTS_TOKEN}
|
||||
export GITHUB_COMMENTS_TOKEN=${env.GITHUB_COMMENTS_TOKEN}
|
||||
export AZURE_TOKEN='${env.AZURE_TOKEN}'
|
||||
# only use 1 thread for tici tests since most require HIL
|
||||
export PYTEST_ADDOPTS="-n0 -s"
|
||||
|
||||
|
||||
export GIT_SSH_COMMAND="ssh -i /data/gitkey"
|
||||
|
||||
source ~/.bash_profile
|
||||
if [ -f /TICI ]; then
|
||||
source /etc/profile
|
||||
|
||||
rm -rf /tmp/tmp*
|
||||
rm -rf ~/.commacache
|
||||
rm -rf /dev/shm/*
|
||||
rm -rf /dev/tmp/tmp*
|
||||
|
||||
if ! systemctl is-active --quiet systemd-resolved; then
|
||||
echo "restarting resolved"
|
||||
sudo systemctl start systemd-resolved
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# restart aux USB
|
||||
if [ -e /sys/bus/usb/drivers/hub/3-0:1.0 ]; then
|
||||
echo "restarting aux usb"
|
||||
echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/unbind
|
||||
sleep 0.5
|
||||
echo "3-0:1.0" | sudo tee /sys/bus/usb/drivers/hub/bind
|
||||
fi
|
||||
fi
|
||||
if [ -f /data/openpilot/launch_env.sh ]; then
|
||||
source /data/openpilot/launch_env.sh
|
||||
fi
|
||||
|
||||
ln -snf ${env.TEST_DIR} /data/pythonpath
|
||||
|
||||
cd ${env.TEST_DIR} || true
|
||||
time ${cmd}
|
||||
END"""
|
||||
|
||||
sh script: ssh_cmd, label: step_label
|
||||
}
|
||||
}
|
||||
|
||||
def deviceStage(String stageName, String deviceType, List extra_env, def steps) {
|
||||
stage(stageName) {
|
||||
if (currentBuild.result != null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isReplay()) {
|
||||
error("REPLAYING TESTS IS NOT ALLOWED. FIX THEM INSTEAD.")
|
||||
}
|
||||
|
||||
def extra = extra_env.collect { "export ${it}" }.join('\n');
|
||||
def branch = env.BRANCH_NAME ?: 'master';
|
||||
def gitDiff = sh returnStdout: true, script: 'curl -s -H "Authorization: Bearer ${GITHUB_COMMENTS_TOKEN}" https://api.github.com/repos/commaai/openpilot/compare/master...${GIT_BRANCH} | jq .files[].filename || echo "/"', label: 'Getting changes'
|
||||
|
||||
lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') {
|
||||
docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') {
|
||||
timeout(time: 35, unit: 'MINUTES') {
|
||||
retry (3) {
|
||||
def date = sh(script: 'date', returnStdout: true).trim();
|
||||
device(device_ip, "set time", "date -s '" + date + "'")
|
||||
device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh"))
|
||||
}
|
||||
steps.each { item ->
|
||||
def name = item[0]
|
||||
def cmd = item[1]
|
||||
|
||||
def args = item[2]
|
||||
def diffPaths = args.diffPaths ?: []
|
||||
def cmdTimeout = args.timeout ?: 9999
|
||||
|
||||
if (branch != "master" && !branch.contains("__jenkins_loop_") && diffPaths && !hasPathChanged(gitDiff, diffPaths)) {
|
||||
println "Skipping ${name}: no changes in ${diffPaths}."
|
||||
return
|
||||
} else {
|
||||
timeout(time: cmdTimeout, unit: 'SECONDS') {
|
||||
device(device_ip, name, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def hasPathChanged(String gitDiff, List<String> paths) {
|
||||
for (path in paths) {
|
||||
if (gitDiff.contains(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
def isReplay() {
|
||||
def replayClass = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
|
||||
return currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replayClass) }
|
||||
}
|
||||
|
||||
def setupCredentials() {
|
||||
withCredentials([
|
||||
string(credentialsId: 'azure_token', variable: 'AZURE_TOKEN'),
|
||||
]) {
|
||||
env.AZURE_TOKEN = "${AZURE_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'ci_artifacts_pat', variable: 'CI_ARTIFACTS_TOKEN'),
|
||||
]) {
|
||||
env.CI_ARTIFACTS_TOKEN = "${CI_ARTIFACTS_TOKEN}"
|
||||
}
|
||||
|
||||
withCredentials([
|
||||
string(credentialsId: 'post_comments_github_pat', variable: 'GITHUB_COMMENTS_TOKEN'),
|
||||
]) {
|
||||
env.GITHUB_COMMENTS_TOKEN = "${GITHUB_COMMENTS_TOKEN}"
|
||||
}
|
||||
}
|
||||
|
||||
def step(String name, String cmd, Map args = [:]) {
|
||||
return [name, cmd, args]
|
||||
}
|
||||
|
||||
node {
|
||||
env.CI = "1"
|
||||
env.PYTHONWARNINGS = "error"
|
||||
env.TEST_DIR = "/data/openpilot"
|
||||
env.SOURCE_DIR = "/data/openpilot_source/"
|
||||
setupCredentials()
|
||||
|
||||
env.GIT_BRANCH = checkout(scm).GIT_BRANCH
|
||||
env.GIT_COMMIT = checkout(scm).GIT_COMMIT
|
||||
|
||||
def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging',
|
||||
'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*']
|
||||
def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*')
|
||||
|
||||
if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) {
|
||||
properties([
|
||||
disableConcurrentBuilds(abortPrevious: true)
|
||||
])
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.BRANCH_NAME == 'devel-staging') {
|
||||
deviceStage("build release-tizi-staging", "tizi-needs-can", [], [
|
||||
step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
}
|
||||
|
||||
if (env.BRANCH_NAME == '__nightly') {
|
||||
parallel (
|
||||
'nightly': {
|
||||
deviceStage("build nightly", "tizi-needs-can", [], [
|
||||
step("build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
'nightly-dev': {
|
||||
deviceStage("build nightly-dev", "tizi-needs-can", [], [
|
||||
step("build nightly-dev", "PANDA_DEBUG_BUILD=1 RELEASE_BRANCH=nightly-dev $SOURCE_DIR/release/build_release.sh"),
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (!env.BRANCH_NAME.matches(excludeRegex)) {
|
||||
parallel (
|
||||
'onroad tests': {
|
||||
deviceStage("onroad", "tizi-needs-can", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("check dirty", "release/check-dirty.sh"),
|
||||
step("onroad tests", "pytest selfdrive/test/test_onroad.py -s", [timeout: 60]),
|
||||
])
|
||||
},
|
||||
'HW + Unit Tests': {
|
||||
deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]),
|
||||
step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"),
|
||||
step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]),
|
||||
step("test manager", "pytest system/manager/test/test_manager.py"),
|
||||
])
|
||||
},
|
||||
'loopback': {
|
||||
deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
])
|
||||
},
|
||||
'camerad OX03C10': {
|
||||
deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'camerad OS04C10': {
|
||||
deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]),
|
||||
step("test exposure", "pytest system/camerad/test/test_exposure.py"),
|
||||
])
|
||||
},
|
||||
'sensord': {
|
||||
deviceStage("LSM + MMC", "tizi-lsmc", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py"),
|
||||
step("test sensord", "pytest system/sensord/tests/test_sensord.py"),
|
||||
])
|
||||
},
|
||||
'replay': {
|
||||
deviceStage("model-replay", "tizi-replay", ["UNSAFE=1"], [
|
||||
step("build", "cd system/manager && ./build.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
step("model replay", "selfdrive/test/process_replay/model_replay.py", [diffPaths: ["selfdrive/modeld/", "tinygrad_repo", "selfdrive/test/process_replay/model_replay.py"]]),
|
||||
])
|
||||
},
|
||||
'tizi': {
|
||||
deviceStage("tizi", "tizi", ["UNSAFE=1"], [
|
||||
step("build openpilot", "cd system/manager && ./build.py"),
|
||||
step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"),
|
||||
step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"),
|
||||
step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"),
|
||||
// TODO: enable once new AGNOS is available
|
||||
// step("test esim", "pytest system/hardware/tici/tests/test_esim.py"),
|
||||
step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]),
|
||||
])
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
currentBuild.result = 'FAILED'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2019, Rick Lan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||
for non-commercial purposes only, subject to the following conditions:
|
||||
|
||||
- The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||
generate revenue) is prohibited without explicit written permission from
|
||||
the copyright holder.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---
|
||||
Copyright (c) 2018, Comma.ai, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,149 +1,74 @@
|
||||

|
||||

|
||||
|
||||
Table of Contents
|
||||
=======================
|
||||
[Read this in English](README_EN.md)
|
||||
|
||||
* [What is openpilot?](#what-is-openpilot)
|
||||
* [Running in a car](#running-on-a-dedicated-device-in-a-car)
|
||||
* [Running on PC](#running-on-pc)
|
||||
* [Community and Contributing](#community-and-contributing)
|
||||
* [User Data and comma Account](#user-data-and-comma-account)
|
||||
* [Safety and Testing](#safety-and-testing)
|
||||
* [Directory Structure](#directory-structure)
|
||||
* [Licensing](#licensing)
|
||||
# **🐲 dragonpilot - 賦予您的愛車「龍」之魂**
|
||||
|
||||
---
|
||||
**我們與您一同翱翔於更智慧、更貼心的駕駛旅程。**
|
||||
|
||||
What is openpilot?
|
||||
------
|
||||
## **👋 嘿, 朋友,歡迎您的到來!**
|
||||
|
||||
[openpilot](http://github.com/commaai/openpilot) is an open source driver assistance system. Currently, openpilot performs the functions of Adaptive Cruise Control (ACC), Automated Lane Centering (ALC), Forward Collision Warning (FCW), and Lane Departure Warning (LDW) for a growing variety of [supported car makes, models, and model years](docs/CARS.md). In addition, while openpilot is engaged, a camera-based Driver Monitoring (DM) feature alerts distracted and asleep drivers. See more about [the vehicle integration](docs/INTEGRATION.md) and [limitations](docs/LIMITATIONS.md).
|
||||
`dragonpilot` 誕生於 2019 年,由三位早期的 openpilot 華人玩家共同創立。初衷很簡單:為廣大的華人用戶、玩家們提供一個友善的交流環境、更簡便的設定協助,並加入更多適合在地使用的貼心功能。
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://i.imgur.com/1w8c6d2.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://i.imgur.com/LnBucik.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/VxiR4iyBruo" title="Video By Charlie Kim"><img src="https://i.imgur.com/4Qoy48c.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/-IkImTe1NYE" title="Video By Aragon"><img src="https://i.imgur.com/04VNzPf.jpg"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://youtu.be/iIUICQkdwFQ" title="Video By Logan LeGrand"><img src="https://i.imgur.com/b1LHQTy.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/XOsa0FsVIsg" title="Video By PinoyDrives"><img src="https://i.imgur.com/6FG0Bd8.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/bCwcJ98R_Xw" title="Video By JS"><img src="https://i.imgur.com/zO18CbW.jpg"></a></td>
|
||||
<td><a href="https://youtu.be/BQ0tF3MTyyc" title="Video By Tsai-Fi"><img src="https://i.imgur.com/eZzelq3.jpg"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
我們深知在地化的重要性,特別是語言的親切感。因此,我們率先導入了完整的中文介面,讓 `dragonpilot` 迅速在華語地區累積了口碑,也讓華人的使用者數量在全球名列前茅。這份來自在地的支持,是我們持續前進的最大動力。
|
||||
|
||||
我們以功能強大的 [openpilot](https://github.com/commaai/openpilot) 為基礎——這套據美國消費者報告評測優於市售車方案的開源輔助駕駛系統——融入了更多在地化的巧思與客製化的溫度,希望能打造出最符合您需求的駕駛夥伴。(您也可以參考我們 repo 中保留的 [openpilot 原始說明檔案](README_OPENPILOT.md))
|
||||
|
||||
Running on a dedicated device in a car
|
||||
------
|
||||
取名 `dragonpilot`,是因為我們希望它能像神話中的「龍」一樣,既強大又充滿智慧,為您的行車安全保駕護航。龍,在我們華人文化中,更是吉祥與力量的象徵,也代表著我們的根源與驕傲。
|
||||
|
||||
To use openpilot in a car, you need four things
|
||||
* A supported device to run this software: a [comma three](https://comma.ai/shop/products/three).
|
||||
* This software. The setup procedure of the comma three allows the user to enter a URL for custom software.
|
||||
The URL, openpilot.comma.ai will install the release version of openpilot. To install openpilot master, you can use installer.comma.ai/commaai/master, and replacing commaai with another GitHub username can install a fork.
|
||||
* One of [the 200+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run openpilot.
|
||||
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car.
|
||||
## **✨ dragonpilot 的里程碑**
|
||||
|
||||
We have detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||
我們不僅保留了 openpilot 的核心優勢,更達成了許多從社群回饋中誕生的里程碑,這些是我們引以為傲的足跡:
|
||||
|
||||
Running on PC
|
||||
------
|
||||
* **🚘 全時置中車道維持 (ALKA)**
|
||||
|
||||
All openpilot services can run as usual on a PC without requiring special hardware or a car. You can also run openpilot on recorded or simulated data to develop or experiment with openpilot.
|
||||
這不只是一個功能,更是 `dragonpilot` 的哲學。我們最早於 [0.6.2 版本](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199) 時便實現了這個功能,其開發歷程始於 2017 Lexus IS300h,接著擴展至 Toyota 全車系,並逐步延伸到其他支援的品牌。它能溫柔地輔助您,讓車輛始終穩定地保持在車道中央,提供一份額外的安心與從容。
|
||||
|
||||
With openpilot's tools, you can plot logs, replay drives, and watch the full-res camera streams. See [the tools README](tools/README.md) for more information.
|
||||
* **🌐 率先導入多國語言介面**
|
||||
|
||||
You can also run openpilot in simulation [with the CARLA simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU.
|
||||
在官方 openpilot 還未支援前,我們便已將多國語言介面實現。`dragonpilot` 完整支援繁體中文、簡體中文與英文,讓操作毫無隔閡。
|
||||
|
||||
A PC running openpilot can also control your vehicle if it is connected to a [webcam](https://github.com/commaai/openpilot/tree/master/tools/webcam), a [black panda](https://comma.ai/shop/products/panda), and a [harness](https://comma.ai/shop/products/car-harness).
|
||||
* **💻 唯一同時支援多硬體平台**
|
||||
|
||||
Community and Contributing
|
||||
------
|
||||
我們是唯一曾致力於讓專案同時兼容 EON、comma two、comma 3 與 Jetson 平台的社群分支,這份努力是為了服務最廣大的玩家社群。
|
||||
此外,在 comma.ai 團隊於 0.10.0 版本宣布停止支持 comma 3 後,我們仍是唯一一個完整同時支援 comma 3、comma 3X 以及 O3、O3L、O3XL(O3 系列為副廠硬體)的社群分支。
|
||||
|
||||
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot). Bug fixes and new car ports are encouraged. Check out [the contributing docs](docs/CONTRIBUTING.md).
|
||||
* **📜 曾榮獲官方認證第一大分支**
|
||||
|
||||
Documentation related to openpilot development can be found on [docs.comma.ai](https://docs.comma.ai). Information about running openpilot (e.g. FAQ, fingerprinting, troubleshooting, custom forks, community hardware) should go on the [wiki](https://github.com/commaai/openpilot/wiki).
|
||||
基於活躍的社群與功能創新,`dragonpilot` 曾一度成長為 comma ai 官方認證的第一大 openpilot 分支,這份榮耀屬於每一位參與者。
|
||||
|
||||
You can add support for your car by following guides we have written for [Brand](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/) and [Model](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) ports. Generally, a car with adaptive cruise control and lane keep assist is a good candidate. [Join our Discord](https://discord.comma.ai) to discuss car ports: most car makes have a dedicated channel.
|
||||
## **🧑💻 設計理念 - 少即是多 (Less is More)**
|
||||
|
||||
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs/).
|
||||
隨著 openpilot 的 AI 模型日益強大,許多過去需要手動微調的功能,現在都已能透過更先進的模型來實現。因此,我們現在的開發重心回歸到 **「最小化修改」(minimal changes)** 的核心原則上。
|
||||
|
||||
And [follow us on Twitter](https://twitter.com/comma_ai).
|
||||
我們的目標是為您提供最純粹、最接近官方的 openpilot 駕駛感受,同時保留 `dragonpilot` 那些經過時間考驗、最受社群喜愛的經典功能。我們相信,在強大的 AI 基礎上,簡潔即是力量。
|
||||
|
||||
User Data and comma Account
|
||||
------
|
||||
## **🛠️ 硬件的足跡 - 一路走來的夥伴們**
|
||||
|
||||
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
|
||||
從最早的 **EON**,到官方的 **comma two / three (C2/C3/C3X)**,再到社群中各式各樣充滿智慧的**副廠機 (如 C1.5, O2, O3, O3L, O3XL 等)**,甚至我們也曾探索過在 [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot) 上的可能性。
|
||||
|
||||
openpilot is open source software: the user is free to disable data collection if they wish to do so.
|
||||
目前最新版本主要支援: comma3 / 3X 以及 O3 / O3L / O3XL 等社群硬體。
|
||||
針對 EON / C1.5 / C2 等舊款硬體,最後支援的版本位於 [d2 分支](https://github.com/dragonpilot-community/dragonpilot/tree/d2)。
|
||||
無論您手上是哪一款設備,都代表著您對開源駕駛輔助的一份熱情。
|
||||
|
||||
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
||||
The driver-facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded.
|
||||
## **🫂 加入我們,成為「尋龍者」的一份子**
|
||||
|
||||
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
|
||||
`dragonpilot` 的成長,離不開每一位使用者的貢獻與回饋。我們是一個以**公開、透明**為原則的溫暖社群,希望在這裡能與所有對 openpilot / dragonpilot 有興趣的用戶分享、交流開發與使用上的經驗。
|
||||
|
||||
Safety and Testing
|
||||
----
|
||||
[**歡迎加入我們的 Facebook 社團進行交流!**](https://www.facebook.com/groups/930190251238639)
|
||||
|
||||
* openpilot observes ISO26262 guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
|
||||
* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit.
|
||||
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
|
||||
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
|
||||
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
|
||||
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
|
||||
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
|
||||
## **❤️ 特別感謝**
|
||||
|
||||
Directory Structure
|
||||
------
|
||||
.
|
||||
├── cereal # The messaging spec and libs used for all logs
|
||||
├── common # Library like functionality we've developed here
|
||||
├── docs # Documentation
|
||||
├── opendbc # Files showing how to interpret data from cars
|
||||
├── panda # Code used to communicate on CAN
|
||||
├── third_party # External libraries
|
||||
├── pyextra # Extra python packages
|
||||
└── system # Generic services
|
||||
├── camerad # Driver to capture images from the camera sensors
|
||||
├── clocksd # Broadcasts current time
|
||||
├── hardware # Hardware abstraction classes
|
||||
├── logcatd # systemd journal as a service
|
||||
└── proclogd # Logs information from /proc
|
||||
└── selfdrive # Code needed to drive the car
|
||||
├── assets # Fonts, images, and sounds for UI
|
||||
├── athena # Allows communication with the app
|
||||
├── boardd # Daemon to talk to the board
|
||||
├── car # Car specific code to read states and control actuators
|
||||
├── controls # Planning and controls
|
||||
├── debug # Tools to help you debug and do car ports
|
||||
├── locationd # Precise localization and vehicle parameter estimation
|
||||
├── loggerd # Logger and uploader of car data
|
||||
├── manager # Daemon that starts/stops all other daemons as needed
|
||||
├── modeld # Driving and monitoring model runners
|
||||
├── monitoring # Daemon to determine driver attention
|
||||
├── navd # Turn-by-turn navigation
|
||||
├── sensord # IMU interface code
|
||||
├── test # Unit tests, system tests, and a car simulator
|
||||
└── ui # The UI
|
||||
`dragonpilot` 從創立至今,從未打算透過 Patreon 等平台進行任何形式的募資。我們的初衷是建立一個讓大家能一起學習、一起成長的社群。It's all about fun, not money.
|
||||
|
||||
Licensing
|
||||
------
|
||||
然而,我們仍要對那些自發性支持本專案的朋友們,致上最誠摯的感謝。正是因為有您們的鼓勵,我們才有更大的動力持續前進。
|
||||
|
||||
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
||||
[**我們的贊助者名單**](SPONSORS.md)
|
||||
|
||||
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
||||
### **安全聲明**
|
||||
|
||||
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
||||
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
||||
NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||
`dragonpilot` 是一種駕駛**輔助**系統,並非全自動駕駛。它旨在減輕您的駕駛疲勞,提升行車安全,但駕駛人仍需時刻保持專注,並隨時準備接管車輛。請務必遵守您所在地區的交通法規。
|
||||
|
||||
---
|
||||
**最後,再次感謝您的到來。**
|
||||
|
||||
<img src="https://d1qb2nb5cznatu.cloudfront.net/startups/i/1061157-bc7e9bf3b246ece7322e6ffe653f6af8-medium_jpg.jpg?buster=1458363130" width="75"></img> <img src="https://cdn-images-1.medium.com/max/1600/1*C87EjxGeMPrkTuVRVWVg4w.png" width="225"></img>
|
||||
|
||||
[](https://github.com/commaai/openpilot/actions)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/alerts/)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/context:python)
|
||||
[](https://lgtm.com/projects/g/commaai/openpilot/context:cpp)
|
||||
[](https://codecov.io/gh/commaai/openpilot)
|
||||
**期待與您一同在智慧駕駛的道路上,乘「龍」而行!**
|
||||
@@ -0,0 +1,74 @@
|
||||

|
||||
|
||||
[Read this in Chinese](README.md)
|
||||
|
||||
# **🐲 dragonpilot - Bringing the Spirit of the Dragon to Your Car**
|
||||
|
||||
**Join us on a smarter, more thoughtful driving journey.**
|
||||
|
||||
## **👋 Welcome, friend!**
|
||||
|
||||
`dragonpilot` was launched in 2019 by three early openpilot enthusiasts from the Chinese community. Our mission was simple: create a friendly space for users to share experiences, provide easier setup help, and add features tailored for local needs.
|
||||
|
||||
Localization has always been at the heart of what we do—starting with a fully Chinese interface. This made `dragonpilot` quickly popular in Chinese-speaking regions and helped our user base grow into one of the largest worldwide. That community support is what keeps us moving forward.
|
||||
|
||||
Built on top of the powerful [openpilot](https://github.com/commaai/openpilot)—an open-source driver assistance system rated by Consumer Reports as outperforming commercial offerings—we add localized refinements and user-focused features to create a driving companion that truly fits your needs. (You can also see the [original openpilot README](README_OPENPILOT.md) preserved in our repo.)
|
||||
|
||||
The name `dragonpilot` reflects our vision: like the dragon of mythology, it is strong and wise, guarding your safety on the road. In Chinese culture, the dragon is also a symbol of luck and strength, representing our roots and pride.
|
||||
|
||||
## **✨ Milestones**
|
||||
|
||||
Beyond carrying forward openpilot's core strengths, we've reached several milestones inspired by community feedback:
|
||||
|
||||
* **🚘 Always Lane Keep Assist (ALKA)**
|
||||
|
||||
More than a feature—it's part of the `dragonpilot` philosophy. Introduced as early as [version 0.6.2](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199), first tested on a 2017 Lexus IS300h, then expanded to Toyota's lineup and beyond. ALKA helps keep your vehicle steadily centered, giving you extra confidence on the road.
|
||||
|
||||
* **🌐 First to add multilingual support**
|
||||
|
||||
Before openpilot officially supported it, we had already introduced multiple languages. `dragonpilot` fully supports Traditional Chinese, Simplified Chinese, and English.
|
||||
|
||||
* **💻 Only community fork to support multiple hardware platforms at once**
|
||||
|
||||
We uniquely worked to make the project run on EON, comma two, comma 3, and Jetson—serving the widest range of users possible.
|
||||
Additionally, after the comma.ai team deprecated the comma 3 in version 0.10.0, we remain the only community fork to offer full, simultaneous support for the comma 3, comma 3X, and the O3, O3L, and O3XL (the O3 series being third-party hardware).
|
||||
|
||||
* **📜 Once recognized as the #1 openpilot fork**
|
||||
|
||||
Thanks to an active community and continuous innovation, `dragonpilot` was once the largest openpilot fork officially recognized by comma ai. This honor belongs to everyone who contributed.
|
||||
|
||||
## **🧑💻 Design Philosophy - Less is More**
|
||||
|
||||
As openpilot's AI grows stronger, many features that once required manual tuning are now handled by advanced models. That's why our focus has returned to **“minimal changes.”**
|
||||
|
||||
We aim to give you the purest, most official-like openpilot driving experience—while preserving `dragonpilot`'s classic, community-loved features. With a solid AI foundation, simplicity is strength.
|
||||
|
||||
## **🛠️ Hardware Journey**
|
||||
|
||||
From the early **EON**, to official devices like **comma two / three (C2/C3/C3X)**, to creative community builds (**C1.5, O2, O3, O3L, O3XL, etc.**), and even experiments with [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot).
|
||||
|
||||
Currently, the latest versions support: **comma3 / 3X** and community hardware like **O3 / O3L / O3XL**.
|
||||
Older devices such as **EON / C1.5 / C2** are supported in the [d2 branch](https://github.com/dragonpilot-community/dragonpilot/tree/d2).
|
||||
Whatever device you're on, it represents your passion for open-source driver assistance.
|
||||
|
||||
## **🫂 Join Us – Become a “Dragon Seeker”**
|
||||
|
||||
`dragonpilot` thrives thanks to every user's contributions and feedback. We're an open, transparent, and welcoming community where enthusiasts can share experiences with openpilot and `dragonpilot`.
|
||||
|
||||
[**Join our Facebook group here!**](https://www.facebook.com/groups/930190251238639)
|
||||
|
||||
## **❤️ Special Thanks**
|
||||
|
||||
Since day one, `dragonpilot` has never asked for funding through Patreon or similar platforms. Our vision is a community where everyone learns and grows together. It's about fun, not money.
|
||||
|
||||
That said, we're deeply grateful to those who voluntarily supported the project. Your encouragement keeps us motivated to keep building.
|
||||
|
||||
[**See our sponsors**](SPONSORS.md)
|
||||
|
||||
### **Safety Notice**
|
||||
|
||||
`dragonpilot` is a driver **assistance** system, not full self-driving. It reduces fatigue and improves safety, but you must remain alert and ready to take control at all times. Always follow your local traffic laws.
|
||||
|
||||
**Thanks again for being here.**
|
||||
|
||||
**We look forward to riding the “dragon” with you on the road to smarter driving!**
|
||||
@@ -0,0 +1,111 @@
|
||||
<div align="center" style="text-align: center;">
|
||||
|
||||
<h1>openpilot</h1>
|
||||
|
||||
<p>
|
||||
<b>openpilot is an operating system for robotics.</b>
|
||||
<br>
|
||||
Currently, it upgrades the driver assistance system in 300+ supported cars.
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
<a href="https://docs.comma.ai">Docs</a>
|
||||
<span> · </span>
|
||||
<a href="https://docs.comma.ai/contributing/roadmap/">Roadmap</a>
|
||||
<span> · </span>
|
||||
<a href="https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md">Contribute</a>
|
||||
<span> · </span>
|
||||
<a href="https://discord.comma.ai">Community</a>
|
||||
<span> · </span>
|
||||
<a href="https://comma.ai/shop">Try it on a comma 3X</a>
|
||||
</h3>
|
||||
|
||||
Quick start: `bash <(curl -fsSL openpilot.comma.ai)`
|
||||
|
||||
[](https://github.com/commaai/openpilot/actions/workflows/tests.yaml)
|
||||
[](LICENSE)
|
||||
[](https://x.com/comma_ai)
|
||||
[](https://discord.comma.ai)
|
||||
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://youtu.be/NmBfgOanCyk" title="Video By Greer Viau"><img src="https://github.com/commaai/openpilot/assets/8762862/2f7112ae-f748-4f39-b617-fabd689c3772"></a></td>
|
||||
<td><a href="https://youtu.be/VHKyqZ7t8Gw" title="Video By Logan LeGrand"><img src="https://github.com/commaai/openpilot/assets/8762862/92351544-2833-40d7-9e0b-7ef7ae37ec4c"></a></td>
|
||||
<td><a href="https://youtu.be/SUIZYzxtMQs" title="A drive to Taco Bell"><img src="https://github.com/commaai/openpilot/assets/8762862/05ceefc5-2628-439c-a9b2-89ce77dc6f63"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Using openpilot in a car
|
||||
------
|
||||
|
||||
To use openpilot in a car, you need four things:
|
||||
1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x).
|
||||
2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version.
|
||||
3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md).
|
||||
4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car.
|
||||
|
||||
We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play.
|
||||
|
||||
|
||||
### Branches
|
||||
|
||||
Running `master` and other branches directly is supported, but it's recommended to run one of the following prebuilt branches:
|
||||
|
||||
| comma four branch | comma 3X branch | URL | description |
|
||||
|------------------------|------------------------|----------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| `release-mici` | `release-tizi` | openpilot.comma.ai | This is openpilot's release branch. |
|
||||
| `release-mici-staging` | `release-tizi-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
|
||||
| `nightly` | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
|
||||
| `nightly-dev` | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
|
||||
|
||||
To start developing openpilot
|
||||
------
|
||||
|
||||
openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot).
|
||||
|
||||
* Join the [community Discord](https://discord.comma.ai)
|
||||
* Check out [the contributing docs](docs/CONTRIBUTING.md)
|
||||
* Check out the [openpilot tools](tools/)
|
||||
* Code documentation lives at https://docs.comma.ai
|
||||
* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki)
|
||||
|
||||
Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](https://comma.ai/bounties) for external contributors.
|
||||
|
||||
Safety and Testing
|
||||
----
|
||||
|
||||
* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details.
|
||||
* openpilot has software-in-the-loop [tests](.github/workflows/tests.yaml) that run on every commit.
|
||||
* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details.
|
||||
* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety).
|
||||
* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes.
|
||||
* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile).
|
||||
* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes.
|
||||
|
||||
<details>
|
||||
<summary>MIT Licensed</summary>
|
||||
|
||||
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
||||
|
||||
Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and its directors, officers, employees, agents, stockholders, affiliates, subcontractors and customers from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses (including without limitation attorneys’ fees and costs) which arise out of, relate to or result from any use of this software by user.
|
||||
|
||||
**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT.
|
||||
YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS.
|
||||
NO WARRANTY EXPRESSED OR IMPLIED.**
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>User Data and comma Account</summary>
|
||||
|
||||
By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone.
|
||||
|
||||
openpilot is open source software: the user is free to disable data collection if they wish to do so.
|
||||
|
||||
openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs.
|
||||
The driver-facing camera and microphone are only logged if you explicitly opt-in in settings.
|
||||
|
||||
By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data.
|
||||
</details>
|
||||
+199
-6
@@ -1,11 +1,204 @@
|
||||
Version 0.9.1 (2022-12-XX)
|
||||
Version 0.10.3 (2025-12-17)
|
||||
========================
|
||||
* Adjust alert volume using ambient noise level
|
||||
* Removed driver monitoring timer resetting on interaction if face detected and distracted
|
||||
* New driving model #36249
|
||||
* New temporal policy architecture
|
||||
* New on-policy training physics noise model
|
||||
* New driver monitoring model #36409
|
||||
* Trained on a new dataset, including comma four data
|
||||
* Improved inter-process communication memory efficiency
|
||||
|
||||
Version 0.10.2 (2025-11-19)
|
||||
========================
|
||||
* comma four support
|
||||
|
||||
Version 0.10.1 (2025-09-08)
|
||||
========================
|
||||
* New driving model #36276
|
||||
* World Model: removed global localization inputs
|
||||
* World Model: 2x the number of parameters
|
||||
* World Model: trained on 4x the number of segments
|
||||
* VAE Compression Model: new architecture and training objective
|
||||
* Driving Vision Model: trained on 4x the number of segments
|
||||
* New Driver Monitoring model #36198
|
||||
* Acura TLX 2021 support thanks to MVL!
|
||||
* Honda City 2023 support thanks to vanillagorillaa and drFritz!
|
||||
* Honda N-Box 2018 support thanks to miettal!
|
||||
* Honda Odyssey 2021-25 support thanks to csouers and MVL!
|
||||
* Honda Passport 2026 support thanks to vanillagorillaa and MVL!
|
||||
|
||||
Version 0.10.0 (2025-08-05)
|
||||
========================
|
||||
* New driving model
|
||||
* New training architecture
|
||||
* Described in our CVPR paper: "Learning to Drive from a World Model"
|
||||
* Longitudinal MPC replaced by E2E planning from World Model in Experimental Mode
|
||||
* Action from lateral MPC as training objective replaced by E2E planning from World Model
|
||||
* Low-speed lead car ground-truth fixes
|
||||
* Enable live-learned steering actuation delay
|
||||
* Opt-in audio recording for dashcam video
|
||||
* Acura MDX 2025 support thanks to vanillagorillaa and MVL!
|
||||
* Honda Accord 2023-25 support thanks to vanillagorillaa and MVL!
|
||||
* Honda CR-V 2023-25 support thanks to vanillagorillaa and MVL!
|
||||
* Honda Pilot 2023-25 support thanks to vanillagorillaa and MVL!
|
||||
|
||||
Version 0.9.9 (2025-05-23)
|
||||
========================
|
||||
* New driving model
|
||||
* New training architecture using parts from MLSIM
|
||||
* Steering actuation delay is now learned online
|
||||
* Ford Escape 2023-24 support thanks to incognitojam!
|
||||
* Ford Kuga 2024 support thanks to incognitojam!
|
||||
* Hyundai Nexo 2021 support thanks to sunnyhaibin!
|
||||
* Tesla Model 3 and Y support thanks to lukasloetkolben!
|
||||
* Lexus RC 2023 support thanks to nelsonjchen!
|
||||
|
||||
Version 0.9.8 (2025-02-28)
|
||||
========================
|
||||
* New driving model
|
||||
* Model now gates applying positive acceleration in Chill mode
|
||||
* New driver monitoring model
|
||||
* Reduced false positives related to passengers
|
||||
* Image processing pipeline moved to the ISP
|
||||
* More GPU time for bigger driving models
|
||||
* Power draw reduced 0.5W, which means your device runs cooler
|
||||
* Added toggle to enable driver monitoring even when openpilot is not engaged
|
||||
* Localizer rewritten to remove GPS dependency at runtime
|
||||
* Firehose Mode for maximizing your training data uploads
|
||||
* Enable openpilot longitudinal control for Ford Q3 vehicles
|
||||
* New Toyota TSS2 longitudinal tune
|
||||
* Rivian R1S and R1T support thanks to lukasloetkolben!
|
||||
* Ford F-150, F-150 Hybrid, Mach-E, and Ranger support
|
||||
|
||||
Version 0.9.7 (2024-06-13)
|
||||
========================
|
||||
* New driving model
|
||||
* Inputs the past curvature for smoother and more accurate lateral control
|
||||
* Simplified neural network architecture in the model's last layers
|
||||
* Minor fixes to desire augmentation and weight decay
|
||||
* New driver monitoring model
|
||||
* Improved end-to-end bit for phone detection
|
||||
* Adjust driving personality with the follow distance button
|
||||
* Support for hybrid variants of supported Ford models
|
||||
* Fingerprinting without the OBD-II port on all cars
|
||||
* Improved fuzzy fingerprinting for Ford and Volkswagen
|
||||
|
||||
Version 0.9.6 (2024-02-27)
|
||||
========================
|
||||
* New driving model
|
||||
* Vision model trained on more data
|
||||
* Improved driving performance
|
||||
* Directly outputs curvature for lateral control
|
||||
* New driver monitoring model
|
||||
* Trained on larger dataset
|
||||
* Model path UI
|
||||
* Shows where driving model wants to be
|
||||
* Shows what model is seeing more clearly, but more jittery
|
||||
* AGNOS 9
|
||||
* comma body streaming and controls over WebRTC
|
||||
* Improved fuzzy fingerprinting for many makes and models
|
||||
* Alpha longitudinal support for new Toyota models
|
||||
* Chevrolet Equinox 2019-22 support thanks to JasonJShuler and nworb-cire!
|
||||
* Dodge Durango 2020-21 support
|
||||
* Hyundai Staria 2023 support thanks to sunnyhaibin!
|
||||
* Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin!
|
||||
* Lexus LC 2024 support thanks to nelsonjchen!
|
||||
* Toyota RAV4 2023-24 support
|
||||
* Toyota RAV4 Hybrid 2023-24 support
|
||||
|
||||
Version 0.9.5 (2023-11-17)
|
||||
========================
|
||||
* New driving model
|
||||
* Improved navigate on openpilot performance using navigation instructions as an additional model input
|
||||
* Do lateral planning inside the model
|
||||
* New vision transformer architecture
|
||||
* Cadillac Escalade ESV 2019 support thanks to twilsonco!
|
||||
* Hyundai Azera 2022 support thanks to sunnyhaibin!
|
||||
* Hyundai Azera Hybrid 2020 support thanks to chanhojung and haram-KONA!
|
||||
* Hyundai Custin 2023 support thanks to sunnyhaibin and Saber422!
|
||||
* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin and alamo3!
|
||||
* Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA!
|
||||
* Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin!
|
||||
* Kia Optima Hybrid 2019 support
|
||||
* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin!
|
||||
* Lexus GS F 2016 support thanks to snyperifle!
|
||||
* Lexus IS 2023 support thanks to L3R5!
|
||||
|
||||
Version 0.9.4 (2023-07-27)
|
||||
========================
|
||||
* comma 3X support
|
||||
* Navigate on openpilot in Experimental mode
|
||||
* When navigation has a destination, openpilot will input the map information into the model, which provides useful context to help the model understand the scene
|
||||
* When navigating on openpilot, openpilot will keep left or right appropriately at forks and exits
|
||||
* When navigating on openpilot, lane change behavior is unchanged and still activated by the driver
|
||||
* When navigate on openpilot is active, the path on the map is green
|
||||
* UI updates
|
||||
* Navigation settings moved to home screen and map
|
||||
* Border color always shows engagement status. Blue means disengaged, green means engaged, and grey means engaged with human overriding
|
||||
* Alerts are shown inside the border. Black means info, orange means warning, and red means critical alert
|
||||
* Bookmarked segments are preserved on the device's storage
|
||||
* Ford Focus 2018 support
|
||||
* Kia Carnival 2023 support thanks to sunnyhaibin!
|
||||
|
||||
Version 0.9.3 (2023-06-29)
|
||||
========================
|
||||
* New driving model
|
||||
* Improved height estimation and added height tracking in liveCalibration
|
||||
* Model inputs refactor
|
||||
* New driving personality setting
|
||||
* Three settings: aggressive, standard, and relaxed
|
||||
* Standard is recommended and the default
|
||||
* In aggressive mode, lead follow distance is shorter and acceleration response is quicker
|
||||
* In relaxed mode, lead follow distance is longer
|
||||
* Improved fuzzy fingerprinting for Hyundai, Kia, and Genesis
|
||||
* Improved thermal management logic
|
||||
|
||||
Version 0.9.2 (2023-05-22)
|
||||
========================
|
||||
* New driving model
|
||||
* Reduced turn diving
|
||||
* Trained on a new dataset
|
||||
* UI updates
|
||||
* New experimental mode visualization
|
||||
* Draw MPC path instead of model-predicted path
|
||||
* AGNOS 7
|
||||
* Faster boot time
|
||||
* Fixes rare no sounds bug
|
||||
* Fixes bootsplash bug at extreme temperatures
|
||||
* Buick LaCrosse 2017-19 support thanks to koch-cf!
|
||||
* Chevrolet Trailblazer 2021-22 support thanks to TurboCE!
|
||||
* Ford Bronco Sport 2021-22 support
|
||||
* Ford Escape 2020-22 support
|
||||
* Ford Explorer 2020-22 support
|
||||
* Ford Kuga 2020-22 support
|
||||
* Ford Maverick 2022-23 support
|
||||
* Genesis GV80 2023 support thanks to JWingate80!
|
||||
* Honda HR-V 2023 support thanks to AlexandreSato and galegozi!
|
||||
* Kia Niro EV 2023 support thanks to JosselinLecocq!
|
||||
* Lexus ES 2017-18 support
|
||||
* Lincoln Aviator 2021 support
|
||||
* Škoda Fabia 2022-23 support thanks to jyoung8607!
|
||||
|
||||
|
||||
Version 0.9.1 (2023-02-28)
|
||||
========================
|
||||
* New driving model
|
||||
* 30% improved height estimation resulting in better driving performance for tall cars
|
||||
* Driver monitoring: removed timer resetting on user interaction if distracted
|
||||
* UI updates
|
||||
* Adjust alert volume using ambient noise level
|
||||
* Driver monitoring icon shows driver's head pose
|
||||
* German translation thanks to Vrabetz and CzokNorris!
|
||||
* Cadillac Escalade 2017 support thanks to rickygilleland!
|
||||
* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler!
|
||||
* Genesis GV60 2023 support thanks to sunnyhaibin!
|
||||
* Hyundai Tucson 2022-23 support
|
||||
* Kia K5 Hybrid 2020 support thanks to sunnyhaibin!
|
||||
* Kia Niro Hybrid 2023 support thanks to sunnyhaibin!
|
||||
* Kia Sorento 2022-23 support thanks to sunnyhaibin!
|
||||
* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin!
|
||||
* Toyota C-HR 2021 support thanks to eFiniLan!
|
||||
* Toyota C-HR Hybrid 2022 support thanks to Korben00!
|
||||
* Volkswagen Crafter and MAN TGE 2017-23 support thanks to jyoung8607!
|
||||
|
||||
Version 0.9.0 (2022-11-21)
|
||||
========================
|
||||
@@ -517,7 +710,7 @@ Version 0.5.13 (2019-05-31)
|
||||
* Reduce CPU utilization by 20% and improve stability
|
||||
* Temporarily remove mapd functionalities to improve stability
|
||||
* Add openpilot record-only mode for unsupported cars
|
||||
* Synchronize controlsd to boardd to reduce latency
|
||||
* Synchronize controlsd to pandad to reduce latency
|
||||
* Remove panda support for Subaru giraffe
|
||||
|
||||
Version 0.5.12 (2019-05-16)
|
||||
@@ -853,7 +1046,7 @@ Version 0.2.8 (2017-02-27)
|
||||
Version 0.2.7 (2017-02-08)
|
||||
===========================
|
||||
* Better performance and pictures at night
|
||||
* Fix ptr alignment issue in boardd
|
||||
* Fix ptr alignment issue in pandad
|
||||
* Fix brake error light, fix crash if too cold
|
||||
|
||||
Version 0.2.6 (2017-01-31)
|
||||
@@ -885,7 +1078,7 @@ Version 0.2.2 (2017-01-10)
|
||||
Version 0.2.1 (2016-12-14)
|
||||
===========================
|
||||
* Performance improvements, removal of more numpy
|
||||
* Fix boardd process priority
|
||||
* Fix pandad process priority
|
||||
* Make counter timer reset on use of steering wheel
|
||||
|
||||
Version 0.2 (2016-12-12)
|
||||
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import platform
|
||||
import shlex
|
||||
import numpy as np
|
||||
|
||||
import SCons.Errors
|
||||
|
||||
SCons.Warnings.warningAsException(True)
|
||||
|
||||
Decider('MD5-timestamp')
|
||||
|
||||
SetOption('num_jobs', max(1, int(os.cpu_count()/2)))
|
||||
|
||||
AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers')
|
||||
AddOption('--asan', action='store_true', help='turn on ASAN')
|
||||
AddOption('--ubsan', action='store_true', help='turn on UBSan')
|
||||
AddOption('--mutation', action='store_true', help='generate mutation-ready code')
|
||||
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
|
||||
AddOption('--minimal',
|
||||
action='store_false',
|
||||
dest='extras',
|
||||
default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS)
|
||||
help='the minimum build to run openpilot. no tests, tools, etc.')
|
||||
|
||||
# Detect platform
|
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
if platform.system() == "Darwin":
|
||||
arch = "Darwin"
|
||||
brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip()
|
||||
elif arch == "aarch64" and os.path.isfile('/TICI'):
|
||||
arch = "larch64"
|
||||
assert arch in [
|
||||
"larch64", # linux tici arm64
|
||||
"aarch64", # linux pc arm64
|
||||
"x86_64", # linux pc x64
|
||||
"Darwin", # macOS arm64 (x86 not supported)
|
||||
]
|
||||
|
||||
env = Environment(
|
||||
ENV={
|
||||
"PATH": os.environ['PATH'],
|
||||
"PYTHONPATH": Dir("#").abspath + ':' + Dir(f"#third_party/acados").abspath,
|
||||
"ACADOS_SOURCE_DIR": Dir("#third_party/acados").abspath,
|
||||
"ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath,
|
||||
"TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer"
|
||||
},
|
||||
CC='clang',
|
||||
CXX='clang++',
|
||||
CCFLAGS=[
|
||||
"-g",
|
||||
"-fPIC",
|
||||
"-O2",
|
||||
"-Wunused",
|
||||
"-Werror",
|
||||
"-Wshadow",
|
||||
"-Wno-unknown-warning-option",
|
||||
"-Wno-inconsistent-missing-override",
|
||||
"-Wno-c99-designator",
|
||||
"-Wno-reorder-init-list",
|
||||
"-Wno-vla-cxx-extension",
|
||||
],
|
||||
CFLAGS=["-std=gnu11"],
|
||||
CXXFLAGS=["-std=c++1z"],
|
||||
CPPPATH=[
|
||||
"#",
|
||||
"#msgq",
|
||||
"#third_party",
|
||||
"#third_party/json11",
|
||||
"#third_party/linux/include",
|
||||
"#third_party/acados/include",
|
||||
"#third_party/acados/include/blasfeo/include",
|
||||
"#third_party/acados/include/hpipm/include",
|
||||
"#third_party/catch2/include",
|
||||
"#third_party/libyuv/include",
|
||||
],
|
||||
LIBPATH=[
|
||||
"#common",
|
||||
"#msgq_repo",
|
||||
"#third_party",
|
||||
"#selfdrive/pandad",
|
||||
"#rednose/helpers",
|
||||
f"#third_party/libyuv/{arch}/lib",
|
||||
f"#third_party/acados/{arch}/lib",
|
||||
],
|
||||
RPATH=[],
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
REDNOSE_ROOT="#",
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
)
|
||||
|
||||
# Arch-specific flags and paths
|
||||
if arch == "larch64":
|
||||
env.Append(CPPPATH=["#third_party/opencl/include"])
|
||||
env.Append(LIBPATH=[
|
||||
"/usr/local/lib",
|
||||
"/system/vendor/lib64",
|
||||
"/usr/lib/aarch64-linux-gnu",
|
||||
])
|
||||
arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"]
|
||||
env.Append(CCFLAGS=arch_flags)
|
||||
env.Append(CXXFLAGS=arch_flags)
|
||||
elif arch == "Darwin":
|
||||
env.Append(LIBPATH=[
|
||||
f"{brew_prefix}/lib",
|
||||
f"{brew_prefix}/opt/openssl@3.0/lib",
|
||||
f"{brew_prefix}/opt/llvm/lib/c++",
|
||||
"/System/Library/Frameworks/OpenGL.framework/Libraries",
|
||||
])
|
||||
env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
||||
env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"])
|
||||
env.Append(CPPPATH=[
|
||||
f"{brew_prefix}/include",
|
||||
f"{brew_prefix}/opt/openssl@3.0/include",
|
||||
])
|
||||
else:
|
||||
env.Append(LIBPATH=[
|
||||
"/usr/lib",
|
||||
"/usr/local/lib",
|
||||
])
|
||||
|
||||
# Sanitizers and extra CCFLAGS from CLI
|
||||
if GetOption('asan'):
|
||||
env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"])
|
||||
env.Append(LINKFLAGS=["-fsanitize=address"])
|
||||
elif GetOption('ubsan'):
|
||||
env.Append(CCFLAGS=["-fsanitize=undefined"])
|
||||
env.Append(LINKFLAGS=["-fsanitize=undefined"])
|
||||
|
||||
_extra_cc = shlex.split(GetOption('ccflags') or '')
|
||||
if _extra_cc:
|
||||
env.Append(CCFLAGS=_extra_cc)
|
||||
|
||||
# no --as-needed on mac linker
|
||||
if arch != "Darwin":
|
||||
env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"])
|
||||
|
||||
# progress output
|
||||
node_interval = 5
|
||||
node_count = 0
|
||||
def progress_function(node):
|
||||
global node_count
|
||||
node_count += node_interval
|
||||
sys.stderr.write("progress: %d\n" % node_count)
|
||||
if os.environ.get('SCONS_PROGRESS'):
|
||||
Progress(progress_function, interval=node_interval)
|
||||
|
||||
# ********** Cython build environment **********
|
||||
py_include = sysconfig.get_paths()['include']
|
||||
envCython = env.Clone()
|
||||
envCython["CPPPATH"] += [py_include, np.get_include()]
|
||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||
envCython["CCFLAGS"].remove("-Werror")
|
||||
|
||||
envCython["LIBS"] = []
|
||||
if arch == "Darwin":
|
||||
envCython["LINKFLAGS"] = env["LINKFLAGS"] + ["-bundle", "-undefined", "dynamic_lookup"]
|
||||
else:
|
||||
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
||||
|
||||
np_version = SCons.Script.Value(np.__version__)
|
||||
Export('envCython', 'np_version')
|
||||
|
||||
Export('env', 'arch')
|
||||
|
||||
# Setup cache dir
|
||||
cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache'
|
||||
CacheDir(cache_dir)
|
||||
Clean(["."], cache_dir)
|
||||
|
||||
# ********** start building stuff **********
|
||||
|
||||
# Build common module
|
||||
SConscript(['common/SConscript'])
|
||||
Import('_common')
|
||||
common = [_common, 'json11', 'zmq']
|
||||
Export('common')
|
||||
|
||||
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
||||
# Enable swaglog include in submodules
|
||||
env_swaglog = env.Clone()
|
||||
env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""')
|
||||
SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog})
|
||||
SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog})
|
||||
|
||||
SConscript(['cereal/SConscript'])
|
||||
|
||||
Import('socketmaster', 'msgq')
|
||||
messaging = [socketmaster, msgq, 'capnp', 'kj',]
|
||||
Export('messaging')
|
||||
|
||||
|
||||
# Build other submodules
|
||||
SConscript(['panda/SConscript'])
|
||||
SConscript(['panda_tici/SConscript'])
|
||||
|
||||
# Build rednose library
|
||||
SConscript(['rednose/SConscript'])
|
||||
|
||||
# Build system services
|
||||
SConscript([
|
||||
'system/ubloxd/SConscript',
|
||||
'system/loggerd/SConscript',
|
||||
])
|
||||
|
||||
if arch == "larch64":
|
||||
SConscript(['system/camerad/SConscript'])
|
||||
|
||||
# Build openpilot
|
||||
SConscript(['third_party/SConscript'])
|
||||
|
||||
SConscript(['selfdrive/SConscript'])
|
||||
|
||||
if Dir('#tools/cabana/').exists() and GetOption('extras'):
|
||||
SConscript(['tools/replay/SConscript'])
|
||||
if arch != "larch64":
|
||||
SConscript(['tools/cabana/SConscript'])
|
||||
|
||||
|
||||
env.CompilationDatabase('compile_commands.json')
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Suspected vulnerabilities can be reported to both `adeeb@comma.ai` and `security@comma.ai`.
|
||||
@@ -1,21 +0,0 @@
|
||||
gen
|
||||
node_modules
|
||||
package-lock.json
|
||||
*.tmp
|
||||
*.pyc
|
||||
__pycache__
|
||||
.*.swp
|
||||
.*.swo
|
||||
*.os
|
||||
*.o
|
||||
*.a
|
||||
|
||||
test_runner
|
||||
|
||||
libmessaging.*
|
||||
libmessaging_shared.*
|
||||
services.h
|
||||
.sconsign.dblite
|
||||
libcereal_shared.*
|
||||
.mypy_cache/
|
||||
catch2/
|
||||
@@ -1,56 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
autoconf \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
capnproto \
|
||||
clang \
|
||||
cppcheck \
|
||||
curl \
|
||||
git \
|
||||
libbz2-dev \
|
||||
libcapnp-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev \
|
||||
libncurses5-dev \
|
||||
libncursesw5-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
libzmq3-dev \
|
||||
llvm \
|
||||
make \
|
||||
ocl-icd-opencl-dev \
|
||||
opencl-headers \
|
||||
python-openssl \
|
||||
tk-dev \
|
||||
wget \
|
||||
xz-utils \
|
||||
zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}"
|
||||
RUN pyenv install 3.8.10 && \
|
||||
pyenv global 3.8.10 && \
|
||||
pyenv rehash && \
|
||||
pip3 install --no-cache-dir pyyaml==5.1.2 Cython==0.29.14 scons==3.1.1 pycapnp==1.0.0 pre-commit==2.15.0 pylint==2.5.2 parameterized==0.7.4 coverage==5.1 numpy==1.21.1
|
||||
|
||||
WORKDIR /project/cereal/messaging
|
||||
RUN git clone https://github.com/catchorg/Catch2.git && \
|
||||
cd Catch2 && \
|
||||
git checkout 229cc4823c8cbe67366da8179efc6089dd3893e9 && \
|
||||
mv single_include/catch2 ../catch2 && \
|
||||
cd .. \
|
||||
rm -rf Catch2
|
||||
|
||||
WORKDIR /project/cereal
|
||||
|
||||
ENV PYTHONPATH=/project
|
||||
|
||||
COPY . .
|
||||
RUN rm -rf .git && \
|
||||
scons -c && scons -j$(nproc)
|
||||
+60
-20
@@ -1,37 +1,77 @@
|
||||
What is cereal? [](https://github.com/commaai/cereal/actions) [](https://codecov.io/gh/commaai/cereal)
|
||||
----
|
||||
# What is cereal?
|
||||
|
||||
cereal is both a messaging spec for robotics systems as well as generic high performance IPC pub sub messaging with a single publisher and multiple subscribers.
|
||||
|
||||
Imagine this use case:
|
||||
* A sensor process reads gyro measurements directly from an IMU and publishes a `sensorEvents` packet
|
||||
* A calibration process subscribes to the `sensorEvents` packet to use the IMU
|
||||
* A localization process subscribes to the `sensorEvents` packet to use the IMU also
|
||||
cereal is the messaging system for openpilot. It uses [msgq](https://github.com/commaai/msgq) as a pub/sub backend, and [Cap'n proto](https://capnproto.org/capnp-tool.html) for serialization of the structs.
|
||||
|
||||
|
||||
Messaging Spec
|
||||
----
|
||||
## Messaging Spec
|
||||
|
||||
You'll find the message types in [log.capnp](log.capnp). It uses [Cap'n proto](https://capnproto.org/capnp-tool.html) and defines one struct called Event.
|
||||
You'll find the message types in [log.capnp](log.capnp). It uses [Cap'n proto](https://capnproto.org/capnp-tool.html) and defines one struct called `Event`.
|
||||
|
||||
All Events have a `logMonoTime` and a `valid`. Then a big union defines the packet type.
|
||||
All `Events` have a `logMonoTime` and a `valid`. Then a big union defines the packet type.
|
||||
|
||||
|
||||
Message definition Best Practices
|
||||
----
|
||||
### Best Practices
|
||||
|
||||
- **All fields must describe quantities in SI units**, unless otherwise specified in the field name.
|
||||
|
||||
- In the context of the message they are in, field names should be completely unambiguous.
|
||||
|
||||
- All values should be easy to plot and be human-readable with minimal parsing.
|
||||
|
||||
### Maintaining backwards-compatibility
|
||||
|
||||
When making changes to the messaging spec you want to maintain backwards-compatibility, such that old logs can
|
||||
be parsed with a new version of cereal. Adding structs and adding members to structs is generally safe, most other
|
||||
things are not. Read more details [here](https://capnproto.org/language.html).
|
||||
|
||||
Pub Sub Backends
|
||||
----
|
||||
### Custom forks
|
||||
|
||||
cereal supports two backends, one based on [zmq](https://zeromq.org/) and another called msgq, a custom pub sub based on shared memory that doesn't require the bytes to pass through the kernel.
|
||||
Forks of [openpilot](https://github.com/commaai/openpilot) might want to add things to the messaging
|
||||
spec, however this could conflict with future changes made in mainline cereal/openpilot. Rebasing against mainline openpilot
|
||||
then means breaking backwards-compatibility with all old logs of your fork. So we added reserved events in
|
||||
[custom.capnp](custom.capnp) that we will leave empty in mainline cereal/openpilot. **If you only modify those, you can ensure your
|
||||
fork will remain backwards-compatible with all versions of mainline openpilot and your fork.**
|
||||
|
||||
An example of compatible changes:
|
||||
```diff
|
||||
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
|
||||
index 3348e859e..3365c7b98 100644
|
||||
--- a/cereal/custom.capnp
|
||||
+++ b/cereal/custom.capnp
|
||||
@@ -10,7 +10,11 @@ $Cxx.namespace("cereal");
|
||||
# DO rename the structs
|
||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||
|
||||
-struct CustomReserved0 @0x81c2f05a394cf4af {
|
||||
+struct SteeringInfo @0x81c2f05a394cf4af {
|
||||
+ active @0 :Bool;
|
||||
+ steeringAngleDeg @1 :Float32;
|
||||
+ steeringRateDeg @2 :Float32;
|
||||
+ steeringAccelDeg @3 :Float32;
|
||||
}
|
||||
|
||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
||||
diff --git a/cereal/log.capnp b/cereal/log.capnp
|
||||
index 1209f3fd9..b189f58b6 100644
|
||||
--- a/cereal/log.capnp
|
||||
+++ b/cereal/log.capnp
|
||||
@@ -2558,14 +2558,14 @@ struct Event {
|
||||
|
||||
# DO change the name of the field
|
||||
# DON'T change anything after the "@"
|
||||
- customReservedRawData0 @124 :Data;
|
||||
+ rawCanData @124 :Data;
|
||||
customReservedRawData1 @125 :Data;
|
||||
customReservedRawData2 @126 :Data;
|
||||
|
||||
# DO change the name of the field and struct
|
||||
# DON'T change the ID (e.g. @107)
|
||||
# DON'T change which struct it points to
|
||||
- customReserved0 @107 :Custom.CustomReserved0;
|
||||
+ steeringInfo @107 :Custom.SteeringInfo;
|
||||
customReserved1 @108 :Custom.CustomReserved1;
|
||||
customReserved2 @109 :Custom.CustomReserved2;
|
||||
customReserved3 @110 :Custom.CustomReserved3;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Example
|
||||
---
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
Import('env', 'common', 'msgq')
|
||||
|
||||
cereal_dir = Dir('.')
|
||||
gen_dir = Dir('gen')
|
||||
|
||||
# Build cereal
|
||||
schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp']
|
||||
env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files],
|
||||
schema_files,
|
||||
f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/")
|
||||
|
||||
cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files])
|
||||
|
||||
# Build messaging
|
||||
services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET')
|
||||
env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc'], LIBS=[msgq, common, 'pthread'])
|
||||
|
||||
socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc'])
|
||||
|
||||
Export('cereal', 'socketmaster')
|
||||
+6
-4
@@ -1,9 +1,11 @@
|
||||
# pylint: skip-file
|
||||
import os
|
||||
import capnp
|
||||
from importlib.resources import as_file, files
|
||||
|
||||
CEREAL_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
capnp.remove_import_hook()
|
||||
|
||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
||||
with as_file(files("cereal")) as fspath:
|
||||
CEREAL_PATH = fspath.as_posix()
|
||||
log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp"))
|
||||
car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp"))
|
||||
custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp"))
|
||||
|
||||
@@ -1,691 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0x8e2af1e708af8b8d;
|
||||
|
||||
# ******* events causing controls state machine transition *******
|
||||
|
||||
struct CarEvent @0x9b1657f34caf3ad3 {
|
||||
name @0 :EventName;
|
||||
|
||||
# event types
|
||||
enable @1 :Bool;
|
||||
noEntry @2 :Bool;
|
||||
warning @3 :Bool; # alerts presented only when enabled or soft disabling
|
||||
userDisable @4 :Bool;
|
||||
softDisable @5 :Bool;
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool; # alerts presented regardless of openpilot state
|
||||
overrideLateral @10 :Bool;
|
||||
overrideLongitudinal @9 :Bool;
|
||||
|
||||
enum EventName @0xbaa8c5d505f727de {
|
||||
canError @0;
|
||||
steerUnavailable @1;
|
||||
brakeUnavailable @2;
|
||||
wrongGear @4;
|
||||
doorOpen @5;
|
||||
seatbeltNotLatched @6;
|
||||
espDisabled @7;
|
||||
wrongCarMode @8;
|
||||
steerTempUnavailable @9;
|
||||
reverseGear @10;
|
||||
buttonCancel @11;
|
||||
buttonEnable @12;
|
||||
pedalPressed @13; # exits active state
|
||||
preEnableStandstill @73; # added during pre-enable state with brake
|
||||
gasPressedOverride @108; # added when user is pressing gas with no disengage on gas
|
||||
steerOverride @114;
|
||||
cruiseDisabled @14;
|
||||
speedTooLow @17;
|
||||
outOfSpace @18;
|
||||
overheat @19;
|
||||
calibrationIncomplete @20;
|
||||
calibrationInvalid @21;
|
||||
controlsMismatch @22;
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
radarFault @26;
|
||||
brakeHold @28;
|
||||
parkBrake @29;
|
||||
manualRestart @30;
|
||||
lowSpeedLockout @31;
|
||||
plannerError @32;
|
||||
joystickDebug @34;
|
||||
steerTempUnavailableSilent @35;
|
||||
resumeRequired @36;
|
||||
preDriverDistracted @37;
|
||||
promptDriverDistracted @38;
|
||||
driverDistracted @39;
|
||||
preDriverUnresponsive @43;
|
||||
promptDriverUnresponsive @44;
|
||||
driverUnresponsive @45;
|
||||
belowSteerSpeed @46;
|
||||
lowBattery @48;
|
||||
vehicleModelInvalid @50;
|
||||
accFaulted @51;
|
||||
accFaultedTemp @115;
|
||||
sensorDataInvalid @52;
|
||||
commIssue @53;
|
||||
commIssueAvgFreq @109;
|
||||
tooDistracted @54;
|
||||
posenetInvalid @55;
|
||||
soundsUnavailable @56;
|
||||
preLaneChangeLeft @57;
|
||||
preLaneChangeRight @58;
|
||||
laneChange @59;
|
||||
lowMemory @63;
|
||||
stockAeb @64;
|
||||
ldw @65;
|
||||
carUnrecognized @66;
|
||||
invalidLkasSetting @69;
|
||||
speedTooHigh @70;
|
||||
laneChangeBlocked @71;
|
||||
relayMalfunction @72;
|
||||
stockFcw @74;
|
||||
startup @75;
|
||||
startupNoCar @76;
|
||||
startupNoControl @77;
|
||||
startupMaster @78;
|
||||
startupNoFw @104;
|
||||
fcw @79;
|
||||
steerSaturated @80;
|
||||
belowEngageSpeed @84;
|
||||
noGps @85;
|
||||
wrongCruiseMode @87;
|
||||
modeldLagging @89;
|
||||
deviceFalling @90;
|
||||
fanMalfunction @91;
|
||||
cameraMalfunction @92;
|
||||
cameraFrameRate @110;
|
||||
gpsMalfunction @94;
|
||||
processNotRunning @95;
|
||||
dashcamMode @96;
|
||||
controlsInitializing @98;
|
||||
usbError @99;
|
||||
roadCameraError @100;
|
||||
driverCameraError @101;
|
||||
wideRoadCameraError @102;
|
||||
localizerMalfunction @103;
|
||||
highCpuUsage @105;
|
||||
cruiseMismatch @106;
|
||||
lkasDisabled @107;
|
||||
canBusMissing @111;
|
||||
controlsdLagging @112;
|
||||
resumeBlocked @113;
|
||||
|
||||
#dp
|
||||
speedLimitActive @116;
|
||||
speedLimitValueChange @117;
|
||||
|
||||
radarCanErrorDEPRECATED @15;
|
||||
communityFeatureDisallowedDEPRECATED @62;
|
||||
radarCommIssueDEPRECATED @67;
|
||||
driverMonitorLowAccDEPRECATED @68;
|
||||
gasUnavailableDEPRECATED @3;
|
||||
dataNeededDEPRECATED @16;
|
||||
modelCommIssueDEPRECATED @27;
|
||||
ipasOverrideDEPRECATED @33;
|
||||
geofenceDEPRECATED @40;
|
||||
driverMonitorOnDEPRECATED @41;
|
||||
driverMonitorOffDEPRECATED @42;
|
||||
calibrationProgressDEPRECATED @47;
|
||||
invalidGiraffeHondaDEPRECATED @49;
|
||||
invalidGiraffeToyotaDEPRECATED @60;
|
||||
internetConnectivityNeededDEPRECATED @61;
|
||||
whitePandaUnsupportedDEPRECATED @81;
|
||||
commIssueWarningDEPRECATED @83;
|
||||
focusRecoverActiveDEPRECATED @86;
|
||||
neosUpdateRequiredDEPRECATED @88;
|
||||
modelLagWarningDEPRECATED @93;
|
||||
startupOneplusDEPRECATED @82;
|
||||
startupFuzzyFingerprintDEPRECATED @97;
|
||||
noTargetDEPRECATED @25;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* main car state @ 100hz *******
|
||||
# all speeds in m/s
|
||||
|
||||
struct CarState {
|
||||
events @13 :List(CarEvent);
|
||||
|
||||
# CAN health
|
||||
canValid @26 :Bool; # invalid counter/checksums
|
||||
canTimeout @40 :Bool; # CAN bus dropped out
|
||||
|
||||
# car speed
|
||||
vEgo @1 :Float32; # best estimate of speed
|
||||
aEgo @16 :Float32; # best estimate of acceleration
|
||||
vEgoRaw @17 :Float32; # unfiltered speed from CAN sensors
|
||||
vEgoCluster @44 :Float32; # best estimate of speed shown on car's instrument cluster, used for UI
|
||||
|
||||
yawRate @22 :Float32; # best estimate of yaw rate
|
||||
standstill @18 :Bool;
|
||||
wheelSpeeds @2 :WheelSpeeds;
|
||||
|
||||
# gas pedal, 0.0-1.0
|
||||
gas @3 :Float32; # this is user pedal only
|
||||
gasPressed @4 :Bool; # this is user pedal only
|
||||
|
||||
# brake pedal, 0.0-1.0
|
||||
brake @5 :Float32; # this is user pedal only
|
||||
brakePressed @6 :Bool; # this is user pedal only
|
||||
regenBraking @45 :Bool; # this is user pedal only
|
||||
parkingBrake @39 :Bool;
|
||||
brakeHoldActive @38 :Bool;
|
||||
|
||||
# steering wheel
|
||||
steeringAngleDeg @7 :Float32;
|
||||
steeringAngleOffsetDeg @37 :Float32; # Offset betweens sensors in case there multiple
|
||||
steeringRateDeg @15 :Float32;
|
||||
steeringTorque @8 :Float32; # TODO: standardize units
|
||||
steeringTorqueEps @27 :Float32; # TODO: standardize units
|
||||
steeringPressed @9 :Bool; # if the user is using the steering wheel
|
||||
steerFaultTemporary @35 :Bool; # temporary EPS fault
|
||||
steerFaultPermanent @36 :Bool; # permanent EPS fault
|
||||
stockAeb @30 :Bool;
|
||||
stockFcw @31 :Bool;
|
||||
espDisabled @32 :Bool;
|
||||
accFaulted @42 :Bool;
|
||||
|
||||
# cruise state
|
||||
cruiseState @10 :CruiseState;
|
||||
|
||||
# gear
|
||||
gearShifter @14 :GearShifter;
|
||||
|
||||
# button presses
|
||||
buttonEvents @11 :List(ButtonEvent);
|
||||
leftBlinker @20 :Bool;
|
||||
rightBlinker @21 :Bool;
|
||||
genericToggle @23 :Bool;
|
||||
|
||||
# lock info
|
||||
doorOpen @24 :Bool;
|
||||
seatbeltUnlatched @25 :Bool;
|
||||
|
||||
# clutch (manual transmission only)
|
||||
clutchPressed @28 :Bool;
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @12: List(UInt64);
|
||||
|
||||
# blindspot sensors
|
||||
leftBlindspot @33 :Bool; # Is there something blocking the left lane change
|
||||
rightBlindspot @34 :Bool; # Is there something blocking the right lane change
|
||||
|
||||
fuelGauge @41 :Float32; # battery or fuel tank level from 0.0 to 1.0
|
||||
charging @43 :Bool;
|
||||
|
||||
# dp
|
||||
cruiseActualEnabled @46 :Bool;
|
||||
engineRPM @47 :Float32;
|
||||
distanceLines @48:UInt8;
|
||||
|
||||
struct WheelSpeeds {
|
||||
# optional wheel speeds
|
||||
fl @0 :Float32;
|
||||
fr @1 :Float32;
|
||||
rl @2 :Float32;
|
||||
rr @3 :Float32;
|
||||
}
|
||||
|
||||
struct CruiseState {
|
||||
enabled @0 :Bool;
|
||||
speed @1 :Float32;
|
||||
speedCluster @6 :Float32; # Set speed as shown on instrument cluster
|
||||
available @2 :Bool;
|
||||
speedOffset @3 :Float32;
|
||||
standstill @4 :Bool;
|
||||
nonAdaptive @5 :Bool;
|
||||
speedLimit @7 :Float32;
|
||||
}
|
||||
|
||||
enum GearShifter {
|
||||
unknown @0;
|
||||
park @1;
|
||||
drive @2;
|
||||
neutral @3;
|
||||
reverse @4;
|
||||
sport @5;
|
||||
low @6;
|
||||
brake @7;
|
||||
eco @8;
|
||||
manumatic @9;
|
||||
}
|
||||
|
||||
# send on change
|
||||
struct ButtonEvent {
|
||||
pressed @0 :Bool;
|
||||
type @1 :Type;
|
||||
|
||||
enum Type {
|
||||
unknown @0;
|
||||
leftBlinker @1;
|
||||
rightBlinker @2;
|
||||
accelCruise @3;
|
||||
decelCruise @4;
|
||||
cancel @5;
|
||||
altButton1 @6;
|
||||
altButton2 @7;
|
||||
altButton3 @8;
|
||||
setCruise @9;
|
||||
resumeCruise @10;
|
||||
gapAdjustCruise @11;
|
||||
}
|
||||
}
|
||||
|
||||
errorsDEPRECATED @0 :List(CarEvent.EventName);
|
||||
brakeLightsDEPRECATED @19 :Bool;
|
||||
steeringRateLimitedDEPRECATED @29 :Bool;
|
||||
}
|
||||
|
||||
# ******* radar state @ 20hz *******
|
||||
|
||||
struct RadarData @0x888ad6581cf0aacb {
|
||||
errors @0 :List(Error);
|
||||
points @1 :List(RadarPoint);
|
||||
|
||||
# which packets this state came from
|
||||
canMonoTimes @2 :List(UInt64);
|
||||
|
||||
enum Error {
|
||||
canError @0;
|
||||
fault @1;
|
||||
wrongConfig @2;
|
||||
}
|
||||
|
||||
# similar to LiveTracks
|
||||
# is one timestamp valid for all? I think so
|
||||
struct RadarPoint {
|
||||
trackId @0 :UInt64; # no trackId reuse
|
||||
|
||||
# these 3 are the minimum required
|
||||
dRel @1 :Float32; # m from the front bumper of the car
|
||||
yRel @2 :Float32; # m
|
||||
vRel @3 :Float32; # m/s
|
||||
|
||||
# these are optional and valid if they are not NaN
|
||||
aRel @4 :Float32; # m/s^2
|
||||
yvRel @5 :Float32; # m/s
|
||||
|
||||
# some radars flag measurements VS estimates
|
||||
measured @6 :Bool;
|
||||
}
|
||||
}
|
||||
|
||||
# ******* car controls @ 100hz *******
|
||||
|
||||
struct CarControl {
|
||||
# must be true for any actuator commands to work
|
||||
enabled @0 :Bool;
|
||||
latActive @11: Bool;
|
||||
longActive @12: Bool;
|
||||
|
||||
# Actuator commands as computed by controlsd
|
||||
actuators @6 :Actuators;
|
||||
|
||||
leftBlinker @15: Bool;
|
||||
rightBlinker @16: Bool;
|
||||
|
||||
# Any car specific rate limits or quirks applied by
|
||||
# the CarController are reflected in actuatorsOutput
|
||||
# and matches what is sent to the car
|
||||
actuatorsOutput @10 :Actuators;
|
||||
|
||||
orientationNED @13 :List(Float32);
|
||||
angularVelocity @14 :List(Float32);
|
||||
|
||||
cruiseControl @4 :CruiseControl;
|
||||
hudControl @5 :HUDControl;
|
||||
|
||||
struct Actuators {
|
||||
# range from 0.0 - 1.0
|
||||
gas @0: Float32;
|
||||
brake @1: Float32;
|
||||
# range from -1.0 - 1.0
|
||||
steer @2: Float32;
|
||||
# value sent over can to the car
|
||||
steerOutputCan @8: Float32;
|
||||
steeringAngleDeg @3: Float32;
|
||||
|
||||
curvature @7: Float32;
|
||||
|
||||
speed @6: Float32; # m/s
|
||||
accel @4: Float32; # m/s^2
|
||||
longControlState @5: LongControlState;
|
||||
|
||||
enum LongControlState @0xe40f3a917d908282{
|
||||
off @0;
|
||||
pid @1;
|
||||
stopping @2;
|
||||
starting @3;
|
||||
}
|
||||
}
|
||||
|
||||
struct CruiseControl {
|
||||
cancel @0: Bool;
|
||||
resume @1: Bool;
|
||||
override @4: Bool;
|
||||
speedOverrideDEPRECATED @2: Float32;
|
||||
accelOverrideDEPRECATED @3: Float32;
|
||||
}
|
||||
|
||||
struct HUDControl {
|
||||
speedVisible @0: Bool;
|
||||
setSpeed @1: Float32;
|
||||
lanesVisible @2: Bool;
|
||||
leadVisible @3: Bool;
|
||||
visualAlert @4: VisualAlert;
|
||||
audibleAlert @5: AudibleAlert;
|
||||
rightLaneVisible @6: Bool;
|
||||
leftLaneVisible @7: Bool;
|
||||
rightLaneDepart @8: Bool;
|
||||
leftLaneDepart @9: Bool;
|
||||
|
||||
enum VisualAlert {
|
||||
# these are the choices from the Honda
|
||||
# map as good as you can for your car
|
||||
none @0;
|
||||
fcw @1;
|
||||
steerRequired @2;
|
||||
brakePressed @3;
|
||||
wrongGear @4;
|
||||
seatbeltUnbuckled @5;
|
||||
speedTooHigh @6;
|
||||
ldw @7;
|
||||
}
|
||||
|
||||
enum AudibleAlert {
|
||||
none @0;
|
||||
|
||||
engage @1;
|
||||
disengage @2;
|
||||
refuse @3;
|
||||
|
||||
warningSoft @4;
|
||||
warningImmediate @5;
|
||||
|
||||
prompt @6;
|
||||
promptRepeat @7;
|
||||
promptDistracted @8;
|
||||
}
|
||||
}
|
||||
|
||||
gasDEPRECATED @1 :Float32;
|
||||
brakeDEPRECATED @2 :Float32;
|
||||
steeringTorqueDEPRECATED @3 :Float32;
|
||||
activeDEPRECATED @7 :Bool;
|
||||
rollDEPRECATED @8 :Float32;
|
||||
pitchDEPRECATED @9 :Float32;
|
||||
}
|
||||
|
||||
# ****** car param ******
|
||||
|
||||
struct CarParams {
|
||||
carName @0 :Text;
|
||||
carFingerprint @1 :Text;
|
||||
fuzzyFingerprint @55 :Bool;
|
||||
|
||||
notCar @66 :Bool; # flag for non-car robotics platforms
|
||||
|
||||
enableGasInterceptor @2 :Bool;
|
||||
pcmCruise @3 :Bool; # is openpilot's state tied to the PCM's cruise state?
|
||||
enableDsu @5 :Bool; # driving support unit
|
||||
enableBsm @56 :Bool; # blind spot monitoring
|
||||
flags @64 :UInt32; # flags for car specific quirks
|
||||
experimentalLongitudinalAvailable @71 :Bool;
|
||||
|
||||
minEnableSpeed @7 :Float32;
|
||||
minSteerSpeed @8 :Float32;
|
||||
safetyConfigs @62 :List(SafetyConfig);
|
||||
alternativeExperience @65 :Int16; # panda flag for features like no disengage on gas
|
||||
|
||||
# Car docs fields
|
||||
maxLateralAccel @68 :Float32;
|
||||
autoResumeSng @69 :Bool; # describes whether car can resume from a stop automatically
|
||||
|
||||
# things about the car in the manual
|
||||
mass @17 :Float32; # [kg] curb weight: all fluids no cargo
|
||||
wheelbase @18 :Float32; # [m] distance from rear axle to front axle
|
||||
centerToFront @19 :Float32; # [m] distance from center of mass to front axle
|
||||
steerRatio @20 :Float32; # [] ratio of steering wheel angle to front wheel angle
|
||||
steerRatioRear @21 :Float32; # [] ratio of steering wheel angle to rear wheel angle (usually 0)
|
||||
|
||||
# things we can derive
|
||||
rotationalInertia @22 :Float32; # [kg*m2] body rotational inertia
|
||||
tireStiffnessFront @23 :Float32; # [N/rad] front tire coeff of stiff
|
||||
tireStiffnessRear @24 :Float32; # [N/rad] rear tire coeff of stiff
|
||||
|
||||
longitudinalTuning @25 :LongitudinalPIDTuning;
|
||||
lateralParams @48 :LateralParams;
|
||||
lateralTuning :union {
|
||||
pid @26 :LateralPIDTuning;
|
||||
indi @27 :LateralINDITuning;
|
||||
lqr @40 :LateralLQRTuning;
|
||||
torque @67 :LateralTorqueTuning;
|
||||
}
|
||||
|
||||
steerLimitAlert @28 :Bool;
|
||||
steerLimitTimer @47 :Float32; # time before steerLimitAlert is issued
|
||||
|
||||
vEgoStopping @29 :Float32; # Speed at which the car goes into stopping state
|
||||
vEgoStarting @59 :Float32; # Speed at which the car goes into starting state
|
||||
stoppingControl @31 :Bool; # Does the car allow full control even at lows speeds when stopping
|
||||
steerControlType @34 :SteerControlType;
|
||||
radarOffCan @35 :Bool; # True when radar objects aren't visible on CAN
|
||||
stopAccel @60 :Float32; # Required acceleration to keep vehicle stationary
|
||||
stoppingDecelRate @52 :Float32; # m/s^2/s while trying to stop
|
||||
startAccel @32 :Float32; # Required acceleration to get car moving
|
||||
startingState @70 :Bool; # Does this car make use of special starting state
|
||||
|
||||
steerActuatorDelay @36 :Float32; # Steering wheel actuator delay in seconds
|
||||
longitudinalActuatorDelayLowerBound @61 :Float32; # Gas/Brake actuator delay in seconds, lower bound
|
||||
longitudinalActuatorDelayUpperBound @58 :Float32; # Gas/Brake actuator delay in seconds, upper bound
|
||||
openpilotLongitudinalControl @37 :Bool; # is openpilot doing the longitudinal control?
|
||||
carVin @38 :Text; # VIN number queried during fingerprinting
|
||||
dashcamOnly @41: Bool;
|
||||
transmissionType @43 :TransmissionType;
|
||||
carFw @44 :List(CarFw);
|
||||
|
||||
radarTimeStep @45: Float32 = 0.05; # time delta between radar updates, 20Hz is very standard
|
||||
fingerprintSource @49: FingerprintSource;
|
||||
networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network
|
||||
|
||||
wheelSpeedFactor @63 :Float32; # Multiplier on wheels speeds to computer actual speeds
|
||||
|
||||
struct SafetyConfig {
|
||||
safetyModel @0 :SafetyModel;
|
||||
safetyParam @3 :UInt16;
|
||||
safetyParamDEPRECATED @1 :Int16;
|
||||
safetyParam2DEPRECATED @2 :UInt32;
|
||||
}
|
||||
|
||||
struct LateralParams {
|
||||
torqueBP @0 :List(Int32);
|
||||
torqueV @1 :List(Int32);
|
||||
}
|
||||
|
||||
struct LateralPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @4 :Float32;
|
||||
}
|
||||
|
||||
struct LateralTorqueTuning {
|
||||
useSteeringAngle @0 :Bool;
|
||||
kp @1 :Float32;
|
||||
ki @2 :Float32;
|
||||
friction @3 :Float32;
|
||||
kf @4 :Float32;
|
||||
steeringAngleDeadzoneDeg @5 :Float32;
|
||||
latAccelFactor @6 :Float32;
|
||||
latAccelOffset @7 :Float32;
|
||||
}
|
||||
|
||||
struct LongitudinalPIDTuning {
|
||||
kpBP @0 :List(Float32);
|
||||
kpV @1 :List(Float32);
|
||||
kiBP @2 :List(Float32);
|
||||
kiV @3 :List(Float32);
|
||||
kf @6 :Float32;
|
||||
deadzoneBP @4 :List(Float32);
|
||||
deadzoneV @5 :List(Float32);
|
||||
}
|
||||
|
||||
struct LateralINDITuning {
|
||||
outerLoopGainBP @4 :List(Float32);
|
||||
outerLoopGainV @5 :List(Float32);
|
||||
innerLoopGainBP @6 :List(Float32);
|
||||
innerLoopGainV @7 :List(Float32);
|
||||
timeConstantBP @8 :List(Float32);
|
||||
timeConstantV @9 :List(Float32);
|
||||
actuatorEffectivenessBP @10 :List(Float32);
|
||||
actuatorEffectivenessV @11 :List(Float32);
|
||||
|
||||
outerLoopGainDEPRECATED @0 :Float32;
|
||||
innerLoopGainDEPRECATED @1 :Float32;
|
||||
timeConstantDEPRECATED @2 :Float32;
|
||||
actuatorEffectivenessDEPRECATED @3 :Float32;
|
||||
}
|
||||
|
||||
struct LateralLQRTuning {
|
||||
scale @0 :Float32;
|
||||
ki @1 :Float32;
|
||||
dcGain @2 :Float32;
|
||||
|
||||
# State space system
|
||||
a @3 :List(Float32);
|
||||
b @4 :List(Float32);
|
||||
c @5 :List(Float32);
|
||||
|
||||
k @6 :List(Float32); # LQR gain
|
||||
l @7 :List(Float32); # Kalman gain
|
||||
}
|
||||
|
||||
enum SafetyModel {
|
||||
silent @0;
|
||||
hondaNidec @1;
|
||||
toyota @2;
|
||||
elm327 @3;
|
||||
gm @4;
|
||||
hondaBoschGiraffe @5;
|
||||
ford @6;
|
||||
cadillac @7;
|
||||
hyundai @8;
|
||||
chrysler @9;
|
||||
tesla @10;
|
||||
subaru @11;
|
||||
gmPassive @12;
|
||||
mazda @13;
|
||||
nissan @14;
|
||||
volkswagen @15;
|
||||
toyotaIpas @16;
|
||||
allOutput @17;
|
||||
gmAscm @18;
|
||||
noOutput @19; # like silent but without silent CAN TXs
|
||||
hondaBosch @20;
|
||||
volkswagenPq @21;
|
||||
subaruLegacy @22; # pre-Global platform
|
||||
hyundaiLegacy @23;
|
||||
hyundaiCommunity @24;
|
||||
stellantisDEPRECATED @25; # Consolidated with Chrysler; may be recycled for the next new model
|
||||
hongqi @26;
|
||||
body @27;
|
||||
hyundaiCanfd @28;
|
||||
}
|
||||
|
||||
enum SteerControlType {
|
||||
torque @0;
|
||||
angle @1;
|
||||
curvature @2;
|
||||
}
|
||||
|
||||
enum TransmissionType {
|
||||
unknown @0;
|
||||
automatic @1; # Traditional auto, including DSG
|
||||
manual @2; # True "stick shift" only
|
||||
direct @3; # Electric vehicle or other direct drive
|
||||
cvt @4;
|
||||
}
|
||||
|
||||
struct CarFw {
|
||||
ecu @0 :Ecu;
|
||||
fwVersion @1 :Data;
|
||||
address @2 :UInt32;
|
||||
subAddress @3 :UInt8;
|
||||
responseAddress @4 :UInt32;
|
||||
request @5 :List(Data);
|
||||
brand @6 :Text;
|
||||
bus @7 :UInt8;
|
||||
}
|
||||
|
||||
enum Ecu {
|
||||
eps @0;
|
||||
abs @1;
|
||||
fwdRadar @2;
|
||||
fwdCamera @3;
|
||||
engine @4;
|
||||
unknown @5;
|
||||
transmission @8; # Transmission Control Module
|
||||
srs @9; # airbag
|
||||
gateway @10; # can gateway
|
||||
hud @11; # heads up display
|
||||
combinationMeter @12; # instrument cluster
|
||||
electricBrakeBooster @15;
|
||||
shiftByWire @16;
|
||||
adas @19;
|
||||
cornerRadar @21;
|
||||
|
||||
# Toyota only
|
||||
dsu @6;
|
||||
apgs @7;
|
||||
|
||||
# Honda only
|
||||
vsa @13; # Vehicle Stability Assist
|
||||
programmedFuelInjection @14;
|
||||
|
||||
# Chrysler only
|
||||
hcp @18; # Hybrid Control Processor
|
||||
|
||||
# Hyundai only
|
||||
vcu @20; # Vehicle (Motor) Control Unit
|
||||
|
||||
debug @17;
|
||||
}
|
||||
|
||||
enum FingerprintSource {
|
||||
can @0;
|
||||
fw @1;
|
||||
fixed @2;
|
||||
}
|
||||
|
||||
enum NetworkLocation {
|
||||
fwdCamera @0; # Standard/default integration at LKAS camera
|
||||
gateway @1; # Integration at vehicle's CAN gateway
|
||||
}
|
||||
|
||||
enableCameraDEPRECATED @4 :Bool;
|
||||
enableApgsDEPRECATED @6 :Bool;
|
||||
steerRateCostDEPRECATED @33 :Float32;
|
||||
isPandaBlackDEPRECATED @39 :Bool;
|
||||
hasStockCameraDEPRECATED @57 :Bool;
|
||||
safetyParamDEPRECATED @10 :Int16;
|
||||
safetyModelDEPRECATED @9 :SafetyModel;
|
||||
safetyModelPassiveDEPRECATED @42 :SafetyModel = silent;
|
||||
minSpeedCanDEPRECATED @51 :Float32;
|
||||
communityFeatureDEPRECATED @46: Bool;
|
||||
startingAccelRateDEPRECATED @53 :Float32;
|
||||
steerMaxBPDEPRECATED @11 :List(Float32);
|
||||
steerMaxVDEPRECATED @12 :List(Float32);
|
||||
gasMaxBPDEPRECATED @13 :List(Float32);
|
||||
gasMaxVDEPRECATED @14 :List(Float32);
|
||||
brakeMaxBPDEPRECATED @15 :List(Float32);
|
||||
brakeMaxVDEPRECATED @16 :List(Float32);
|
||||
directAccelControlDEPRECATED @30 :Bool;
|
||||
maxSteeringAngleDegDEPRECATED @54 :Float32;
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../opendbc_repo/opendbc/car/car.capnp
|
||||
@@ -0,0 +1,105 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0xb526ba661d550a59;
|
||||
|
||||
# custom.capnp: a home for empty structs reserved for custom forks
|
||||
# These structs are guaranteed to remain reserved and empty in mainline
|
||||
# cereal, so use these if you want custom events in your fork.
|
||||
|
||||
# DO rename the structs
|
||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||
|
||||
struct ControlsStateExt @0x81c2f05a394cf4af {
|
||||
alkaActive @0 :Bool;
|
||||
}
|
||||
|
||||
struct CarStateExt @0xaedffd8f31e7b55d {
|
||||
# dp - ALKA: lkasOn state from carstate (mirrors panda's lkas_on)
|
||||
lkasOn @0 :Bool;
|
||||
}
|
||||
|
||||
struct ModelExt @0xf35cc4560bbf6ec2 {
|
||||
leftEdgeDetected @0 :Bool;
|
||||
rightEdgeDetected @1 :Bool;
|
||||
}
|
||||
|
||||
struct LiveGPS @0xda96579883444c35 {
|
||||
# Position
|
||||
latitude @0 :Float64; # degrees
|
||||
longitude @1 :Float64; # degrees
|
||||
altitude @2 :Float64; # meters (WGS84)
|
||||
|
||||
# Motion
|
||||
speed @3 :Float32; # m/s (horizontal speed)
|
||||
bearingDeg @4 :Float32; # degrees (heading)
|
||||
|
||||
# Accuracy
|
||||
horizontalAccuracy @5 :Float32; # meters
|
||||
verticalAccuracy @6 :Float32; # meters
|
||||
|
||||
# Status
|
||||
gpsOK @7 :Bool; # livePose valid + GPS fresh
|
||||
status @8 :Status;
|
||||
|
||||
enum Status {
|
||||
noGps @0;
|
||||
initializing @1;
|
||||
calibrating @2;
|
||||
valid @3;
|
||||
recalibrating @4;
|
||||
gpsStale @5;
|
||||
}
|
||||
|
||||
# Metadata
|
||||
unixTimestampMillis @9 :Int64;
|
||||
lastGpsTimestamp @10 :UInt64; # logMonoTime of last GPS
|
||||
}
|
||||
|
||||
struct CustomReserved4 @0x80ae746ee2596b11 {
|
||||
}
|
||||
|
||||
struct CustomReserved5 @0xa5cd762cd951a455 {
|
||||
}
|
||||
|
||||
struct CustomReserved6 @0xf98d843bfd7004a3 {
|
||||
}
|
||||
|
||||
struct CustomReserved7 @0xb86e6369214c01c8 {
|
||||
}
|
||||
|
||||
struct CustomReserved8 @0xf416ec09499d9d19 {
|
||||
}
|
||||
|
||||
struct CustomReserved9 @0xa1680744031fdb2d {
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
}
|
||||
|
||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||
}
|
||||
|
||||
struct CustomReserved12 @0x9ccdc8676701b412 {
|
||||
}
|
||||
|
||||
struct CustomReserved13 @0xcd96dafb67a082d0 {
|
||||
}
|
||||
|
||||
struct CustomReserved14 @0xb057204d7deadf3f {
|
||||
}
|
||||
|
||||
struct CustomReserved15 @0xbd443b539493bc68 {
|
||||
}
|
||||
|
||||
struct CustomReserved16 @0xfc6241ed8877b611 {
|
||||
}
|
||||
|
||||
struct CustomReserved17 @0xa30662f84033036c {
|
||||
}
|
||||
|
||||
struct CustomReserved18 @0xc86a3d38d13eb3ef {
|
||||
}
|
||||
|
||||
struct CustomReserved19 @0xa4f1eb3323f5f582 {
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0xbfa7e645486440c7;
|
||||
|
||||
# dp
|
||||
struct DragonConf {
|
||||
dpAtl @0 :UInt8;
|
||||
dpLocale @1 :Text;
|
||||
dpLateralMode @2 :UInt8;
|
||||
dpLcMinMph @3 :UInt8;
|
||||
dpLcAutoMinMph @4 :UInt8;
|
||||
dpLcAutoDelay @5 :Float32;
|
||||
dpLateralLanelines @6 :Bool;
|
||||
dpLateralCameraOffset @7 :Int8;
|
||||
dpLateralPathOffset @8 :Int8;
|
||||
dpIpAddr @9 :Text;
|
||||
dpUiTop @10 :Bool;
|
||||
dpUiSide @11 :Bool;
|
||||
dpUiVolume @12 :Int8;
|
||||
dpUiBrightness @13 :UInt8;
|
||||
dpUiDisplayMode @14 :UInt8;
|
||||
dpUiSpeed @15 :Bool;
|
||||
dpUiEvent @16 :Bool;
|
||||
dpUiFace @17 :Bool;
|
||||
dpUiLeadInfo @18 :Bool;
|
||||
dpUiLaneline @19 :Bool;
|
||||
dpUiChevron @20 :Bool;
|
||||
dpUiDmCam @21 :Bool;
|
||||
dpToyotaSng @22 :Bool;
|
||||
dpAccelProfileCtrl @23 :Bool;
|
||||
dpAccelProfile @24 :UInt8;
|
||||
dpToyotaCruiseOverride @25 :Bool;
|
||||
dpToyotaCruiseOverrideSpeed @26 :UInt8;
|
||||
dpToyotaAutoLock @27 :Bool;
|
||||
dpToyotaAutoUnlock @28 :Bool;
|
||||
dpMapd @29 :Bool;
|
||||
dpLocalDb @30 :Bool;
|
||||
dpDashcamd @31 :Bool;
|
||||
dpMazdaSteerAlert @32 :Bool;
|
||||
dpSpeedCheck @33 :Bool;
|
||||
dpFollowingProfileCtrl @34 :Bool;
|
||||
dpFollowingProfile @35 :UInt8;
|
||||
dpE2EConditional @36 :Bool;
|
||||
dpE2EConditionalAtSpeed @37 :UInt8;
|
||||
dpE2EConditionalAtSpeedLead @38 :UInt8;
|
||||
dpE2EConditionalAdaptFp @39 :Bool;
|
||||
dpE2EConditionalAdaptAp @40 :Bool;
|
||||
dpE2EConditionalVoacc @41 :Bool;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -r gen/ts
|
||||
rm -r gen/js
|
||||
|
||||
mkdir gen/ts
|
||||
mkdir gen/js
|
||||
|
||||
echo "Installing needed npm modules"
|
||||
npm i capnpc-ts capnp-ts
|
||||
|
||||
capnpc -o node_modules/.bin/capnpc-ts:gen/ts log.capnp car.capnp
|
||||
capnpc -o node_modules/.bin/capnpc-ts:gen/ts car.capnp
|
||||
|
||||
cat log.capnp | egrep '\([a-zA-Z]*\.[^\s]+\.[^s]+\)' | sed 's/^.*([a-zA-Z]*\.\([a-zA-Z.]*\)).*/\1/' | while read line
|
||||
do
|
||||
TOKEN=`echo $line | sed 's/\./_/g'`
|
||||
ROOT=`echo $line | sed 's/\..*$//g'`
|
||||
cat gen/ts/log.capnp.ts | grep '^import.*'${TOKEN}
|
||||
if [[ "$?" == "1" ]]
|
||||
then
|
||||
sed -i 's/^\(import {.*\)'${ROOT}'\(,*\) \(.*\)$/\1'${ROOT}', '${TOKEN}'\2 \3/' ./gen/ts/log.capnp.ts
|
||||
fi
|
||||
done
|
||||
|
||||
tsc ./gen/ts/* --lib es2015 --outDir ./gen/js
|
||||
Binary file not shown.
+750
-262
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
using Cxx = import "./include/c++.capnp";
|
||||
$Cxx.namespace("cereal");
|
||||
|
||||
@0xa086df597ef5d7a0;
|
||||
|
||||
# Geometry
|
||||
struct Point {
|
||||
x @0: Float64;
|
||||
y @1: Float64;
|
||||
z @2: Float64;
|
||||
}
|
||||
|
||||
struct PolyLine {
|
||||
points @0: List(Point);
|
||||
}
|
||||
|
||||
# Map features
|
||||
struct Lane {
|
||||
id @0 :Text;
|
||||
|
||||
leftBoundary @1 :LaneBoundary;
|
||||
rightBoundary @2 :LaneBoundary;
|
||||
|
||||
leftAdjacentId @3 :Text;
|
||||
rightAdjacentId @4 :Text;
|
||||
|
||||
inboundIds @5 :List(Text);
|
||||
outboundIds @6 :List(Text);
|
||||
|
||||
struct LaneBoundary {
|
||||
polyLine @0 :PolyLine;
|
||||
startHeading @1 :Float32; # WRT north
|
||||
}
|
||||
}
|
||||
|
||||
# Map tiles
|
||||
struct TileSummary {
|
||||
version @0 :Text;
|
||||
updatedAt @1 :UInt64; # Millis since epoch
|
||||
|
||||
level @2 :UInt8;
|
||||
x @3 :UInt16;
|
||||
y @4 :UInt16;
|
||||
}
|
||||
|
||||
struct MapTile {
|
||||
summary @0 :TileSummary;
|
||||
lanes @1 :List(Lane);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
demo
|
||||
bridge
|
||||
test_runner
|
||||
*.o
|
||||
*.os
|
||||
*.d
|
||||
*.a
|
||||
*.so
|
||||
messaging_pyx.cpp
|
||||
build/
|
||||
+157
-148
@@ -1,39 +1,53 @@
|
||||
# must be build with scons
|
||||
from .messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
from .messaging_pyx import MultiplePublishersError, MessagingError # pylint: disable=no-name-in-module, import-error
|
||||
# must be built with scons
|
||||
from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
||||
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
|
||||
from msgq.ipc_pyx import MultiplePublishersError, IpcError
|
||||
from msgq import fake_event_handle, drain_sock_raw
|
||||
import msgq
|
||||
|
||||
import os
|
||||
import capnp
|
||||
import time
|
||||
|
||||
from typing import Optional, List, Union
|
||||
from collections import deque
|
||||
from typing import Optional, List, Union, Dict
|
||||
|
||||
from cereal import log
|
||||
from cereal.services import service_list
|
||||
|
||||
assert MultiplePublishersError
|
||||
assert MessagingError
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.util import MovingAverage
|
||||
|
||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||
AVG_FREQ_HISTORY = 100
|
||||
SIMULATION = "SIMULATION" in os.environ
|
||||
|
||||
# sec_since_boot is faster, but allow to run standalone too
|
||||
try:
|
||||
from common.realtime import sec_since_boot
|
||||
except ImportError:
|
||||
import time
|
||||
sec_since_boot = time.time
|
||||
print("Warning, using python time.time() instead of faster sec_since_boot")
|
||||
|
||||
context = Context()
|
||||
def pub_sock(endpoint: str) -> PubSocket:
|
||||
service = SERVICE_LIST.get(endpoint)
|
||||
segment_size = service.queue_size if service else 0
|
||||
return msgq.pub_sock(endpoint, segment_size)
|
||||
|
||||
def log_from_bytes(dat: bytes) -> capnp.lib.capnp._DynamicStructReader:
|
||||
return log.Event.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT)
|
||||
|
||||
def new_message(service: Optional[str] = None, size: Optional[int] = None) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
dat = log.Event.new_message()
|
||||
dat.logMonoTime = int(sec_since_boot() * 1e9)
|
||||
dat.valid = True
|
||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
||||
service = SERVICE_LIST.get(endpoint)
|
||||
segment_size = service.queue_size if service else 0
|
||||
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
||||
timeout=timeout, segment_size=segment_size)
|
||||
|
||||
|
||||
def reset_context():
|
||||
msgq.context = Context()
|
||||
|
||||
|
||||
def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event) -> capnp.lib.capnp._DynamicStructReader:
|
||||
with struct.from_bytes(dat, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as msg:
|
||||
return msg
|
||||
|
||||
|
||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
args = {
|
||||
'valid': False,
|
||||
'logMonoTime': int(time.monotonic() * 1e9),
|
||||
**kwargs
|
||||
}
|
||||
dat = log.Event.new_message(**args)
|
||||
if service is not None:
|
||||
if size is None:
|
||||
dat.init(service)
|
||||
@@ -41,56 +55,11 @@ def new_message(service: Optional[str] = None, size: Optional[int] = None) -> ca
|
||||
dat.init(service, size)
|
||||
return dat
|
||||
|
||||
def pub_sock(endpoint: str) -> PubSocket:
|
||||
sock = PubSocket()
|
||||
sock.connect(context, endpoint)
|
||||
return sock
|
||||
|
||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
||||
sock = SubSocket()
|
||||
sock.connect(context, endpoint, addr.encode('utf8'), conflate)
|
||||
|
||||
if timeout is not None:
|
||||
sock.setTimeout(timeout)
|
||||
|
||||
if poller is not None:
|
||||
poller.registerSocket(sock)
|
||||
return sock
|
||||
|
||||
|
||||
def drain_sock_raw(sock: SubSocket, wait_for_one: bool = False) -> List[bytes]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret: List[bytes] = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None:
|
||||
break
|
||||
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
ret: List[capnp.lib.capnp._DynamicStructReader] = []
|
||||
while 1:
|
||||
if wait_for_one and len(ret) == 0:
|
||||
dat = sock.receive()
|
||||
else:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
|
||||
if dat is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = log_from_bytes(dat)
|
||||
ret.append(dat)
|
||||
|
||||
return ret
|
||||
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
||||
return [log_from_bytes(m) for m in msgs]
|
||||
|
||||
|
||||
# TODO: print when we drop packets?
|
||||
@@ -100,32 +69,35 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
|
||||
|
||||
while 1:
|
||||
if wait and dat is None:
|
||||
rcv = sock.receive()
|
||||
recv = sock.receive()
|
||||
else:
|
||||
rcv = sock.receive(non_blocking=True)
|
||||
recv = sock.receive(non_blocking=True)
|
||||
|
||||
if rcv is None: # Timeout hit
|
||||
if recv is None: # Timeout hit
|
||||
break
|
||||
|
||||
dat = rcv
|
||||
dat = recv
|
||||
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
||||
"""Keep receiving until we get a message"""
|
||||
while True:
|
||||
@@ -133,49 +105,105 @@ def recv_one_retry(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader:
|
||||
if dat is not None:
|
||||
return log_from_bytes(dat)
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[List[str]] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
addr: str = "127.0.0.1"):
|
||||
self.frame = -1
|
||||
self.updated = {s: False for s in services}
|
||||
self.rcv_time = {s: 0. for s in services}
|
||||
self.rcv_frame = {s: 0 for s in services}
|
||||
self.alive = {s: False for s in services}
|
||||
self.freq_ok = {s: False for s in services}
|
||||
self.recv_dts = {s: deque([0.0] * AVG_FREQ_HISTORY, maxlen=AVG_FREQ_HISTORY) for s in services}
|
||||
self.sock = {}
|
||||
self.freq = {}
|
||||
self.data = {}
|
||||
self.valid = {}
|
||||
self.logMonoTime = {}
|
||||
|
||||
class FrequencyTracker:
|
||||
def __init__(self, service_freq: float, update_freq: float, is_poll: bool):
|
||||
freq = max(min(service_freq, update_freq), 1.)
|
||||
if is_poll:
|
||||
min_freq = max_freq = freq
|
||||
else:
|
||||
max_freq = min(freq, update_freq)
|
||||
if service_freq >= 2 * update_freq:
|
||||
min_freq = update_freq
|
||||
elif update_freq >= 2* service_freq:
|
||||
min_freq = freq
|
||||
else:
|
||||
min_freq = min(freq, freq / 2.)
|
||||
|
||||
self.min_freq = min_freq * 0.8
|
||||
self.max_freq = max_freq * 1.2
|
||||
self.avg_dt = MovingAverage(int(10 * freq))
|
||||
self.recent_avg_dt = MovingAverage(int(freq))
|
||||
self.prev_time = 0.0
|
||||
|
||||
def record_recv_time(self, cur_time: float) -> None:
|
||||
# TODO: Handle case where cur_time is less than prev_time
|
||||
if self.prev_time > 1e-5:
|
||||
dt = cur_time - self.prev_time
|
||||
|
||||
self.avg_dt.add_value(dt)
|
||||
self.recent_avg_dt.add_value(dt)
|
||||
|
||||
self.prev_time = cur_time
|
||||
|
||||
@property
|
||||
def valid(self) -> bool:
|
||||
if self.avg_dt.count == 0:
|
||||
return False
|
||||
|
||||
avg_freq = 1.0 / self.avg_dt.get_average()
|
||||
if self.min_freq <= avg_freq <= self.max_freq:
|
||||
return True
|
||||
|
||||
avg_freq_recent = 1.0 / self.recent_avg_dt.get_average()
|
||||
return self.min_freq <= avg_freq_recent <= self.max_freq
|
||||
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
||||
self.frame = -1
|
||||
self.services = services
|
||||
self.seen = {s: False for s in services}
|
||||
self.updated = {s: False for s in services}
|
||||
self.recv_time = {s: 0. for s in services}
|
||||
self.recv_frame = {s: 0 for s in services}
|
||||
self.sock = {}
|
||||
self.data = {}
|
||||
self.logMonoTime = {s: 0 for s in services}
|
||||
|
||||
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
||||
self.static_freq_services = set(s for s in services if not on_demand[s])
|
||||
self.alive = {s: on_demand[s] for s in services}
|
||||
self.freq_ok = {s: on_demand[s] for s in services}
|
||||
self.valid = {s: on_demand[s] for s in services}
|
||||
|
||||
self.freq_tracker: Dict[str, FrequencyTracker] = {}
|
||||
self.poller = Poller()
|
||||
self.non_polled_services = [s for s in services if poll is not None and
|
||||
len(poll) and s not in poll]
|
||||
polled_services = set([poll, ] if poll is not None else services)
|
||||
self.non_polled_services = set(services) - polled_services
|
||||
|
||||
self.ignore_average_freq = [] if ignore_avg_freq is None else ignore_avg_freq
|
||||
self.ignore_alive = [] if ignore_alive is None else ignore_alive
|
||||
self.ignore_valid = [] if ignore_valid is None else ignore_valid
|
||||
|
||||
self.simulation = bool(int(os.getenv("SIMULATION", "0")))
|
||||
|
||||
# if freq and poll aren't specified, assume the max to be conservative
|
||||
assert frequency is None or poll is None, "Do not specify 'frequency' - frequency of the polled service will be used."
|
||||
self.update_freq = frequency or max([SERVICE_LIST[s].frequency for s in polled_services])
|
||||
|
||||
for s in services:
|
||||
if addr is not None:
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
self.freq[s] = service_list[s].frequency
|
||||
p = self.poller if s not in self.non_polled_services else None
|
||||
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
|
||||
|
||||
try:
|
||||
data = new_message(s)
|
||||
except capnp.lib.capnp.KjException: # pylint: disable=c-extension-no-member
|
||||
except capnp.lib.capnp.KjException:
|
||||
data = new_message(s, 0) # lists
|
||||
|
||||
self.data[s] = getattr(data, s)
|
||||
self.logMonoTime[s] = 0
|
||||
self.valid[s] = data.valid
|
||||
self.data[s] = getattr(data.as_reader(), s)
|
||||
self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll)
|
||||
|
||||
def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader:
|
||||
return self.data[s]
|
||||
|
||||
def update(self, timeout: int = 1000) -> None:
|
||||
def _check_avg_freq(self, s: str) -> bool:
|
||||
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
|
||||
|
||||
def update(self, timeout: int = 100) -> None:
|
||||
msgs = []
|
||||
for sock in self.poller.poll(timeout):
|
||||
msgs.append(recv_one_or_none(sock))
|
||||
@@ -183,69 +211,43 @@ class SubMaster:
|
||||
# non-blocking receive for non-polled sockets
|
||||
for s in self.non_polled_services:
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
self.update_msgs(sec_since_boot(), msgs)
|
||||
self.update_msgs(time.monotonic(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
self.frame += 1
|
||||
self.updated = dict.fromkeys(self.updated, False)
|
||||
self.updated = dict.fromkeys(self.services, False)
|
||||
for msg in msgs:
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
s = msg.which()
|
||||
self.seen[s] = True
|
||||
self.updated[s] = True
|
||||
|
||||
if self.rcv_time[s] > 1e-5 and self.freq[s] > 1e-5 and (s not in self.non_polled_services) \
|
||||
and (s not in self.ignore_average_freq):
|
||||
self.recv_dts[s].append(cur_time - self.rcv_time[s])
|
||||
|
||||
self.rcv_time[s] = cur_time
|
||||
self.rcv_frame[s] = self.frame
|
||||
self.freq_tracker[s].record_recv_time(cur_time)
|
||||
self.recv_time[s] = cur_time
|
||||
self.recv_frame[s] = self.frame
|
||||
self.data[s] = getattr(msg, s)
|
||||
self.logMonoTime[s] = msg.logMonoTime
|
||||
self.valid[s] = msg.valid
|
||||
|
||||
if SIMULATION:
|
||||
self.freq_ok[s] = True
|
||||
self.alive[s] = True
|
||||
for s in self.static_freq_services:
|
||||
# alive if delay is within 10x the expected frequency; checks relaxed in simulator
|
||||
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
||||
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
||||
|
||||
if not SIMULATION:
|
||||
for s in self.data:
|
||||
# arbitrary small number to avoid float comparison. If freq is 0, we can skip the check
|
||||
if self.freq[s] > 1e-5:
|
||||
# alive if delay is within 10x the expected frequency
|
||||
self.alive[s] = (cur_time - self.rcv_time[s]) < (10. / self.freq[s])
|
||||
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
||||
|
||||
# TODO: check if update frequency is high enough to not drop messages
|
||||
# freq_ok if average frequency is higher than 90% of expected frequency
|
||||
avg_dt = sum(self.recv_dts[s]) / AVG_FREQ_HISTORY
|
||||
expected_dt = 1 / (self.freq[s] * 0.90)
|
||||
self.freq_ok[s] = (avg_dt < expected_dt)
|
||||
else:
|
||||
self.freq_ok[s] = True
|
||||
self.alive[s] = True
|
||||
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
||||
|
||||
def all_alive(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return all(self.alive[s] for s in service_list if s not in self.ignore_alive)
|
||||
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
||||
|
||||
def all_freq_ok(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return all(self.freq_ok[s] for s in service_list if s not in self.ignore_alive)
|
||||
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
||||
|
||||
def all_valid(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.valid.keys()
|
||||
return all(self.valid[s] for s in service_list)
|
||||
|
||||
def all_checks(self, service_list=None) -> bool:
|
||||
if service_list is None: # check all
|
||||
service_list = self.alive.keys()
|
||||
return self.all_alive(service_list=service_list) \
|
||||
and self.all_freq_ok(service_list=service_list) \
|
||||
and self.all_valid(service_list=service_list)
|
||||
|
||||
class PubMaster:
|
||||
def __init__(self, services: List[str]):
|
||||
@@ -258,5 +260,12 @@ class PubMaster:
|
||||
dat = dat.to_bytes()
|
||||
self.sock[s].send(dat)
|
||||
|
||||
def wait_for_readers_to_update(self, s: str, timeout: int, dt: float = 0.05) -> bool:
|
||||
for _ in range(int(timeout*(1./dt))):
|
||||
if self.sock[s].all_readers_updated():
|
||||
return True
|
||||
time.sleep(dt)
|
||||
return False
|
||||
|
||||
def all_readers_updated(self, s: str) -> bool:
|
||||
return self.sock[s].all_readers_updated()
|
||||
return self.sock[s].all_readers_updated() # type: ignore
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "cereal/messaging/msgq_to_zmq.h"
|
||||
#include "cereal/services.h"
|
||||
#include "common/util.h"
|
||||
|
||||
ExitHandler do_exit;
|
||||
|
||||
static std::vector<std::string> get_services(const std::string &whitelist_str, bool zmq_to_msgq) {
|
||||
std::vector<std::string> service_list;
|
||||
for (const auto& it : services) {
|
||||
std::string name = it.second.name;
|
||||
bool in_whitelist = whitelist_str.find(name) != std::string::npos;
|
||||
if (zmq_to_msgq && !in_whitelist) {
|
||||
continue;
|
||||
}
|
||||
service_list.push_back(name);
|
||||
}
|
||||
return service_list;
|
||||
}
|
||||
|
||||
void msgq_to_zmq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
MsgqToZmq bridge;
|
||||
bridge.run(endpoints, ip);
|
||||
}
|
||||
|
||||
void zmq_to_msgq(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
auto poller = std::make_unique<ZMQPoller>();
|
||||
auto pub_context = std::make_unique<MSGQContext>();
|
||||
auto sub_context = std::make_unique<ZMQContext>();
|
||||
std::map<SubSocket *, PubSocket *> sub2pub;
|
||||
|
||||
for (auto endpoint : endpoints) {
|
||||
auto pub_sock = new MSGQPubSocket();
|
||||
auto sub_sock = new ZMQSubSocket();
|
||||
pub_sock->connect(pub_context.get(), endpoint);
|
||||
sub_sock->connect(sub_context.get(), endpoint, ip, false);
|
||||
|
||||
poller->registerSocket(sub_sock);
|
||||
sub2pub[sub_sock] = pub_sock;
|
||||
}
|
||||
|
||||
while (!do_exit) {
|
||||
for (auto sub_sock : poller->poll(100)) {
|
||||
std::unique_ptr<Message> msg(sub_sock->receive(true));
|
||||
if (msg) {
|
||||
sub2pub[sub_sock]->sendMessage(msg.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up allocated sockets
|
||||
for (auto &[sub_sock, pub_sock] : sub2pub) {
|
||||
delete sub_sock;
|
||||
delete pub_sock;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool is_zmq_to_msgq = argc > 2;
|
||||
std::string ip = is_zmq_to_msgq ? argv[1] : "127.0.0.1";
|
||||
std::string whitelist_str = is_zmq_to_msgq ? std::string(argv[2]) : "";
|
||||
std::vector<std::string> endpoints = get_services(whitelist_str, is_zmq_to_msgq);
|
||||
|
||||
if (is_zmq_to_msgq) {
|
||||
zmq_to_msgq(endpoints, ip);
|
||||
} else {
|
||||
msgq_to_zmq(endpoints, ip);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import time
|
||||
|
||||
from messaging_pyx import Context, Poller, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
MSGS = 1e5
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Context()
|
||||
sub_sock = SubSocket()
|
||||
pub_sock = PubSocket()
|
||||
|
||||
sub_sock.connect(c, "controlsState")
|
||||
pub_sock.connect(c, "controlsState")
|
||||
|
||||
poller = Poller()
|
||||
poller.registerSocket(sub_sock)
|
||||
|
||||
t = time.time()
|
||||
for i in range(int(MSGS)):
|
||||
bts = i.to_bytes(4, 'little')
|
||||
pub_sock.send(bts)
|
||||
|
||||
for s in poller.poll(100):
|
||||
dat = s.receive()
|
||||
ii = int.from_bytes(dat, 'little')
|
||||
assert(i == ii)
|
||||
|
||||
dt = time.time() - t
|
||||
print("%.1f msg/s" % (MSGS / dt))
|
||||
@@ -1,72 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <capnp/serialize.h>
|
||||
#include "../gen/cpp/log.capnp.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define CLOCK_BOOTTIME CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
#define MSG_MULTIPLE_PUBLISHERS 100
|
||||
|
||||
bool messaging_use_zmq();
|
||||
|
||||
class Context {
|
||||
public:
|
||||
virtual void * getRawContext() = 0;
|
||||
static Context * create();
|
||||
virtual ~Context(){};
|
||||
};
|
||||
|
||||
class Message {
|
||||
public:
|
||||
virtual void init(size_t size) = 0;
|
||||
virtual void init(char * data, size_t size) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual size_t getSize() = 0;
|
||||
virtual char * getData() = 0;
|
||||
virtual ~Message(){};
|
||||
};
|
||||
|
||||
|
||||
class SubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint, std::string address, bool conflate=false, bool check_endpoint=true) = 0;
|
||||
virtual void setTimeout(int timeout) = 0;
|
||||
virtual Message *receive(bool non_blocking=false) = 0;
|
||||
virtual void * getRawSocket() = 0;
|
||||
static SubSocket * create();
|
||||
static SubSocket * create(Context * context, std::string endpoint, std::string address="127.0.0.1", bool conflate=false, bool check_endpoint=true);
|
||||
virtual ~SubSocket(){};
|
||||
};
|
||||
|
||||
class PubSocket {
|
||||
public:
|
||||
virtual int connect(Context *context, std::string endpoint, bool check_endpoint=true) = 0;
|
||||
virtual int sendMessage(Message *message) = 0;
|
||||
virtual int send(char *data, size_t size) = 0;
|
||||
virtual bool all_readers_updated() = 0;
|
||||
static PubSocket * create();
|
||||
static PubSocket * create(Context * context, std::string endpoint, bool check_endpoint=true);
|
||||
static PubSocket * create(Context * context, std::string endpoint, int port, bool check_endpoint=true);
|
||||
virtual ~PubSocket(){};
|
||||
};
|
||||
|
||||
class Poller {
|
||||
public:
|
||||
virtual void registerSocket(SubSocket *socket) = 0;
|
||||
virtual std::vector<SubSocket*> poll(int timeout) = 0;
|
||||
static Poller * create();
|
||||
static Poller * create(std::vector<SubSocket*> sockets);
|
||||
virtual ~Poller(){};
|
||||
};
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
#include "common/timing.h"
|
||||
#include "msgq/ipc.h"
|
||||
|
||||
class SubMaster {
|
||||
public:
|
||||
SubMaster(const std::vector<const char *> &service_list,
|
||||
SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll = {},
|
||||
const char *address = nullptr, const std::vector<const char *> &ignore_alive = {});
|
||||
void update(int timeout = 1000);
|
||||
void update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages);
|
||||
@@ -98,10 +46,7 @@ public:
|
||||
|
||||
cereal::Event::Builder initEvent(bool valid = true) {
|
||||
cereal::Event::Builder event = initRoot<cereal::Event>();
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_BOOTTIME, &t);
|
||||
uint64_t current_time = t.tv_sec * 1000000000ULL + t.tv_nsec;
|
||||
event.setLogMonoTime(current_time);
|
||||
event.setLogMonoTime(nanos_since_boot());
|
||||
event.setValid(valid);
|
||||
return event;
|
||||
}
|
||||
@@ -111,6 +56,18 @@ public:
|
||||
return heapArray_.asBytes();
|
||||
}
|
||||
|
||||
size_t getSerializedSize() {
|
||||
return capnp::computeSerializedSizeInWords(*this) * sizeof(capnp::word);
|
||||
}
|
||||
|
||||
int serializeToBuffer(unsigned char *buffer, size_t buffer_size) {
|
||||
size_t serialized_size = getSerializedSize();
|
||||
if (serialized_size > buffer_size) { return -1; }
|
||||
kj::ArrayOutputStream out(kj::ArrayPtr<capnp::byte>(buffer, buffer_size));
|
||||
capnp::writeMessage(out, *this);
|
||||
return serialized_size;
|
||||
}
|
||||
|
||||
private:
|
||||
kj::Array<capnp::word> heapArray_;
|
||||
};
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
|
||||
|
||||
cdef extern from "messaging.h":
|
||||
cdef cppclass Context:
|
||||
@staticmethod
|
||||
Context * create()
|
||||
|
||||
cdef cppclass Message:
|
||||
void init(size_t)
|
||||
void init(char *, size_t)
|
||||
void close()
|
||||
size_t getSize()
|
||||
char *getData()
|
||||
|
||||
cdef cppclass SubSocket:
|
||||
@staticmethod
|
||||
SubSocket * create()
|
||||
int connect(Context *, string, string, bool)
|
||||
Message * receive(bool)
|
||||
void setTimeout(int)
|
||||
|
||||
cdef cppclass PubSocket:
|
||||
@staticmethod
|
||||
PubSocket * create()
|
||||
int connect(Context *, string)
|
||||
int sendMessage(Message *)
|
||||
int send(char *, size_t)
|
||||
bool all_readers_updated()
|
||||
|
||||
cdef cppclass Poller:
|
||||
@staticmethod
|
||||
Poller * create()
|
||||
void registerSocket(SubSocket *)
|
||||
vector[SubSocket*] poll(int) nogil
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,54 +0,0 @@
|
||||
# MSGQ: A lock free single producer multi consumer message queue
|
||||
|
||||
## What is MSGQ?
|
||||
MSGQ is a system to pass messages from a single producer to multiple consumers. All the consumers need to be able to receive all the messages. It is designed to be a high performance replacement for ZMQ-like SUB/PUB patterns. It uses a ring buffer in shared memory to efficiently read and write data. Each read requires a copy. Writing can be done without a copy, as long as the size of the data is known in advance.
|
||||
|
||||
## Storage
|
||||
The storage for the queue consists of an area of metadata, and the actual buffer. The metadata contains:
|
||||
|
||||
1. A counter to the number of readers that are active
|
||||
2. A pointer to the head of the queue for writing. From now on referred to as *write pointer*
|
||||
3. A cycle counter for the writer. This counter is incremented when the writer wraps around
|
||||
4. N pointers, pointing to the current read position for all the readers. From now on referred to as *read pointer*
|
||||
5. N counters, counting the number of cycles for all the readers
|
||||
6. N booleans, indicating validity for all the readers. From now on referred to as *validity flag*
|
||||
|
||||
The counter and the pointer are both 32 bit values, packed into 64 bit so they can be read and written atomically.
|
||||
|
||||
The data buffer is a ring buffer. All messages are prefixed by an 8 byte size field, followed by the data. A size of -1 indicates a wrap-around, and means the next message is stored at the beginning of the buffer.
|
||||
|
||||
|
||||
## Writing
|
||||
Writing involves the following steps:
|
||||
|
||||
1. Check if the area that is to be written overlaps with any of the read pointers, mark those readers as invalid by clearing the validity flag.
|
||||
2. Write the message
|
||||
3. Increase the write pointer by the size of the message
|
||||
|
||||
In case there is not enough space at the end of the buffer, a special empty message with a prefix of -1 is written. The cycle counter is incremented by one. In this case step 1 will check there are no read pointers pointing to the remainder of the buffer. Then another write cycle will start with the actual message.
|
||||
|
||||
There always needs to be 8 bytes of empty space at the end of the buffer. By doing this there is always space to write the -1.
|
||||
|
||||
## Reset reader
|
||||
When the reader is lagging too much behind the read pointer becomes invalid and no longer points to the beginning of a valid message. To reset a reader to the current write pointer, the following steps are performed:
|
||||
|
||||
1. Set valid flag
|
||||
2. Set read cycle counter to that of the writer
|
||||
3. Set read pointer to write pointer
|
||||
|
||||
## Reading
|
||||
Reading involves the following steps:
|
||||
|
||||
1. Read the size field at the current read pointer
|
||||
2. Read the validity flag
|
||||
3. Copy the data out of the buffer
|
||||
4. Increase the read pointer by the size of the message
|
||||
5. Check the validity flag again
|
||||
|
||||
Before starting the copy, the valid flag is checked. This is to prevent a race condition where the size prefix was invalid, and the read could read outside of the buffer. Make sure that step 1 and 2 are not reordered by your compiler or CPU.
|
||||
|
||||
If a writer overwrites the data while it's being copied out, the data will be invalid. Therefore the validity flag is also checked after reading it. The order of step 4 and 5 does not matter.
|
||||
|
||||
If at steps 2 or 5 the validity flag is not set, the reader is reset. Any data that was already read is discarded. After the reader is reset, the reading starts from the beginning.
|
||||
|
||||
If a message with size -1 is encountered, step 3 and 4 are replaced by increasing the cycle counter and setting the read pointer to the beginning of the buffer. After that another read is performed.
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "cereal/messaging/msgq_to_zmq.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "common/util.h"
|
||||
|
||||
extern ExitHandler do_exit;
|
||||
|
||||
// Max messages to process per socket per poll
|
||||
constexpr int MAX_MESSAGES_PER_SOCKET = 50;
|
||||
|
||||
static std::string recv_zmq_msg(void *sock) {
|
||||
zmq_msg_t msg;
|
||||
zmq_msg_init(&msg);
|
||||
std::string ret;
|
||||
if (zmq_msg_recv(&msg, sock, 0) > 0) {
|
||||
ret.assign((char *)zmq_msg_data(&msg), zmq_msg_size(&msg));
|
||||
}
|
||||
zmq_msg_close(&msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MsgqToZmq::run(const std::vector<std::string> &endpoints, const std::string &ip) {
|
||||
zmq_context = std::make_unique<ZMQContext>();
|
||||
msgq_context = std::make_unique<MSGQContext>();
|
||||
|
||||
// Create ZMQPubSockets for each endpoint
|
||||
for (const auto &endpoint : endpoints) {
|
||||
auto &socket_pair = socket_pairs.emplace_back();
|
||||
socket_pair.endpoint = endpoint;
|
||||
socket_pair.pub_sock = std::make_unique<ZMQPubSocket>();
|
||||
int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint);
|
||||
if (ret != 0) {
|
||||
printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start ZMQ monitoring thread to monitor socket events
|
||||
std::thread thread(&MsgqToZmq::zmqMonitorThread, this);
|
||||
|
||||
// Main loop for processing messages
|
||||
while (!do_exit) {
|
||||
{
|
||||
std::unique_lock lk(mutex);
|
||||
cv.wait(lk, [this]() { return do_exit || !sub2pub.empty(); });
|
||||
if (do_exit) break;
|
||||
|
||||
for (auto sub_sock : msgq_poller->poll(100)) {
|
||||
// Process messages for each socket
|
||||
ZMQPubSocket *pub_sock = sub2pub.at(sub_sock);
|
||||
for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) {
|
||||
auto msg = std::unique_ptr<Message>(sub_sock->receive(true));
|
||||
if (!msg) break;
|
||||
|
||||
while (pub_sock->sendMessage(msg.get()) == -1) {
|
||||
if (errno != EINTR) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
util::sleep_for(1); // Give zmqMonitorThread a chance to acquire the mutex
|
||||
}
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void MsgqToZmq::zmqMonitorThread() {
|
||||
std::vector<zmq_pollitem_t> pollitems;
|
||||
|
||||
// Set up ZMQ monitor for each pub socket
|
||||
for (int i = 0; i < socket_pairs.size(); ++i) {
|
||||
std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i);
|
||||
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED);
|
||||
|
||||
void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR);
|
||||
zmq_connect(monitor_socket, addr.c_str());
|
||||
pollitems.emplace_back(zmq_pollitem_t{.socket = monitor_socket, .events = ZMQ_POLLIN});
|
||||
}
|
||||
|
||||
while (!do_exit) {
|
||||
int ret = zmq_poll(pollitems.data(), pollitems.size(), 1000);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) {
|
||||
// Due to frequent EINTR signals from msgq, introduce a brief delay (200 ms)
|
||||
// to reduce CPU usage during retry attempts.
|
||||
util::sleep_for(200);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pollitems.size(); ++i) {
|
||||
if (pollitems[i].revents & ZMQ_POLLIN) {
|
||||
// First frame in message contains event number and value
|
||||
std::string frame = recv_zmq_msg(pollitems[i].socket);
|
||||
if (frame.empty()) continue;
|
||||
|
||||
uint16_t event_type = *(uint16_t *)(frame.data());
|
||||
|
||||
// Second frame in message contains event address
|
||||
frame = recv_zmq_msg(pollitems[i].socket);
|
||||
if (frame.empty()) continue;
|
||||
|
||||
std::unique_lock lk(mutex);
|
||||
auto &pair = socket_pairs[i];
|
||||
if (event_type & ZMQ_EVENT_ACCEPTED) {
|
||||
printf("socket [%s] connected\n", pair.endpoint.c_str());
|
||||
if (++pair.connected_clients == 1) {
|
||||
// Create new MSGQ subscriber socket and map to ZMQ publisher
|
||||
pair.sub_sock = std::make_unique<MSGQSubSocket>();
|
||||
pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1");
|
||||
sub2pub[pair.sub_sock.get()] = pair.pub_sock.get();
|
||||
registerSockets();
|
||||
}
|
||||
} else if (event_type & ZMQ_EVENT_DISCONNECTED) {
|
||||
printf("socket [%s] disconnected\n", pair.endpoint.c_str());
|
||||
if (pair.connected_clients == 0 || --pair.connected_clients == 0) {
|
||||
// Remove MSGQ subscriber socket from mapping and reset it
|
||||
sub2pub.erase(pair.sub_sock.get());
|
||||
pair.sub_sock.reset(nullptr);
|
||||
registerSockets();
|
||||
}
|
||||
}
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up monitor sockets
|
||||
for (int i = 0; i < pollitems.size(); ++i) {
|
||||
zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0);
|
||||
zmq_close(pollitems[i].socket);
|
||||
}
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
void MsgqToZmq::registerSockets() {
|
||||
msgq_poller = std::make_unique<MSGQPoller>();
|
||||
for (const auto &socket_pair : socket_pairs) {
|
||||
if (socket_pair.sub_sock) {
|
||||
msgq_poller->registerSocket(socket_pair.sub_sock.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define private public
|
||||
#include "msgq/impl_msgq.h"
|
||||
#include "msgq/impl_zmq.h"
|
||||
|
||||
class MsgqToZmq {
|
||||
public:
|
||||
MsgqToZmq() {}
|
||||
void run(const std::vector<std::string> &endpoints, const std::string &ip);
|
||||
|
||||
protected:
|
||||
void registerSockets();
|
||||
void zmqMonitorThread();
|
||||
|
||||
struct SocketPair {
|
||||
std::string endpoint;
|
||||
std::unique_ptr<ZMQPubSocket> pub_sock;
|
||||
std::unique_ptr<MSGQSubSocket> sub_sock;
|
||||
int connected_clients = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<MSGQContext> msgq_context;
|
||||
std::unique_ptr<ZMQContext> zmq_context;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::unique_ptr<MSGQPoller> msgq_poller;
|
||||
std::map<SubSocket *, ZMQPubSocket *> sub2pub;
|
||||
std::vector<SocketPair> socket_pairs;
|
||||
};
|
||||
@@ -0,0 +1,204 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "cereal/services.h"
|
||||
#include "cereal/messaging/messaging.h"
|
||||
|
||||
const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1");
|
||||
|
||||
static inline bool inList(const std::vector<const char *> &list, const char *value) {
|
||||
for (auto &v : list) {
|
||||
if (strcmp(value, v) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class MessageContext {
|
||||
public:
|
||||
MessageContext() : ctx_(nullptr) {}
|
||||
~MessageContext() { delete ctx_; }
|
||||
inline Context *context() {
|
||||
std::call_once(init_flag, [=]() { ctx_ = Context::create(); });
|
||||
return ctx_;
|
||||
}
|
||||
private:
|
||||
Context *ctx_;
|
||||
std::once_flag init_flag;
|
||||
};
|
||||
|
||||
MessageContext message_context;
|
||||
|
||||
struct SubMaster::SubMessage {
|
||||
std::string name;
|
||||
SubSocket *socket = nullptr;
|
||||
float freq = 0.0f;
|
||||
bool updated = false, alive = false, valid = false, ignore_alive;
|
||||
uint64_t rcv_time = 0, rcv_frame = 0;
|
||||
void *allocated_msg_reader = nullptr;
|
||||
bool is_polled = false;
|
||||
capnp::FlatArrayMessageReader *msg_reader = nullptr;
|
||||
AlignedBuffer aligned_buf;
|
||||
cereal::Event::Reader event;
|
||||
};
|
||||
|
||||
SubMaster::SubMaster(const std::vector<const char *> &service_list, const std::vector<const char *> &poll,
|
||||
const char *address, const std::vector<const char *> &ignore_alive) {
|
||||
poller_ = Poller::create();
|
||||
for (auto name : service_list) {
|
||||
assert(services.count(std::string(name)) > 0);
|
||||
|
||||
service serv = services.at(std::string(name));
|
||||
SubSocket *socket = SubSocket::create(message_context.context(), name, address ? address : "127.0.0.1", true, true, serv.queue_size);
|
||||
assert(socket != 0);
|
||||
bool is_polled = inList(poll, name) || poll.empty();
|
||||
if (is_polled) poller_->registerSocket(socket);
|
||||
SubMessage *m = new SubMessage{
|
||||
.name = name,
|
||||
.socket = socket,
|
||||
.freq = serv.frequency,
|
||||
.ignore_alive = inList(ignore_alive, name),
|
||||
.allocated_msg_reader = malloc(sizeof(capnp::FlatArrayMessageReader)),
|
||||
.is_polled = is_polled};
|
||||
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader({});
|
||||
messages_[socket] = m;
|
||||
services_[name] = m;
|
||||
}
|
||||
}
|
||||
|
||||
void SubMaster::update(int timeout) {
|
||||
for (auto &kv : messages_) kv.second->updated = false;
|
||||
|
||||
auto sockets = poller_->poll(timeout);
|
||||
|
||||
// add non-polled sockets for non-blocking receive
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
SubSocket *s = kv.first;
|
||||
if (!m->is_polled) sockets.push_back(s);
|
||||
}
|
||||
|
||||
uint64_t current_time = nanos_since_boot();
|
||||
|
||||
std::vector<std::pair<std::string, cereal::Event::Reader>> messages;
|
||||
|
||||
for (auto s : sockets) {
|
||||
Message *msg = s->receive(true);
|
||||
if (msg == nullptr) continue;
|
||||
|
||||
SubMessage *m = messages_.at(s);
|
||||
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
capnp::ReaderOptions options;
|
||||
options.traversalLimitInWords = kj::maxValue; // Don't limit
|
||||
m->msg_reader = new (m->allocated_msg_reader) capnp::FlatArrayMessageReader(m->aligned_buf.align(msg), options);
|
||||
delete msg;
|
||||
messages.push_back({m->name, m->msg_reader->getRoot<cereal::Event>()});
|
||||
}
|
||||
|
||||
update_msgs(current_time, messages);
|
||||
}
|
||||
|
||||
void SubMaster::update_msgs(uint64_t current_time, const std::vector<std::pair<std::string, cereal::Event::Reader>> &messages){
|
||||
if (++frame == UINT64_MAX) frame = 1;
|
||||
|
||||
for (auto &kv : messages) {
|
||||
auto m_find = services_.find(kv.first);
|
||||
if (m_find == services_.end()){
|
||||
continue;
|
||||
}
|
||||
SubMessage *m = m_find->second;
|
||||
m->event = kv.second;
|
||||
m->updated = true;
|
||||
m->rcv_time = current_time;
|
||||
m->rcv_frame = frame;
|
||||
m->valid = m->event.getValid();
|
||||
if (SIMULATION) m->alive = true;
|
||||
}
|
||||
|
||||
if (!SIMULATION) {
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
m->alive = (m->freq <= (1e-5) || ((current_time - m->rcv_time) * (1e-9)) < (10.0 / m->freq));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubMaster::all_(const std::vector<const char *> &service_list, bool valid, bool alive) {
|
||||
int found = 0;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
if (service_list.size() == 0 || inList(service_list, m->name.c_str())) {
|
||||
found += (!valid || m->valid) && (!alive || (m->alive || m->ignore_alive));
|
||||
}
|
||||
}
|
||||
return service_list.size() == 0 ? found == messages_.size() : found == service_list.size();
|
||||
}
|
||||
|
||||
void SubMaster::drain() {
|
||||
while (true) {
|
||||
auto polls = poller_->poll(0);
|
||||
if (polls.size() == 0)
|
||||
break;
|
||||
|
||||
for (auto sock : polls) {
|
||||
Message *msg = sock->receive(true);
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SubMaster::updated(const char *name) const {
|
||||
return services_.at(name)->updated;
|
||||
}
|
||||
|
||||
bool SubMaster::alive(const char *name) const {
|
||||
return services_.at(name)->alive;
|
||||
}
|
||||
|
||||
bool SubMaster::valid(const char *name) const {
|
||||
return services_.at(name)->valid;
|
||||
}
|
||||
|
||||
uint64_t SubMaster::rcv_frame(const char *name) const {
|
||||
return services_.at(name)->rcv_frame;
|
||||
}
|
||||
|
||||
uint64_t SubMaster::rcv_time(const char *name) const {
|
||||
return services_.at(name)->rcv_time;
|
||||
}
|
||||
|
||||
cereal::Event::Reader &SubMaster::operator[](const char *name) const {
|
||||
return services_.at(name)->event;
|
||||
}
|
||||
|
||||
SubMaster::~SubMaster() {
|
||||
delete poller_;
|
||||
for (auto &kv : messages_) {
|
||||
SubMessage *m = kv.second;
|
||||
m->msg_reader->~FlatArrayMessageReader();
|
||||
free(m->allocated_msg_reader);
|
||||
delete m->socket;
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
PubMaster::PubMaster(const std::vector<const char *> &service_list) {
|
||||
for (auto name : service_list) {
|
||||
assert(services.count(name) > 0);
|
||||
service serv = services.at(std::string(name));
|
||||
PubSocket *socket = PubSocket::create(message_context.context(), name, true, serv.queue_size);
|
||||
assert(socket);
|
||||
sockets_[name] = socket;
|
||||
}
|
||||
}
|
||||
|
||||
int PubMaster::send(const char *name, MessageBuilder &msg) {
|
||||
auto bytes = msg.toBytes();
|
||||
return send(name, bytes.begin(), bytes.size());
|
||||
}
|
||||
|
||||
PubMaster::~PubMaster() {
|
||||
for (auto s : sockets_) delete s.second;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
from messaging_pyx import Context, SubSocket, PubSocket # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Context()
|
||||
pub_sock = PubSocket()
|
||||
pub_sock.connect(c, "controlsState")
|
||||
|
||||
for i in range(int(1e10)):
|
||||
print(i)
|
||||
sub_sock = SubSocket()
|
||||
sub_sock.connect(c, "controlsState")
|
||||
|
||||
pub_sock.send(b'a')
|
||||
print(sub_sock.receive())
|
||||
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import capnp
|
||||
import multiprocessing
|
||||
import numbers
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
from parameterized import parameterized
|
||||
import pytest
|
||||
|
||||
from cereal import log, car
|
||||
import cereal.messaging as messaging
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
events = [evt for evt in log.Event.schema.union_fields if evt in SERVICE_LIST.keys()]
|
||||
|
||||
def random_sock():
|
||||
return random.choice(events)
|
||||
|
||||
def random_socks(num_socks=10):
|
||||
return list({random_sock() for _ in range(num_socks)})
|
||||
|
||||
def random_bytes(length=1000):
|
||||
return bytes([random.randrange(0xFF) for _ in range(length)])
|
||||
|
||||
def zmq_sleep(t=1):
|
||||
if "ZMQ" in os.environ:
|
||||
time.sleep(t)
|
||||
|
||||
|
||||
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||
def random_carstate():
|
||||
fields = ["vEgo", "aEgo", "brake", "steeringAngleDeg"]
|
||||
msg = messaging.new_message("carState")
|
||||
cs = msg.carState
|
||||
for f in fields:
|
||||
setattr(cs, f, random.random() * 10)
|
||||
return msg
|
||||
|
||||
# TODO: this should compare any capnp structs
|
||||
def assert_carstate(cs1, cs2):
|
||||
for f in car.CarState.schema.non_union_fields:
|
||||
# TODO: check all types
|
||||
val1, val2 = getattr(cs1, f), getattr(cs2, f)
|
||||
if isinstance(val1, numbers.Number):
|
||||
assert val1 == val2, f"{f}: sent '{val1}' vs recvd '{val2}'"
|
||||
|
||||
def delayed_send(delay, sock, dat):
|
||||
def send_func():
|
||||
sock.send(dat)
|
||||
threading.Timer(delay, send_func).start()
|
||||
|
||||
|
||||
class TestMessaging:
|
||||
def setUp(self):
|
||||
# TODO: ZMQ tests are too slow; all sleeps will need to be
|
||||
# replaced with logic to block on the necessary condition
|
||||
if "ZMQ" in os.environ:
|
||||
pytest.skip()
|
||||
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep()
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_new_message(self, evt):
|
||||
try:
|
||||
msg = messaging.new_message(evt)
|
||||
except capnp.lib.capnp.KjException:
|
||||
msg = messaging.new_message(evt, random.randrange(200))
|
||||
assert (time.monotonic() - msg.logMonoTime) < 0.1
|
||||
assert not msg.valid
|
||||
assert evt == msg.which()
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_pub_sock(self, evt):
|
||||
messaging.pub_sock(evt)
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_sub_sock(self, evt):
|
||||
messaging.sub_sock(evt)
|
||||
|
||||
@parameterized.expand([
|
||||
(messaging.drain_sock, capnp._DynamicStructReader),
|
||||
(messaging.drain_sock_raw, bytes),
|
||||
])
|
||||
def test_drain_sock(self, func, expected_type):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msgs in queue
|
||||
msgs = func(sub_sock)
|
||||
assert isinstance(msgs, list)
|
||||
assert len(msgs) == 0
|
||||
|
||||
# no wait but msgs are queued up
|
||||
num_msgs = random.randrange(3, 10)
|
||||
for _ in range(num_msgs):
|
||||
pub_sock.send(messaging.new_message(sock).to_bytes())
|
||||
time.sleep(0.1)
|
||||
msgs = func(sub_sock)
|
||||
assert isinstance(msgs, list)
|
||||
assert all(isinstance(msg, expected_type) for msg in msgs)
|
||||
assert len(msgs) == num_msgs
|
||||
|
||||
def test_recv_sock(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=100)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_sock(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# no wait and one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
time.sleep(0.01)
|
||||
recvd = messaging.recv_sock(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
# https://github.com/python/mypy/issues/13038
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
def test_recv_one(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_one(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
recvd = messaging.recv_one(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
|
||||
def test_recv_one_or_none(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket shouldn't block
|
||||
recvd = messaging.recv_one_or_none(sub_sock)
|
||||
assert recvd is None
|
||||
|
||||
# one msg in queue
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
recvd = messaging.recv_one_or_none(sub_sock)
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
def test_recv_one_retry(self):
|
||||
sock = "carState"
|
||||
sock_timeout = 0.1
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
||||
zmq_sleep()
|
||||
|
||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
||||
if "ZMQ" not in os.environ:
|
||||
# wait 5 socket timeouts and make sure it's still retrying
|
||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||
p.start()
|
||||
time.sleep(sock_timeout*5)
|
||||
assert p.is_alive()
|
||||
p.terminate()
|
||||
|
||||
# wait 5 socket timeouts before sending
|
||||
msg = random_carstate()
|
||||
start_time = time.monotonic()
|
||||
delayed_send(sock_timeout*5, pub_sock, msg.to_bytes())
|
||||
recvd = messaging.recv_one_retry(sub_sock)
|
||||
assert (time.monotonic() - start_time) >= sock_timeout*5
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
@@ -0,0 +1,160 @@
|
||||
import random
|
||||
import time
|
||||
from typing import Sized, cast
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||
random_bytes, random_carstate, assert_carstate, \
|
||||
zmq_sleep
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
class TestSubMaster:
|
||||
|
||||
def setup_method(self):
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
def test_init(self):
|
||||
sm = messaging.SubMaster(events)
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||
assert len(cast(Sized, p)) == len(events)
|
||||
|
||||
def test_init_state(self):
|
||||
socks = random_socks()
|
||||
sm = messaging.SubMaster(socks)
|
||||
assert sm.frame == -1
|
||||
assert not any(sm.updated.values())
|
||||
assert not any(sm.seen.values())
|
||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in sm.services}
|
||||
assert all(sm.alive[s] == sm.valid[s] == sm.freq_ok[s] == on_demand[s] for s in sm.services)
|
||||
assert all(t == 0. for t in sm.recv_time.values())
|
||||
assert all(f == 0 for f in sm.recv_frame.values())
|
||||
assert all(t == 0 for t in sm.logMonoTime.values())
|
||||
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
sm.sock, sm.data, sm.logMonoTime, sm.valid]:
|
||||
assert len(cast(Sized, p)) == len(socks)
|
||||
|
||||
def test_getitem(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
sm.update(1000)
|
||||
assert_carstate(msg.carState, sm[sock])
|
||||
|
||||
# TODO: break this test up to individually test SubMaster.update and SubMaster.update_msgs
|
||||
def test_update(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
for i in range(10):
|
||||
msg = messaging.new_message(sock)
|
||||
pub_sock.send(msg.to_bytes())
|
||||
sm.update(1000)
|
||||
assert sm.frame == i
|
||||
assert all(sm.updated.values())
|
||||
|
||||
def test_update_timeout(self):
|
||||
sock = random_sock()
|
||||
sm = messaging.SubMaster([sock,])
|
||||
timeout = random.randrange(1000, 3000)
|
||||
start_time = time.monotonic()
|
||||
sm.update(timeout)
|
||||
t = time.monotonic() - start_time
|
||||
assert t >= timeout/1000.
|
||||
assert t < 3
|
||||
assert not any(sm.updated.values())
|
||||
|
||||
def test_avg_frequency_checks(self):
|
||||
for poll in (True, False):
|
||||
sm = messaging.SubMaster(["modelV2", "carParams", "carState", "cameraOdometry", "liveCalibration"],
|
||||
poll=("modelV2" if poll else None),
|
||||
frequency=(20. if not poll else None))
|
||||
|
||||
checks = {
|
||||
"carState": (20, 20),
|
||||
"modelV2": (20, 20 if poll else 10),
|
||||
"cameraOdometry": (20, 10),
|
||||
"liveCalibration": (4, 4),
|
||||
"carParams": (None, None),
|
||||
"userBookmark": (None, None),
|
||||
}
|
||||
|
||||
for service, (max_freq, min_freq) in checks.items():
|
||||
if max_freq is not None:
|
||||
assert sm._check_avg_freq(service)
|
||||
assert sm.freq_tracker[service].max_freq == max_freq*1.2
|
||||
assert sm.freq_tracker[service].min_freq == min_freq*0.8
|
||||
else:
|
||||
assert not sm._check_avg_freq(service)
|
||||
|
||||
def test_alive(self):
|
||||
pass
|
||||
|
||||
def test_ignore_alive(self):
|
||||
pass
|
||||
|
||||
def test_valid(self):
|
||||
pass
|
||||
|
||||
# SubMaster should always conflate
|
||||
def test_conflate(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
|
||||
n = 10
|
||||
for i in range(n+1):
|
||||
msg = messaging.new_message(sock)
|
||||
msg.carState.vEgo = i
|
||||
pub_sock.send(msg.to_bytes())
|
||||
time.sleep(0.01)
|
||||
sm.update(1000)
|
||||
assert sm[sock].vEgo == n
|
||||
|
||||
|
||||
class TestPubMaster:
|
||||
|
||||
def setup_method(self):
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
def test_init(self):
|
||||
messaging.PubMaster(events)
|
||||
|
||||
def test_send(self):
|
||||
socks = random_socks()
|
||||
pm = messaging.PubMaster(socks)
|
||||
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
|
||||
zmq_sleep()
|
||||
|
||||
# PubMaster accepts either a capnp msg builder or bytes
|
||||
for capnp in [True, False]:
|
||||
for i in range(100):
|
||||
sock = socks[i % len(socks)]
|
||||
|
||||
if capnp:
|
||||
try:
|
||||
msg = messaging.new_message(sock)
|
||||
except Exception:
|
||||
msg = messaging.new_message(sock, random.randrange(50))
|
||||
else:
|
||||
msg = random_bytes()
|
||||
|
||||
pm.send(sock, msg)
|
||||
recvd = sub_socks[sock].receive()
|
||||
|
||||
if capnp:
|
||||
msg.clear_write_flag()
|
||||
msg = msg.to_bytes()
|
||||
assert msg == recvd, i
|
||||
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Dict
|
||||
from parameterized import parameterized
|
||||
|
||||
import cereal.services as services
|
||||
from cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
class TestServices:
|
||||
|
||||
@parameterized.expand(SERVICE_LIST.keys())
|
||||
def test_services(self, s):
|
||||
service = SERVICE_LIST[s]
|
||||
assert service.frequency <= 104
|
||||
assert service.decimation != 0
|
||||
|
||||
def test_generated_header(self):
|
||||
with tempfile.NamedTemporaryFile(suffix=".h") as f:
|
||||
ret = os.system(f"python3 {services.__file__} > {f.name} && clang++ {f.name} -std=c++11")
|
||||
assert ret == 0, "generated services header is not valid C"
|
||||
@@ -1,76 +0,0 @@
|
||||
/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT services.py */
|
||||
#ifndef __SERVICES_H
|
||||
#define __SERVICES_H
|
||||
struct service { char name[0x100]; int port; bool should_log; int frequency; int decimation; };
|
||||
static struct service services[] = {
|
||||
{ "gyroscope", 8001, true, 104, 104 },
|
||||
{ "gyroscope2", 8002, true, 100, 100 },
|
||||
{ "accelerometer", 8003, true, 104, 104 },
|
||||
{ "accelerometer2", 8004, true, 100, 100 },
|
||||
{ "magnetometer", 8005, true, 100, 100 },
|
||||
{ "lightSensor", 8006, true, 100, 100 },
|
||||
{ "temperatureSensor", 8007, true, 100, 100 },
|
||||
{ "gpsNMEA", 8008, true, 9, -1 },
|
||||
{ "deviceState", 8009, true, 2, 1 },
|
||||
{ "can", 8010, true, 100, -1 },
|
||||
{ "controlsState", 8011, true, 100, 10 },
|
||||
{ "pandaStates", 8012, true, 2, 1 },
|
||||
{ "peripheralState", 8013, true, 2, 1 },
|
||||
{ "radarState", 8014, true, 20, 5 },
|
||||
{ "roadEncodeIdx", 8015, false, 20, 1 },
|
||||
{ "liveTracks", 8016, true, 20, -1 },
|
||||
{ "sendcan", 8017, true, 100, 139 },
|
||||
{ "logMessage", 8018, true, 0, -1 },
|
||||
{ "errorLogMessage", 8019, true, 0, 1 },
|
||||
{ "liveCalibration", 8020, true, 4, 4 },
|
||||
{ "liveTorqueParameters", 8021, true, 4, 1 },
|
||||
{ "androidLog", 8023, true, 0, -1 },
|
||||
{ "carState", 8024, true, 100, 10 },
|
||||
{ "carControl", 8025, true, 100, 10 },
|
||||
{ "longitudinalPlan", 8026, true, 20, 5 },
|
||||
{ "procLog", 8027, true, 0, -1 },
|
||||
{ "gpsLocationExternal", 8028, true, 10, 10 },
|
||||
{ "gpsLocation", 8029, true, 1, 1 },
|
||||
{ "ubloxGnss", 8030, true, 10, -1 },
|
||||
{ "qcomGnss", 8031, true, 2, -1 },
|
||||
{ "gnssMeasurements", 8032, true, 10, 10 },
|
||||
{ "clocks", 8033, true, 1, 1 },
|
||||
{ "ubloxRaw", 8034, true, 20, -1 },
|
||||
{ "liveLocationKalman", 8035, true, 20, 5 },
|
||||
{ "liveParameters", 8036, true, 20, 5 },
|
||||
{ "cameraOdometry", 8037, true, 20, 5 },
|
||||
{ "lateralPlan", 8038, true, 20, 5 },
|
||||
{ "thumbnail", 8039, true, 0, 1 },
|
||||
{ "carEvents", 8040, true, 1, 1 },
|
||||
{ "carParams", 8041, true, 0, 1 },
|
||||
{ "roadCameraState", 8042, true, 20, 20 },
|
||||
{ "driverCameraState", 8043, true, 10, 10 },
|
||||
{ "driverEncodeIdx", 8044, false, 10, 1 },
|
||||
{ "driverStateV2", 8045, true, 20, 10 },
|
||||
{ "driverState", 8046, true, 10, 5 },
|
||||
{ "driverMonitoringState", 8047, true, 10, 5 },
|
||||
{ "wideRoadEncodeIdx", 8048, false, 20, 1 },
|
||||
{ "wideRoadCameraState", 8049, true, 20, 20 },
|
||||
{ "modelV2", 8050, true, 20, 40 },
|
||||
{ "managerState", 8051, true, 2, 1 },
|
||||
{ "uploaderState", 8052, true, 0, 1 },
|
||||
{ "navInstruction", 8053, true, 1, 10 },
|
||||
{ "navRoute", 8054, true, 0, -1 },
|
||||
{ "navThumbnail", 8055, true, 0, -1 },
|
||||
{ "navModel", 8056, true, 2, 4 },
|
||||
{ "mapRenderState", 8057, true, 2, 1 },
|
||||
{ "qRoadEncodeIdx", 8058, false, 20, -1 },
|
||||
{ "userFlag", 8059, true, 0, 1 },
|
||||
{ "microphone", 8060, true, 10, 10 },
|
||||
{ "uiDebug", 8061, true, 0, 1 },
|
||||
{ "testJoystick", 8062, true, 0, -1 },
|
||||
{ "roadEncodeData", 8063, false, 20, -1 },
|
||||
{ "driverEncodeData", 8064, false, 20, -1 },
|
||||
{ "wideRoadEncodeData", 8065, false, 20, -1 },
|
||||
{ "qRoadEncodeData", 8066, false, 20, -1 },
|
||||
{ "dragonConf", 8067, false, 1, -1 },
|
||||
{ "liveMapData", 8068, true, 0, -1 },
|
||||
{ "sensorEvents", 8069, true, 100, 100 },
|
||||
};
|
||||
#endif
|
||||
|
||||
+68
-50
@@ -1,101 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
RESERVED_PORT = 8022 # sshd
|
||||
STARTING_PORT = 8001
|
||||
|
||||
|
||||
def new_port(port: int):
|
||||
port += STARTING_PORT
|
||||
return port + 1 if port >= RESERVED_PORT else port
|
||||
# TODO: this should be automatically determined using the capnp schema
|
||||
class QueueSize(IntEnum):
|
||||
BIG = 10 * 1024 * 1024 # 10MB - video frames, large AI outputs
|
||||
MEDIUM = 2 * 1024 * 1024 # 2MB - high freq (CAN), livestream
|
||||
SMALL = 250 * 1024 # 250KB - most services
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, port: int, should_log: bool, frequency: float, decimation: Optional[int] = None):
|
||||
self.port = port
|
||||
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
|
||||
queue_size: QueueSize = QueueSize.SMALL):
|
||||
self.should_log = should_log
|
||||
self.frequency = frequency
|
||||
self.decimation = decimation
|
||||
self.queue_size = queue_size
|
||||
|
||||
|
||||
services = {
|
||||
_services: dict[str, tuple] = {
|
||||
# service: (should_log, frequency, qlog decimation (optional))
|
||||
# note: the "EncodeIdx" packets will still be in the log
|
||||
"gyroscope": (True, 104., 104),
|
||||
"gyroscope2": (True, 100., 100),
|
||||
"accelerometer": (True, 104., 104),
|
||||
"accelerometer2": (True, 100., 100),
|
||||
"magnetometer": (True, 100., 100),
|
||||
"magnetometer": (True, 25.),
|
||||
"lightSensor": (True, 100., 100),
|
||||
"temperatureSensor": (True, 100., 100),
|
||||
"temperatureSensor": (True, 2., 200),
|
||||
"gpsNMEA": (True, 9.),
|
||||
"deviceState": (True, 2., 1),
|
||||
"can": (True, 100.),
|
||||
"controlsState": (True, 100., 10),
|
||||
"pandaStates": (True, 2., 1),
|
||||
"touch": (True, 20., 1),
|
||||
"can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment
|
||||
"controlsState": (True, 100., 10, QueueSize.MEDIUM),
|
||||
"selfdriveState": (True, 100., 10),
|
||||
"pandaStates": (True, 10., 1),
|
||||
"peripheralState": (True, 2., 1),
|
||||
"radarState": (True, 20., 5),
|
||||
"roadEncodeIdx": (False, 20., 1),
|
||||
"liveTracks": (True, 20.),
|
||||
"sendcan": (True, 100., 139),
|
||||
"sendcan": (True, 100., 139, QueueSize.MEDIUM),
|
||||
"logMessage": (True, 0.),
|
||||
"errorLogMessage": (True, 0., 1),
|
||||
"liveCalibration": (True, 4., 4),
|
||||
"liveTorqueParameters": (True, 4., 1),
|
||||
"liveDelay": (True, 4., 1),
|
||||
"androidLog": (True, 0.),
|
||||
"carState": (True, 100., 10),
|
||||
"carControl": (True, 100., 10),
|
||||
"longitudinalPlan": (True, 20., 5),
|
||||
"procLog": (True, 0.5),
|
||||
"carOutput": (True, 100., 10),
|
||||
"longitudinalPlan": (True, 20., 10),
|
||||
"driverAssistance": (True, 20., 20),
|
||||
"procLog": (True, 0.5, 15, QueueSize.BIG),
|
||||
"gpsLocationExternal": (True, 10., 10),
|
||||
"gpsLocation": (True, 1., 1),
|
||||
"ubloxGnss": (True, 10.),
|
||||
"qcomGnss": (True, 2.),
|
||||
"gnssMeasurements": (True, 10., 10),
|
||||
"clocks": (True, 1., 1),
|
||||
"clocks": (True, 0.1, 1),
|
||||
"ubloxRaw": (True, 20.),
|
||||
"liveLocationKalman": (True, 20., 5),
|
||||
"livePose": (True, 20., 4),
|
||||
"liveParameters": (True, 20., 5),
|
||||
"cameraOdometry": (True, 20., 5),
|
||||
"lateralPlan": (True, 20., 5),
|
||||
"thumbnail": (True, 0.2, 1),
|
||||
"carEvents": (True, 1., 1),
|
||||
"cameraOdometry": (True, 20., 10),
|
||||
"thumbnail": (True, 1 / 60., 1),
|
||||
"onroadEvents": (True, 1., 1),
|
||||
"carParams": (True, 0.02, 1),
|
||||
"roadCameraState": (True, 20., 20),
|
||||
"driverCameraState": (True, 10., 10),
|
||||
"driverEncodeIdx": (False, 10., 1),
|
||||
"driverCameraState": (True, 20., 20),
|
||||
"driverEncodeIdx": (False, 20., 1),
|
||||
"driverStateV2": (True, 20., 10),
|
||||
"driverState": (True, 10, 5),
|
||||
"driverMonitoringState": (True, 10., 5),
|
||||
"driverMonitoringState": (True, 20., 10),
|
||||
"wideRoadEncodeIdx": (False, 20., 1),
|
||||
"wideRoadCameraState": (True, 20., 20),
|
||||
"modelV2": (True, 20., 40),
|
||||
"drivingModelData": (True, 20., 10),
|
||||
"modelV2": (True, 20., None, QueueSize.BIG),
|
||||
"managerState": (True, 2., 1),
|
||||
"uploaderState": (True, 0., 1),
|
||||
"navInstruction": (True, 1., 10),
|
||||
"navRoute": (True, 0.),
|
||||
"navThumbnail": (True, 0.),
|
||||
"navModel": (True, 2., 4.),
|
||||
"mapRenderState": (True, 2., 1.),
|
||||
"qRoadEncodeIdx": (False, 20.),
|
||||
"userFlag": (True, 0., 1),
|
||||
"microphone": (True, 10., 10),
|
||||
"userBookmark": (True, 0., 1),
|
||||
"soundPressure": (True, 10., 10),
|
||||
"rawAudioData": (False, 20.),
|
||||
"bookmarkButton": (True, 0., 1),
|
||||
"audioFeedback": (True, 0., 1),
|
||||
"roadEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
"driverEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
"wideRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
"qRoadEncodeData": (False, 20., None, QueueSize.BIG),
|
||||
|
||||
# debug
|
||||
"uiDebug": (True, 0., 1),
|
||||
"testJoystick": (True, 0.),
|
||||
"roadEncodeData": (False, 20.),
|
||||
"driverEncodeData": (False, 20.),
|
||||
"wideRoadEncodeData": (False, 20.),
|
||||
"qRoadEncodeData": (False, 20.),
|
||||
|
||||
# dp
|
||||
"dragonConf": (False, 1.),
|
||||
"liveMapData": (True, 0.),
|
||||
"sensorEvents": (True, 100., 100),
|
||||
"alertDebug": (True, 20., 5),
|
||||
"livestreamWideRoadEncodeIdx": (False, 20.),
|
||||
"livestreamRoadEncodeIdx": (False, 20.),
|
||||
"livestreamDriverEncodeIdx": (False, 20.),
|
||||
"livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
||||
"livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
||||
"livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM),
|
||||
"customReservedRawData0": (True, 0.),
|
||||
"customReservedRawData1": (True, 0.),
|
||||
"customReservedRawData2": (True, 0.),
|
||||
"controlsStateExt": (True, 100.),
|
||||
"carStateExt": (True, 100.),
|
||||
"modelExt": (True, 20.),
|
||||
"liveGPS": (True, 20.),
|
||||
}
|
||||
service_list = {name: Service(new_port(idx), *vals) for # type: ignore
|
||||
idx, (name, vals) in enumerate(services.items())}
|
||||
SERVICE_LIST = {name: Service(*vals) for
|
||||
idx, (name, vals) in enumerate(_services.items())}
|
||||
|
||||
|
||||
def build_header():
|
||||
@@ -103,14 +116,19 @@ def build_header():
|
||||
h += "/* THIS IS AN AUTOGENERATED FILE, PLEASE EDIT services.py */\n"
|
||||
h += "#ifndef __SERVICES_H\n"
|
||||
h += "#define __SERVICES_H\n"
|
||||
h += "struct service { char name[0x100]; int port; bool should_log; int frequency; int decimation; };\n"
|
||||
h += "static struct service services[] = {\n"
|
||||
for k, v in service_list.items():
|
||||
|
||||
h += "#include <map>\n"
|
||||
h += "#include <string>\n"
|
||||
|
||||
h += "struct service { std::string name; bool should_log; float frequency; int decimation; size_t queue_size; };\n"
|
||||
h += "static std::map<std::string, service> services = {\n"
|
||||
for k, v in SERVICE_LIST.items():
|
||||
should_log = "true" if v.should_log else "false"
|
||||
decimation = -1 if v.decimation is None else v.decimation
|
||||
h += ' { "%s", %d, %s, %d, %d },\n' % \
|
||||
(k, v.port, should_log, v.frequency, decimation)
|
||||
h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
|
||||
(k, k, should_log, v.frequency, decimation, v.queue_size)
|
||||
h += "};\n"
|
||||
|
||||
h += "#endif\n"
|
||||
return h
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
|
||||
int ipc_connect(const char* socket_path);
|
||||
int ipc_bind(const char* socket_path);
|
||||
int ipc_sendrecv_with_fds(bool send, int fd, void *buf, size_t buf_size, int* fds, int num_fds,
|
||||
int *out_num_fds);
|
||||
@@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
#include "visionipc.h"
|
||||
|
||||
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
|
||||
#ifdef __APPLE__
|
||||
#include <OpenCL/cl.h>
|
||||
#else
|
||||
#include <CL/cl.h>
|
||||
#endif
|
||||
|
||||
#define VISIONBUF_SYNC_FROM_DEVICE 0
|
||||
#define VISIONBUF_SYNC_TO_DEVICE 1
|
||||
|
||||
enum VisionStreamType {
|
||||
VISION_STREAM_RGB_ROAD,
|
||||
VISION_STREAM_RGB_DRIVER,
|
||||
VISION_STREAM_RGB_WIDE_ROAD,
|
||||
|
||||
VISION_STREAM_ROAD,
|
||||
VISION_STREAM_DRIVER,
|
||||
VISION_STREAM_WIDE_ROAD,
|
||||
|
||||
VISION_STREAM_RGB_MAP,
|
||||
VISION_STREAM_MAX,
|
||||
};
|
||||
|
||||
class VisionBuf {
|
||||
public:
|
||||
size_t len = 0;
|
||||
size_t mmap_len = 0;
|
||||
void * addr = nullptr;
|
||||
uint64_t *frame_id;
|
||||
int fd = 0;
|
||||
|
||||
bool rgb = false;
|
||||
size_t width = 0;
|
||||
size_t height = 0;
|
||||
size_t stride = 0;
|
||||
|
||||
// YUV
|
||||
uint8_t * y = nullptr;
|
||||
uint8_t * u = nullptr;
|
||||
uint8_t * v = nullptr;
|
||||
|
||||
// Visionipc
|
||||
uint64_t server_id = 0;
|
||||
size_t idx = 0;
|
||||
VisionStreamType type;
|
||||
|
||||
// OpenCL
|
||||
cl_mem buf_cl = nullptr;
|
||||
cl_command_queue copy_q = nullptr;
|
||||
|
||||
// ion
|
||||
int handle = 0;
|
||||
|
||||
void allocate(size_t len);
|
||||
void import();
|
||||
void init_cl(cl_device_id device_id, cl_context ctx);
|
||||
void init_rgb(size_t width, size_t height, size_t stride);
|
||||
void init_yuv(size_t width, size_t height);
|
||||
int sync(int dir);
|
||||
int free();
|
||||
|
||||
void set_frame_id(uint64_t id);
|
||||
uint64_t get_frame_id();
|
||||
};
|
||||
|
||||
void visionbuf_compute_aligned_width_and_height(int width, int height, int *aligned_w, int *aligned_h);
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
constexpr int VISIONIPC_MAX_FDS = 128;
|
||||
|
||||
struct VisionIpcBufExtra {
|
||||
uint32_t frame_id;
|
||||
uint64_t timestamp_sof;
|
||||
uint64_t timestamp_eof;
|
||||
};
|
||||
|
||||
struct VisionIpcPacket {
|
||||
uint64_t server_id;
|
||||
size_t idx;
|
||||
struct VisionIpcBufExtra extra;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libc.stdint cimport uint32_t, uint64_t
|
||||
from libcpp cimport bool
|
||||
|
||||
cdef extern from "visionbuf.h":
|
||||
cdef enum VisionStreamType:
|
||||
pass
|
||||
|
||||
cdef cppclass VisionBuf:
|
||||
void * addr
|
||||
size_t len
|
||||
size_t width
|
||||
size_t height
|
||||
size_t stride
|
||||
|
||||
cdef extern from "visionipc.h":
|
||||
struct VisionIpcBufExtra:
|
||||
uint32_t frame_id
|
||||
uint64_t timestamp_sof
|
||||
uint64_t timestamp_eof
|
||||
|
||||
cdef extern from "visionipc_server.h":
|
||||
cdef cppclass VisionIpcServer:
|
||||
VisionIpcServer(string, void*, void*)
|
||||
void create_buffers(VisionStreamType, size_t, bool, size_t, size_t)
|
||||
VisionBuf * get_buffer(VisionStreamType)
|
||||
void send(VisionBuf *, VisionIpcBufExtra *, bool)
|
||||
void start_listener()
|
||||
|
||||
cdef extern from "visionipc_client.h":
|
||||
cdef cppclass VisionIpcClient:
|
||||
VisionIpcClient(string, VisionStreamType, bool, void*, void*)
|
||||
VisionBuf * recv(VisionIpcBufExtra *, int)
|
||||
bool connect(bool)
|
||||
bool is_connected()
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
|
||||
#include "messaging/messaging.h"
|
||||
#include "visionipc/visionipc.h"
|
||||
#include "visionipc/visionbuf.h"
|
||||
|
||||
std::string get_endpoint_name(std::string name, VisionStreamType type);
|
||||
|
||||
class VisionIpcServer {
|
||||
private:
|
||||
cl_device_id device_id = nullptr;
|
||||
cl_context ctx = nullptr;
|
||||
uint64_t server_id;
|
||||
|
||||
std::atomic<bool> should_exit = false;
|
||||
std::string name;
|
||||
std::thread listener_thread;
|
||||
|
||||
std::map<VisionStreamType, std::atomic<size_t> > cur_idx;
|
||||
std::map<VisionStreamType, std::vector<VisionBuf*> > buffers;
|
||||
std::map<VisionStreamType, std::map<VisionBuf*, size_t> > idxs;
|
||||
|
||||
Context * msg_ctx;
|
||||
std::map<VisionStreamType, PubSocket*> sockets;
|
||||
|
||||
void listener(void);
|
||||
|
||||
public:
|
||||
VisionIpcServer(std::string name, cl_device_id device_id=nullptr, cl_context ctx=nullptr);
|
||||
~VisionIpcServer();
|
||||
|
||||
VisionBuf * get_buffer(VisionStreamType type);
|
||||
|
||||
void create_buffers(VisionStreamType type, size_t num_buffers, bool rgb, size_t width, size_t height);
|
||||
void send(VisionBuf * buf, VisionIpcBufExtra * extra, bool sync=true);
|
||||
void start_listener();
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
Import('env', 'envCython', 'arch')
|
||||
|
||||
common_libs = [
|
||||
'params.cc',
|
||||
'swaglog.cc',
|
||||
'util.cc',
|
||||
'ratekeeper.cc',
|
||||
'clutil.cc',
|
||||
]
|
||||
|
||||
_common = env.Library('common', common_libs, LIBS="json11")
|
||||
Export('_common')
|
||||
|
||||
if GetOption('extras'):
|
||||
env.Program('tests/test_common',
|
||||
['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'],
|
||||
LIBS=[_common, 'json11', 'zmq', 'pthread'])
|
||||
|
||||
# Cython bindings
|
||||
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
|
||||
|
||||
SConscript([
|
||||
'transformations/SConscript',
|
||||
])
|
||||
|
||||
Import('transformations_python')
|
||||
common_python = [params_python, transformations_python]
|
||||
|
||||
Export('common_python')
|
||||
@@ -0,0 +1,62 @@
|
||||
import jwt
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.system.version import get_version
|
||||
|
||||
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
|
||||
|
||||
# name: jwt signature algorithm
|
||||
KEYS = {"id_rsa": "RS256",
|
||||
"id_ecdsa": "ES256"}
|
||||
|
||||
|
||||
class Api:
|
||||
def __init__(self, dongle_id):
|
||||
self.dongle_id = dongle_id
|
||||
self.jwt_algorithm, self.private_key, _ = get_key_pair()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.request('GET', *args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.request('POST', *args, **kwargs)
|
||||
|
||||
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
||||
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
||||
|
||||
def get_token(self, payload_extra=None, expiry_hours=1):
|
||||
now = datetime.now(UTC).replace(tzinfo=None)
|
||||
payload = {
|
||||
'identity': self.dongle_id,
|
||||
'nbf': now,
|
||||
'iat': now,
|
||||
'exp': now + timedelta(hours=expiry_hours)
|
||||
}
|
||||
if payload_extra is not None:
|
||||
payload.update(payload_extra)
|
||||
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
|
||||
if isinstance(token, bytes):
|
||||
token = token.decode('utf8')
|
||||
return token
|
||||
|
||||
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
|
||||
headers = {}
|
||||
if access_token is not None:
|
||||
headers['Authorization'] = "JWT " + access_token
|
||||
|
||||
headers['User-Agent'] = "openpilot-" + get_version()
|
||||
|
||||
# TODO: add session to Api
|
||||
req = requests if session is None else session
|
||||
return req.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params)
|
||||
|
||||
|
||||
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
|
||||
for key in KEYS:
|
||||
if os.path.isfile(Paths.persist_root() + f'/comma/{key}') and os.path.isfile(Paths.persist_root() + f'/comma/{key}.pub'):
|
||||
with open(Paths.persist_root() + f'/comma/{key}') as private, open(Paths.persist_root() + f'/comma/{key}.pub') as public:
|
||||
return KEYS[key], private.read(), public.read()
|
||||
return None, None, None
|
||||
@@ -1,47 +0,0 @@
|
||||
import jwt
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from common.basedir import PERSIST
|
||||
from system.version import get_version
|
||||
|
||||
from common.params import Params
|
||||
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') if not Params().get_bool("dp_api_custom") else Params().get("dp_api_custom_url", encoding='utf-8')
|
||||
|
||||
class Api():
|
||||
def __init__(self, dongle_id):
|
||||
self.dongle_id = dongle_id
|
||||
with open(PERSIST+'/comma/id_rsa') as f:
|
||||
self.private_key = f.read()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.request('GET', *args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
return self.request('POST', *args, **kwargs)
|
||||
|
||||
def request(self, method, endpoint, timeout=None, access_token=None, **params):
|
||||
return api_get(endpoint, method=method, timeout=timeout, access_token=access_token, **params)
|
||||
|
||||
def get_token(self, expiry_hours=1):
|
||||
now = datetime.utcnow()
|
||||
payload = {
|
||||
'identity': self.dongle_id,
|
||||
'nbf': now,
|
||||
'iat': now,
|
||||
'exp': now + timedelta(hours=expiry_hours)
|
||||
}
|
||||
token = jwt.encode(payload, self.private_key, algorithm='RS256')
|
||||
if isinstance(token, bytes):
|
||||
token = token.decode('utf8')
|
||||
return token
|
||||
|
||||
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
|
||||
headers = {}
|
||||
if access_token is not None:
|
||||
headers['Authorization'] = "JWT " + access_token
|
||||
|
||||
headers['User-Agent'] = "openpilot-" + get_version()
|
||||
|
||||
return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params)
|
||||
@@ -1,11 +1,4 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from system.hardware import PC
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
|
||||
|
||||
if PC:
|
||||
PERSIST = os.path.join(str(Path.home()), ".comma", "persist")
|
||||
else:
|
||||
PERSIST = "/persist"
|
||||
|
||||
-2804
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level = 3
|
||||
from posix.time cimport clock_gettime, timespec, CLOCK_MONOTONIC_RAW, clockid_t
|
||||
|
||||
IF UNAME_SYSNAME == "Darwin":
|
||||
# Darwin doesn't have a CLOCK_BOOTTIME
|
||||
CLOCK_BOOTTIME = CLOCK_MONOTONIC_RAW
|
||||
ELSE:
|
||||
from posix.time cimport CLOCK_BOOTTIME
|
||||
|
||||
cdef double readclock(clockid_t clock_id):
|
||||
cdef timespec ts
|
||||
cdef double current
|
||||
|
||||
clock_gettime(clock_id, &ts)
|
||||
current = ts.tv_sec + (ts.tv_nsec / 1000000000.)
|
||||
return current
|
||||
|
||||
def monotonic_time():
|
||||
return readclock(CLOCK_MONOTONIC_RAW)
|
||||
|
||||
def sec_since_boot():
|
||||
return readclock(CLOCK_BOOTTIME)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
#include "common/clutil.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/swaglog.h"
|
||||
|
||||
namespace { // helper functions
|
||||
|
||||
template <typename Func, typename Id, typename Name>
|
||||
std::string get_info(Func get_info_func, Id id, Name param_name) {
|
||||
size_t size = 0;
|
||||
CL_CHECK(get_info_func(id, param_name, 0, NULL, &size));
|
||||
std::string info(size, '\0');
|
||||
CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL));
|
||||
return info;
|
||||
}
|
||||
inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); }
|
||||
inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); }
|
||||
|
||||
void cl_print_info(cl_platform_id platform, cl_device_id device) {
|
||||
size_t work_group_size = 0;
|
||||
cl_device_type device_type = 0;
|
||||
clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL);
|
||||
clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL);
|
||||
const char *type_str = "Other...";
|
||||
switch (device_type) {
|
||||
case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break;
|
||||
case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break;
|
||||
case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break;
|
||||
}
|
||||
|
||||
LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str());
|
||||
LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str());
|
||||
LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str());
|
||||
LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str());
|
||||
LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str());
|
||||
LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str());
|
||||
LOGD("max work group size: %zu", work_group_size);
|
||||
LOGD("type = %d, %s", (int)device_type, type_str);
|
||||
}
|
||||
|
||||
void cl_print_build_errors(cl_program program, cl_device_id device) {
|
||||
cl_build_status status;
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL);
|
||||
size_t log_size;
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
|
||||
std::string log(log_size, '\0');
|
||||
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL);
|
||||
|
||||
LOGE("build failed; status=%d, log: %s", status, log.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cl_device_id cl_get_device_id(cl_device_type device_type) {
|
||||
cl_uint num_platforms = 0;
|
||||
CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms));
|
||||
std::unique_ptr<cl_platform_id[]> platform_ids = std::make_unique<cl_platform_id[]>(num_platforms);
|
||||
CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL));
|
||||
|
||||
for (size_t i = 0; i < num_platforms; ++i) {
|
||||
LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str());
|
||||
|
||||
// Get first device
|
||||
if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) {
|
||||
cl_print_info(platform_ids[i], device_id);
|
||||
return device_id;
|
||||
}
|
||||
}
|
||||
LOGE("No valid openCL platform found");
|
||||
assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cl_context cl_create_context(cl_device_id device_id) {
|
||||
return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
|
||||
}
|
||||
|
||||
void cl_release_context(cl_context context) {
|
||||
clReleaseContext(context);
|
||||
}
|
||||
|
||||
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) {
|
||||
return cl_program_from_source(ctx, device_id, util::read_file(path), args);
|
||||
}
|
||||
|
||||
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) {
|
||||
const char *csrc = src.c_str();
|
||||
cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err));
|
||||
if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) {
|
||||
cl_print_build_errors(prg, device_id);
|
||||
assert(0);
|
||||
}
|
||||
return prg;
|
||||
}
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
})
|
||||
|
||||
cl_device_id cl_get_device_id(cl_device_type device_type);
|
||||
cl_context cl_create_context(cl_device_id device_id);
|
||||
void cl_release_context(cl_context context);
|
||||
cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr);
|
||||
cl_program cl_program_from_binary(cl_context ctx, cl_device_id device_id, const uint8_t* binary, size_t length, const char* args = nullptr);
|
||||
cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args);
|
||||
const char* cl_get_error_string(int err);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import numpy as np
|
||||
|
||||
# conversions
|
||||
class CV:
|
||||
# Speed
|
||||
MPH_TO_KPH = 1.609344
|
||||
KPH_TO_MPH = 1. / MPH_TO_KPH
|
||||
MS_TO_KPH = 3.6
|
||||
KPH_TO_MS = 1. / MS_TO_KPH
|
||||
MS_TO_MPH = MS_TO_KPH * KPH_TO_MPH
|
||||
MPH_TO_MS = MPH_TO_KPH * KPH_TO_MS
|
||||
MS_TO_KNOTS = 1.9438
|
||||
KNOTS_TO_MS = 1. / MS_TO_KNOTS
|
||||
|
||||
# Angle
|
||||
DEG_TO_RAD = np.pi / 180.
|
||||
RAD_TO_DEG = 1. / DEG_TO_RAD
|
||||
|
||||
# Mass
|
||||
LB_TO_KG = 0.453592
|
||||
|
||||
|
||||
ACCELERATION_DUE_TO_GRAVITY = 9.81 # m/s^2
|
||||
@@ -1,9 +0,0 @@
|
||||
# remove all keys that end in DEPRECATED
|
||||
def strip_deprecated_keys(d):
|
||||
for k in list(d.keys()):
|
||||
if isinstance(k, str):
|
||||
if k.endswith('DEPRECATED'):
|
||||
d.pop(k)
|
||||
elif isinstance(d[k], dict):
|
||||
strip_deprecated_keys(d[k])
|
||||
return d
|
||||
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env python3.7
|
||||
#pylint: skip-file
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from math import floor
|
||||
from system.hardware import TICI
|
||||
|
||||
'''
|
||||
* type: Bool, Int8, UInt8, UInt16, Float32
|
||||
* conf_type: param, struct
|
||||
* dependencies needs to use struct and loaded prior so we don't have to read the param multiple times.
|
||||
* update_once: True, False (the param will only load up once, need both param and struct defined)
|
||||
'''
|
||||
confs = [
|
||||
# custom api server
|
||||
{'name': 'dp_api_custom', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_api_custom_url', 'default': 'https://api.retropilot.org', 'type': 'Text', 'depends': [{'name': 'dp_api_custom', 'vals': [True]}], 'conf_type': ['param']},
|
||||
|
||||
{'name': 'dp_atl', 'default': 0, 'type': 'UInt8', 'conf_type': ['param', 'struct'], 'update_once': True},
|
||||
|
||||
{'name': 'dp_locale', 'default': 'en-US', 'type': 'Text', 'conf_type': ['param', 'struct'], 'update_once': True},
|
||||
{'name': 'dp_jetson', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
|
||||
# nav service
|
||||
{'name': 'dp_nav', 'default': True, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_otisserv', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_mapbox_token_pk', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_mapbox_token_sk', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_full_screen', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_gmap_enable', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_gmap_key', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_amap_enable', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_amap_key', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_amap_key_2', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_style_day', 'default': 'mapbox://styles/rav4kumar/ckv7dtfba6oik15r0w8dh1c1q', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_nav_style_night', 'default': 'mapbox://styles/rav4kumar/ckvsf3f4u0zb414tcz9vof5jc', 'type': 'Text', 'conf_type': ['param']},
|
||||
|
||||
# gpxd
|
||||
{'name': 'dp_gpxd', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
|
||||
# assign car via param
|
||||
# echo -n TOYOTA C-HR 2021 > /data/params/d/dp_car_assigned
|
||||
{'name': 'dp_car_assigned', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
{'name': 'dp_car_list', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
|
||||
{'name': 'dp_last_modified', 'default': str(floor(time.time())), 'type': 'Text', 'conf_type': ['param']},
|
||||
|
||||
# lateral - alc
|
||||
{'name': 'dp_lateral_mode', 'default': 1, 'type': 'UInt8', 'min': 0, 'max': 2, 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_signal_off_delay', 'default': 3., 'type': 'Float32', 'min': 0., 'max': 10., 'depends': [{'name': 'dp_lateral_mode', 'vals': [0]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lc_min_mph', 'default': 15, 'type': 'UInt8', 'min': 0, 'max': 255, 'depends': [{'name': 'dp_lateral_mode', 'vals': [1, 2]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lc_auto_min_mph', 'default': 40, 'type': 'UInt8', 'min': 0, 'max': 255, 'depends': [{'name': 'dp_lateral_mode', 'vals': [2]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lc_auto_delay', 'default': 3., 'type': 'Float32', 'min': 0., 'max': 10., 'depends': [{'name': 'dp_lateral_mode', 'vals': [2]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lateral_lanelines', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lateral_camera_offset', 'default': 4 if TICI else -6, 'type': 'Int8', 'min': -100, 'max': 100, 'depends': [{'name': 'dp_lateral_lanelines', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lateral_path_offset', 'default': 4 if TICI else 0, 'type': 'Int8', 'min': -100, 'max': 100, 'depends': [{'name': 'dp_lateral_lanelines', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
|
||||
#ui
|
||||
{'name': 'dp_ip_addr', 'default': '', 'type': 'Text', 'conf_type': ['struct']},
|
||||
{'name': 'dp_quiet_drive', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_ui_top', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_side', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_volume', 'default': -5, 'type': 'Int8', 'min': -5, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_brightness', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_display_mode', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 1, 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_speed', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_event', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_face', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_lead_info', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_laneline', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_chevron', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_ui_dm_cam', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
|
||||
#toyota
|
||||
{'name': 'dp_toyota_sng', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_accel_profile_ctrl', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_accel_profile', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 2, 'depends': [{'name': 'dp_accel_profile_ctrl', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_ap_btn_link', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_toyota_cruise_override', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_cruise_override_speed', 'default': 30, 'type': 'UInt8', 'min': 5, 'max': 60, 'depends': [{'name': 'dp_accel_profile_ctrl', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_auto_lock', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_auto_unlock', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_fp_btn_link', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
|
||||
{'name': 'dp_mapd', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_local_db', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
|
||||
|
||||
# dashcam related
|
||||
{'name': 'dp_dashcamd', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# # auto shutdown
|
||||
{'name': 'dp_auto_shutdown', 'default': True, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_auto_shutdown_in', 'default': 60, 'type': 'UInt16', 'min': 0, 'max': 600, 'depends': [{'name': 'dp_auto_shutdown', 'vals': [True]}], 'conf_type': ['param']},
|
||||
|
||||
{'name': 'dp_mazda_steer_alert', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_mazda_dashcam_bypass', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# # service
|
||||
# {'name': 'dp_updated', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_logger', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_athenad', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_atl', 'vals': [False]}], 'conf_type': ['param', 'struct'], 'update_once': True},
|
||||
# {'name': 'dp_uploader', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_atl', 'vals': [False]}], 'conf_type': ['param', 'struct'], 'update_once': True},
|
||||
# # {'name': 'dp_gpxd', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_hotspot_on_boot', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_honda_eps_mod', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_dm', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_speed_check', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_temp_check', 'default': True, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_vag_resume_fix', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# # lat ctrl
|
||||
# {'name': 'dp_lane_less_mode_ctrl', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_lane_less_mode', 'default': 2, 'type': 'UInt8', 'min': 0, 'max': 2, 'depends': [{'name': 'dp_lane_less_mode_ctrl', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
# # long ctrl
|
||||
# {'name': 'dp_allow_gas', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_atl', 'vals': [False]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_following_profile_ctrl', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_following_profile', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 2, 'depends': [{'name': 'dp_following_profile_ctrl', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_lateral_tune', 'default': 0, 'type': 'UInt8', 'conf_type': ['param']},
|
||||
{'name': 'dp_lateral_torque_live_tune', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_accel_profile_ctrl', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_accel_profile', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 2, 'depends': [{'name': 'dp_accel_profile_ctrl', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
# # safety
|
||||
# {'name': 'dp_gear_check', 'default': True, 'type': 'Bool', 'depends': [{'name': 'dp_atl', 'vals': [False]}], 'conf_type': ['param', 'struct']},
|
||||
# # UIs
|
||||
# {'name': 'dp_ui_display_mode', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 1, 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_speed', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_event', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_max_speed', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_face', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_lane', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_lead', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_side', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_top', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_blinker', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_brightness', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_ui_volume', 'default': -5, 'type': 'Int8', 'min': -5, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
# # toyota
|
||||
{'name': 'dp_toyota_rav4_tss2_tune', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_toyota_prius_bad_angle_tune', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_e2e_conditional', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_e2e_conditional_at_speed', 'default': 60, 'type': 'UInt8', 'min': 30, 'max': 80, 'depends': [{'name': 'dp_e2e_conditional', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_e2e_conditional_at_speed_lead', 'default': 40, 'type': 'UInt8', 'min': 0, 'max': 80, 'depends': [{'name': 'dp_e2e_conditional', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_e2e_conditional_adapt_fp', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_e2e_conditional', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_e2e_conditional_adapt_ap', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_e2e_conditional', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_e2e_conditional_voacc', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_e2e_conditional', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_toyota_no_min_acc_limit', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_toyota_ldw', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
{'name': 'dp_toyota_zss', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_toyota_fp_btn_link', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_toyota_ap_btn_link', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_toyota_disable_relay', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_toyota_cruise_override', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_toyota_cruise_override_vego', 'default': False, 'type': 'Bool', 'depends': [{'name': 'dp_toyota_cruise_override', 'vals': [True]}], 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_toyota_cruise_override_at', 'default': 44, 'type': 'Float32', 'depends': [{'name': 'dp_toyota_cruise_override', 'vals': [True]}], 'min': 0, 'max': 50., 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_toyota_cruise_override_speed', 'default': 32, 'type': 'Float32', 'depends': [{'name': 'dp_toyota_cruise_override', 'vals': [True]}], 'min': 0, 'max': 50., 'conf_type': ['param', 'struct']},
|
||||
# # hyundai
|
||||
# {'name': 'dp_hkg_smart_mdps', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# # honda
|
||||
# {'name': 'dp_honda_kmh_display', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# # volkswagen
|
||||
# # {'name': 'dp_vw_panda', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# #misc
|
||||
# {'name': 'dp_ip_addr', 'default': '', 'type': 'Text', 'conf_type': ['struct']},
|
||||
# {'name': 'dp_fan_mode', 'default': 0, 'type': 'UInt8', 'min': 0, 'max': 2, 'conf_type': ['param']},
|
||||
# {'name': 'dp_last_modified', 'default': str(floor(time.time())), 'type': 'Text', 'conf_type': ['param']},
|
||||
# {'name': 'dp_camera_offset', 'default': CAMERA_OFFSET, 'type': 'Int8', 'min': -100, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_path_offset', 'default': 0 if EON else -4 if TICI else 0, 'type': 'Int8', 'min': -100, 'max': 100, 'conf_type': ['param', 'struct']},
|
||||
#
|
||||
# {'name': 'dp_reg', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# # sr learner related
|
||||
# {'name': 'dp_sr_learner', 'default': True, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_sr_custom', 'default': 9.99, 'min': 9.99, 'max': 30., 'type': 'Float32', 'depends': [{'name': 'dp_sr_learner', 'vals': [False]}], 'conf_type': ['param', 'struct']},
|
||||
# {'name': 'dp_sr_stock', 'default': 9.99, 'min': 9.99, 'max': 100., 'type': 'Float32', 'conf_type': ['param']},
|
||||
#
|
||||
# {'name': 'dp_lqr', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_reset_live_param_on_start', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
#
|
||||
# {'name': 'dp_car_assigned', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
# {'name': 'dp_no_batt', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_last_candidate', 'default': '', 'type': 'Text', 'conf_type': ['param']},
|
||||
# {'name': 'dp_prebuilt', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_gpxd', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_mapd', 'default': False, 'type': 'Bool', 'conf_type': ['param', 'struct']},
|
||||
# # no gps is for mr. one's harness + black panda in one solution (without GPS chip)
|
||||
# {'name': 'dp_panda_no_gps', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
#
|
||||
# {'name': 'dp_no_offroad_fix', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
# {'name': 'dp_ftpd', 'default': False, 'type': 'Bool', 'conf_type': ['param']},
|
||||
{'name': 'dp_lateral_version', 'default': 0, 'min': 0, 'max': 2, 'type': 'UInt8', 'conf_type': ['param']},
|
||||
{'name': 'dp_lateral_steer_rate_cost', 'default': 1.0, 'min': 0.5, 'max': 2.0, 'type': 'Float32', 'conf_type': ['param']},
|
||||
]
|
||||
|
||||
def get_definition(name):
|
||||
for conf in confs:
|
||||
if conf['name'] == name:
|
||||
return conf
|
||||
return None
|
||||
|
||||
def to_param_val(name, val):
|
||||
conf = get_definition(name)
|
||||
if conf is not None:
|
||||
type = conf['type'].lower()
|
||||
try:
|
||||
if 'bool' in type:
|
||||
val = '1' if val else '0'
|
||||
elif 'int' in type:
|
||||
val = int(val)
|
||||
elif 'float' in type:
|
||||
val = float(val)
|
||||
return str(val)
|
||||
except (ValueError, TypeError):
|
||||
return ''
|
||||
return ''
|
||||
|
||||
def to_struct_val(name, val):
|
||||
conf = get_definition(name)
|
||||
if conf is not None:
|
||||
try:
|
||||
type = conf['type'].lower()
|
||||
if 'bool' in type:
|
||||
val = True if val == '1' else False
|
||||
elif 'int' in type:
|
||||
val = int(val)
|
||||
elif 'float' in type:
|
||||
val = float(val)
|
||||
return val
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
'''
|
||||
function to convert param name into struct name.
|
||||
'''
|
||||
def get_struct_name(snake_str):
|
||||
components = snake_str.split('_')
|
||||
# We capitalize the first letter of each component except the first one
|
||||
# with the 'title' method and join them together.
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
'''
|
||||
function to generate struct for log.capnp
|
||||
'''
|
||||
def gen_log_struct():
|
||||
count = 0
|
||||
str = "# dp\n"
|
||||
str += "struct DragonConf {\n"
|
||||
for conf in confs:
|
||||
name = get_struct_name(conf['name'])
|
||||
if 'struct' in conf['conf_type']:
|
||||
str += f" {name} @{count} :{conf['type']};\n"
|
||||
count += 1
|
||||
str += "}"
|
||||
print(str)
|
||||
|
||||
'''
|
||||
function to generate support car list
|
||||
'''
|
||||
def get_support_car_list():
|
||||
attrs = ['FINGERPRINTS', 'FW_VERSIONS']
|
||||
cars = dict({"cars": []})
|
||||
models = []
|
||||
for car_folder in [x[0] for x in os.walk('/data/openpilot/selfdrive/car')]:
|
||||
try:
|
||||
car_name = car_folder.split('/')[-1]
|
||||
if car_name != "mock":
|
||||
for attr in attrs:
|
||||
values = __import__('selfdrive.car.%s.values' % car_name, fromlist=[attr])
|
||||
if hasattr(values, attr):
|
||||
attr_values = getattr(values, attr)
|
||||
else:
|
||||
continue
|
||||
if isinstance(attr_values, dict):
|
||||
for f, v in attr_values.items():
|
||||
if f not in models:
|
||||
models.append(f)
|
||||
except (ImportError, IOError, ValueError):
|
||||
pass
|
||||
models.sort()
|
||||
cars["cars"] = models
|
||||
return json.dumps(cars)
|
||||
|
||||
'''
|
||||
function to init param value.
|
||||
should add this into manager.py
|
||||
'''
|
||||
def init_params_vals(params):
|
||||
for conf in confs:
|
||||
if 'param' in conf['conf_type']:
|
||||
if conf['name'] == 'dp_car_list':
|
||||
params.put(conf['name'], get_support_car_list())
|
||||
elif params.get(conf['name']) is None:
|
||||
params.put(conf['name'], to_param_val(conf['name'], conf['default']))
|
||||
|
||||
def gen_params_cc_keys():
|
||||
for conf in confs:
|
||||
if 'param' in conf['conf_type']:
|
||||
print(" {\"%s\", PERSISTENT}," % conf['name'])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if (len(sys.argv) > 1) and sys.argv[1] == 'cc':
|
||||
gen_params_cc_keys()
|
||||
else:
|
||||
gen_log_struct()
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python3.7
|
||||
# import subprocess
|
||||
# from cereal import car
|
||||
from common.params import Params
|
||||
from common.realtime import sec_since_boot
|
||||
import os
|
||||
params = Params()
|
||||
LAST_MODIFIED = params.get_param_path() + "/dp_last_modified"
|
||||
|
||||
# delay of reading last modified
|
||||
# LAST_MODIFIED_TIMER_THERMALD = 10.
|
||||
LAST_MODIFIED_TIMER_SYSTEMD = 1.
|
||||
# LAST_MODIFIED_TIMER_LANE_PLANNER = 3.
|
||||
# LAST_MODIFIED_TIMER_UPLOADER = 10.
|
||||
|
||||
# def is_online():
|
||||
# try:
|
||||
# return not subprocess.call(["ping", "-W", "4", "-c", "1", "117.28.245.92"])
|
||||
# except ProcessLookupError:
|
||||
# return False
|
||||
#
|
||||
# def common_controller_ctrl(enabled, dragonconf, blinker_on, steer_req, v_ego):
|
||||
# if enabled:
|
||||
# if dragonconf.dpLateralMode == 0 and blinker_on:
|
||||
# steer_req = 0 if isinstance(steer_req, int) else False
|
||||
# return steer_req
|
||||
#
|
||||
def get_last_modified(delay, old_check, old_modified):
|
||||
new_check = sec_since_boot()
|
||||
if os.path.isfile(LAST_MODIFIED) and (old_check is None or new_check - old_check >= delay):
|
||||
return new_check, os.stat(LAST_MODIFIED).st_mtime
|
||||
else:
|
||||
return old_check, old_modified
|
||||
|
||||
# def param_get_if_updated(param, type, old_val, old_modified):
|
||||
# try:
|
||||
# modified = os.stat(PARAM_PATH + param).st_mtime
|
||||
# except OSError:
|
||||
# return old_val, old_modified
|
||||
# if old_modified != modified:
|
||||
# new_val = param_get(param, type, old_val)
|
||||
# new_modified = modified
|
||||
# else:
|
||||
# new_val = old_val
|
||||
# new_modified = old_modified
|
||||
# return new_val, new_modified
|
||||
|
||||
# def param_get(param_name, type, default):
|
||||
# try:
|
||||
# val = params.get(param_name, encoding='utf8').rstrip('\x00')
|
||||
# if type == 'bool':
|
||||
# val = val == '1'
|
||||
# elif type == 'int':
|
||||
# val = int(val)
|
||||
# elif type == 'float':
|
||||
# val = float(val)
|
||||
# except (TypeError, ValueError):
|
||||
# val = default
|
||||
# return val
|
||||
@@ -1,55 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import fcntl
|
||||
import hashlib
|
||||
import platform
|
||||
from cffi import FFI
|
||||
|
||||
def suffix():
|
||||
if platform.system() == "Darwin":
|
||||
return ".dylib"
|
||||
else:
|
||||
return ".so"
|
||||
|
||||
def ffi_wrap(name, c_code, c_header, tmpdir="/tmp/ccache", cflags="", libraries=None):
|
||||
if libraries is None:
|
||||
libraries = []
|
||||
|
||||
cache = name + "_" + hashlib.sha1(c_code.encode('utf-8')).hexdigest()
|
||||
try:
|
||||
os.mkdir(tmpdir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
fd = os.open(tmpdir, 0)
|
||||
fcntl.flock(fd, fcntl.LOCK_EX)
|
||||
try:
|
||||
sys.path.append(tmpdir)
|
||||
try:
|
||||
mod = __import__(cache)
|
||||
except Exception:
|
||||
print(f"cache miss {cache}")
|
||||
compile_code(cache, c_code, c_header, tmpdir, cflags, libraries)
|
||||
mod = __import__(cache)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
return mod.ffi, mod.lib
|
||||
|
||||
|
||||
def compile_code(name, c_code, c_header, directory, cflags="", libraries=None):
|
||||
if libraries is None:
|
||||
libraries = []
|
||||
|
||||
ffibuilder = FFI()
|
||||
ffibuilder.set_source(name, c_code, source_extension='.cpp', libraries=libraries)
|
||||
ffibuilder.cdef(c_header)
|
||||
os.environ['OPT'] = "-fwrapv -O2 -DNDEBUG -std=c++1z"
|
||||
os.environ['CFLAGS'] = cflags
|
||||
ffibuilder.compile(verbose=True, debug=False, tmpdir=directory)
|
||||
|
||||
|
||||
def wrap_compiled(name, directory):
|
||||
sys.path.append(directory)
|
||||
mod = __import__(name)
|
||||
return mod.ffi, mod.lib
|
||||
@@ -1,113 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from atomicwrites import AtomicWriter
|
||||
|
||||
|
||||
def mkdirs_exists_ok(path):
|
||||
if path.startswith('http://') or path.startswith('https://'):
|
||||
raise ValueError('URL path')
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError:
|
||||
if not os.path.isdir(path):
|
||||
raise
|
||||
|
||||
|
||||
def rm_not_exists_ok(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
if os.path.exists(path):
|
||||
raise
|
||||
|
||||
|
||||
def rm_tree_or_link(path):
|
||||
if os.path.islink(path):
|
||||
os.unlink(path)
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def get_tmpdir_on_same_filesystem(path):
|
||||
normpath = os.path.normpath(path)
|
||||
parts = normpath.split("/")
|
||||
if len(parts) > 1 and parts[1] == "scratch":
|
||||
return "/scratch/tmp"
|
||||
elif len(parts) > 2 and parts[2] == "runner":
|
||||
return f"/{parts[1]}/runner/tmp"
|
||||
return "/tmp"
|
||||
|
||||
|
||||
class NamedTemporaryDir():
|
||||
def __init__(self, temp_dir=None):
|
||||
self._path = tempfile.mkdtemp(dir=temp_dir)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._path
|
||||
|
||||
def close(self):
|
||||
shutil.rmtree(self._path)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
|
||||
class CallbackReader:
|
||||
"""Wraps a file, but overrides the read method to also
|
||||
call a callback function with the number of bytes read so far."""
|
||||
def __init__(self, f, callback, *args):
|
||||
self.f = f
|
||||
self.callback = callback
|
||||
self.cb_args = args
|
||||
self.total_read = 0
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.f, attr)
|
||||
|
||||
def read(self, *args, **kwargs):
|
||||
chunk = self.f.read(*args, **kwargs)
|
||||
self.total_read += len(chunk)
|
||||
self.callback(*self.cb_args, self.total_read)
|
||||
return chunk
|
||||
|
||||
|
||||
def _get_fileobject_func(writer, temp_dir):
|
||||
def _get_fileobject():
|
||||
return writer.get_fileobject(dir=temp_dir)
|
||||
return _get_fileobject
|
||||
|
||||
def monkeypatch_os_link():
|
||||
# This is neccesary on EON/C2, where os.link is patched out of python
|
||||
if not hasattr(os, 'link'):
|
||||
from cffi import FFI
|
||||
ffi = FFI()
|
||||
ffi.cdef("int link(const char *oldpath, const char *newpath);")
|
||||
libc = ffi.dlopen(None)
|
||||
|
||||
def link(src, dest):
|
||||
return libc.link(src.encode(), dest.encode())
|
||||
os.link = link
|
||||
|
||||
def atomic_write_on_fs_tmp(path, **kwargs):
|
||||
"""Creates an atomic writer using a temporary file in a temporary directory
|
||||
on the same filesystem as path.
|
||||
"""
|
||||
# TODO(mgraczyk): This use of AtomicWriter relies on implementation details to set the temp
|
||||
# directory.
|
||||
monkeypatch_os_link()
|
||||
writer = AtomicWriter(path, **kwargs)
|
||||
return writer._open(_get_fileobject_func(writer, get_tmpdir_on_same_filesystem(path)))
|
||||
|
||||
|
||||
def atomic_write_in_dir(path, **kwargs):
|
||||
"""Creates an atomic writer using a temporary file in the same directory
|
||||
as the destination file.
|
||||
"""
|
||||
monkeypatch_os_link()
|
||||
writer = AtomicWriter(path, **kwargs)
|
||||
return writer._open(_get_fileobject_func(writer, os.path.dirname(path)))
|
||||
+17
-1
@@ -1,5 +1,4 @@
|
||||
class FirstOrderFilter:
|
||||
# first order filter
|
||||
def __init__(self, x0, rc, dt, initialized=True):
|
||||
self.x = x0
|
||||
self.dt = dt
|
||||
@@ -16,3 +15,20 @@ class FirstOrderFilter:
|
||||
self.initialized = True
|
||||
self.x = x
|
||||
return self.x
|
||||
|
||||
|
||||
class BounceFilter(FirstOrderFilter):
|
||||
def __init__(self, x0, rc, dt, initialized=True, bounce=2):
|
||||
self.velocity = FirstOrderFilter(0.0, 0.15, dt)
|
||||
self.bounce = bounce
|
||||
super().__init__(x0, rc, dt, initialized)
|
||||
|
||||
def update(self, x):
|
||||
super().update(x)
|
||||
scale = self.dt / (1.0 / 60.0) # tuned at 60 fps
|
||||
self.velocity.x += (x - self.x) * self.bounce * scale * self.dt
|
||||
self.velocity.update(0.0)
|
||||
if abs(self.velocity.x) < 1e-5:
|
||||
self.velocity.x = 0.0
|
||||
self.x += self.velocity.x
|
||||
return self.x
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from functools import cache
|
||||
import subprocess
|
||||
from openpilot.common.utils import run_cmd, run_cmd_default
|
||||
|
||||
|
||||
@cache
|
||||
def get_commit(cwd: str = None, branch: str = "HEAD") -> str:
|
||||
return run_cmd_default(["git", "rev-parse", branch], cwd=cwd)
|
||||
|
||||
|
||||
@cache
|
||||
def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str:
|
||||
return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd)
|
||||
|
||||
|
||||
@cache
|
||||
def get_short_branch(cwd: str = None) -> str:
|
||||
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
|
||||
|
||||
|
||||
@cache
|
||||
def get_branch(cwd: str = None) -> str:
|
||||
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd)
|
||||
|
||||
|
||||
@cache
|
||||
def get_origin(cwd: str = None) -> str:
|
||||
try:
|
||||
local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd)
|
||||
tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd)
|
||||
return run_cmd(["git", "config", "remote." + tracking_remote + ".url"], cwd=cwd)
|
||||
except subprocess.CalledProcessError: # Not on a branch, fallback
|
||||
return run_cmd_default(["git", "config", "--get", "remote.origin.url"], cwd=cwd)
|
||||
|
||||
|
||||
@cache
|
||||
def get_normalized_origin(cwd: str = None) -> str:
|
||||
return get_origin(cwd) \
|
||||
.replace("git@", "", 1) \
|
||||
.replace(".git", "", 1) \
|
||||
.replace("https://", "", 1) \
|
||||
.replace(":", "/", 1)
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Pin definitions
|
||||
#ifdef QCOM2
|
||||
#define GPIO_HUB_RST_N 30
|
||||
#define GPIO_UBLOX_RST_N 32
|
||||
#define GPIO_UBLOX_SAFEBOOT_N 33
|
||||
#define GPIO_UBLOX_PWR_EN 34
|
||||
#define GPIO_STM_RST_N 124
|
||||
#define GPIO_STM_BOOT0 134
|
||||
#define GPIO_BMX_ACCEL_INT 21
|
||||
#define GPIO_BMX_GYRO_INT 23
|
||||
#define GPIO_BMX_MAGN_INT 87
|
||||
#define GPIO_LSM_INT 84
|
||||
#define GPIOCHIP_INT 0
|
||||
#else
|
||||
#define GPIO_HUB_RST_N 0
|
||||
#define GPIO_UBLOX_RST_N 0
|
||||
#define GPIO_UBLOX_SAFEBOOT_N 0
|
||||
#define GPIO_UBLOX_PWR_EN 0
|
||||
#define GPIO_STM_RST_N 0
|
||||
#define GPIO_STM_BOOT0 0
|
||||
#define GPIO_BMX_ACCEL_INT 0
|
||||
#define GPIO_BMX_GYRO_INT 0
|
||||
#define GPIO_BMX_MAGN_INT 0
|
||||
#define GPIO_LSM_INT 0
|
||||
#define GPIOCHIP_INT 0
|
||||
#endif
|
||||
|
||||
int gpio_init(int pin_nr, bool output);
|
||||
int gpio_set(int pin_nr, bool high);
|
||||
|
||||
int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr);
|
||||
+66
-2
@@ -1,4 +1,7 @@
|
||||
from typing import Optional
|
||||
import os
|
||||
import fcntl
|
||||
import ctypes
|
||||
from functools import cache
|
||||
|
||||
def gpio_init(pin: int, output: bool) -> None:
|
||||
try:
|
||||
@@ -14,7 +17,7 @@ def gpio_set(pin: int, high: bool) -> None:
|
||||
except Exception as e:
|
||||
print(f"Failed to set gpio {pin} value: {e}")
|
||||
|
||||
def gpio_read(pin: int) -> Optional[bool]:
|
||||
def gpio_read(pin: int) -> bool | None:
|
||||
val = None
|
||||
try:
|
||||
with open(f"/sys/class/gpio/gpio{pin}/value", 'rb') as f:
|
||||
@@ -23,3 +26,64 @@ def gpio_read(pin: int) -> Optional[bool]:
|
||||
print(f"Failed to set gpio {pin} value: {e}")
|
||||
|
||||
return val
|
||||
|
||||
def gpio_export(pin: int) -> None:
|
||||
if os.path.isdir(f"/sys/class/gpio/gpio{pin}"):
|
||||
return
|
||||
|
||||
try:
|
||||
with open("/sys/class/gpio/export", 'w') as f:
|
||||
f.write(str(pin))
|
||||
except Exception:
|
||||
print(f"Failed to export gpio {pin}")
|
||||
|
||||
@cache
|
||||
def get_irq_action(irq: int) -> list[str]:
|
||||
try:
|
||||
with open(f"/sys/kernel/irq/{irq}/actions") as f:
|
||||
actions = f.read().strip().split(',')
|
||||
return actions
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
|
||||
def get_irqs_for_action(action: str) -> list[str]:
|
||||
ret = []
|
||||
with open("/proc/interrupts") as f:
|
||||
for l in f.readlines():
|
||||
irq = l.split(':')[0].strip()
|
||||
if irq.isdigit() and action in get_irq_action(irq):
|
||||
ret.append(irq)
|
||||
return ret
|
||||
|
||||
# *** gpiochip ***
|
||||
|
||||
class gpioevent_data(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("timestamp", ctypes.c_uint64),
|
||||
("id", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class gpioevent_request(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("lineoffset", ctypes.c_uint32),
|
||||
("handleflags", ctypes.c_uint32),
|
||||
("eventflags", ctypes.c_uint32),
|
||||
("label", ctypes.c_char * 32),
|
||||
("fd", ctypes.c_int)
|
||||
]
|
||||
|
||||
def gpiochip_get_ro_value_fd(label: str, gpiochip_id: int, pin: int) -> int:
|
||||
GPIOEVENT_REQUEST_BOTH_EDGES = 0x3
|
||||
GPIOHANDLE_REQUEST_INPUT = 0x1
|
||||
GPIO_GET_LINEEVENT_IOCTL = 0xc030b404
|
||||
|
||||
rq = gpioevent_request()
|
||||
rq.lineoffset = pin
|
||||
rq.handleflags = GPIOHANDLE_REQUEST_INPUT
|
||||
rq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES
|
||||
rq.label = label.encode('utf-8')[:31] + b'\0'
|
||||
|
||||
fd = os.open(f"/dev/gpiochip{gpiochip_id}", os.O_RDONLY)
|
||||
fcntl.ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, rq)
|
||||
os.close(fd)
|
||||
return int(rq.fd)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
def get_gps_location_service(params: Params) -> str:
|
||||
if params.get_bool("UbloxAvailable"):
|
||||
return "gpsLocationExternal"
|
||||
else:
|
||||
return "gpsLocation"
|
||||
@@ -1,27 +0,0 @@
|
||||
import gettext
|
||||
from common.params import Params
|
||||
|
||||
locale_dir = "/data/openpilot/selfdrive/assets/locales"
|
||||
# supported_language = ["en-US", "zh-TW", "zh-CN", "ja-JP", "ko-KR"]
|
||||
supported_languages = {
|
||||
"main_en": "en-US",
|
||||
"main_zh-CHT": "zh-TW",
|
||||
"main_zh-CHS": "zh-CN",
|
||||
"main_ko": "ko-KR",
|
||||
"main_ja": "ja-JP",
|
||||
"main_de": "de-DE",
|
||||
"main_pt-BR": "pt_BR",
|
||||
}
|
||||
|
||||
def events():
|
||||
locale = Params().get("LanguageSetting", encoding='utf8')
|
||||
try:
|
||||
if locale is not None:
|
||||
locale = supported_languages[locale.strip()]
|
||||
else:
|
||||
locale = "en-US"
|
||||
except KeyError:
|
||||
locale = "en-US"
|
||||
i18n = gettext.translation("events", localedir=locale_dir, fallback=True, languages=[locale])
|
||||
i18n.install()
|
||||
return i18n.gettext
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
class I2CBus {
|
||||
private:
|
||||
int i2c_fd;
|
||||
|
||||
public:
|
||||
I2CBus(uint8_t bus_id);
|
||||
~I2CBus();
|
||||
|
||||
int read_register(uint8_t device_address, uint register_address, uint8_t *buffer, uint8_t len);
|
||||
int set_register(uint8_t device_address, uint register_address, uint8_t data);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
simple_kalman_impl.c
|
||||
@@ -1,3 +0,0 @@
|
||||
# pylint: skip-file
|
||||
from common.kalman.simple_kalman_impl import KF1D as KF1D
|
||||
assert KF1D
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
||||
# cython: language_level = 3
|
||||
|
||||
cdef class KF1D:
|
||||
cdef public:
|
||||
double x0_0
|
||||
double x1_0
|
||||
double K0_0
|
||||
double K1_0
|
||||
double A0_0
|
||||
double A0_1
|
||||
double A1_0
|
||||
double A1_1
|
||||
double C0_0
|
||||
double C0_1
|
||||
double A_K_0
|
||||
double A_K_1
|
||||
double A_K_2
|
||||
double A_K_3
|
||||
@@ -1,37 +0,0 @@
|
||||
# distutils: language = c++
|
||||
# cython: language_level=3
|
||||
|
||||
cdef class KF1D:
|
||||
def __init__(self, x0, A, C, K):
|
||||
self.x0_0 = x0[0][0]
|
||||
self.x1_0 = x0[1][0]
|
||||
self.A0_0 = A[0][0]
|
||||
self.A0_1 = A[0][1]
|
||||
self.A1_0 = A[1][0]
|
||||
self.A1_1 = A[1][1]
|
||||
self.C0_0 = C[0]
|
||||
self.C0_1 = C[1]
|
||||
self.K0_0 = K[0][0]
|
||||
self.K1_0 = K[1][0]
|
||||
|
||||
self.A_K_0 = self.A0_0 - self.K0_0 * self.C0_0
|
||||
self.A_K_1 = self.A0_1 - self.K0_0 * self.C0_1
|
||||
self.A_K_2 = self.A1_0 - self.K1_0 * self.C0_0
|
||||
self.A_K_3 = self.A1_1 - self.K1_0 * self.C0_1
|
||||
|
||||
def update(self, meas):
|
||||
cdef double x0_0 = self.A_K_0 * self.x0_0 + self.A_K_1 * self.x1_0 + self.K0_0 * meas
|
||||
cdef double x1_0 = self.A_K_2 * self.x0_0 + self.A_K_3 * self.x1_0 + self.K1_0 * meas
|
||||
self.x0_0 = x0_0
|
||||
self.x1_0 = x1_0
|
||||
|
||||
return [self.x0_0, self.x1_0]
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return [[self.x0_0], [self.x1_0]]
|
||||
|
||||
@x.setter
|
||||
def x(self, x):
|
||||
self.x0_0 = x[0][0]
|
||||
self.x1_0 = x[1][0]
|
||||
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
class KF1D:
|
||||
# this EKF assumes constant covariance matrix, so calculations are much simpler
|
||||
# the Kalman gain also needs to be precomputed using the control module
|
||||
|
||||
def __init__(self, x0, A, C, K):
|
||||
self.x = x0
|
||||
self.A = A
|
||||
self.C = np.atleast_2d(C)
|
||||
self.K = K
|
||||
|
||||
self.A_K = self.A - np.dot(self.K, self.C)
|
||||
|
||||
# K matrix needs to be pre-computed as follow:
|
||||
# import control
|
||||
# (x, l, K) = control.dare(np.transpose(self.A), np.transpose(self.C), Q, R)
|
||||
# self.K = np.transpose(K)
|
||||
|
||||
def update(self, meas):
|
||||
self.x = np.dot(self.A_K, self.x) + np.dot(self.K, meas)
|
||||
return self.x
|
||||
@@ -1,87 +0,0 @@
|
||||
import unittest
|
||||
import random
|
||||
import timeit
|
||||
import numpy as np
|
||||
|
||||
from common.kalman.simple_kalman import KF1D
|
||||
from common.kalman.simple_kalman_old import KF1D as KF1D_old
|
||||
|
||||
|
||||
class TestSimpleKalman(unittest.TestCase):
|
||||
def setUp(self):
|
||||
dt = 0.01
|
||||
x0_0 = 0.0
|
||||
x1_0 = 0.0
|
||||
A0_0 = 1.0
|
||||
A0_1 = dt
|
||||
A1_0 = 0.0
|
||||
A1_1 = 1.0
|
||||
C0_0 = 1.0
|
||||
C0_1 = 0.0
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
self.kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]),
|
||||
A=np.array([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.array([C0_0, C0_1]),
|
||||
K=np.array([[K0_0], [K1_0]]))
|
||||
|
||||
self.kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
C=[C0_0, C0_1],
|
||||
K=[[K0_0], [K1_0]])
|
||||
|
||||
def test_getter_setter(self):
|
||||
self.kf.x = [[1.0], [1.0]]
|
||||
self.assertEqual(self.kf.x, [[1.0], [1.0]])
|
||||
|
||||
def update_returns_state(self):
|
||||
x = self.kf.update(100)
|
||||
self.assertEqual(x, self.kf.x)
|
||||
|
||||
def test_old_equal_new(self):
|
||||
for _ in range(1000):
|
||||
v_wheel = random.uniform(0, 200)
|
||||
|
||||
x_old = self.kf_old.update(v_wheel)
|
||||
x = self.kf.update(v_wheel)
|
||||
|
||||
# Compare the output x, verify that the error is less than 1e-4
|
||||
np.testing.assert_almost_equal(x_old[0], x[0])
|
||||
np.testing.assert_almost_equal(x_old[1], x[1])
|
||||
|
||||
def test_new_is_faster(self):
|
||||
setup = """
|
||||
import numpy as np
|
||||
|
||||
from common.kalman.simple_kalman import KF1D
|
||||
from common.kalman.simple_kalman_old import KF1D as KF1D_old
|
||||
|
||||
dt = 0.01
|
||||
x0_0 = 0.0
|
||||
x1_0 = 0.0
|
||||
A0_0 = 1.0
|
||||
A0_1 = dt
|
||||
A1_0 = 0.0
|
||||
A1_1 = 1.0
|
||||
C0_0 = 1.0
|
||||
C0_1 = 0.0
|
||||
K0_0 = 0.12287673
|
||||
K1_0 = 0.29666309
|
||||
|
||||
kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]),
|
||||
A=np.array([[A0_0, A0_1], [A1_0, A1_1]]),
|
||||
C=np.array([C0_0, C0_1]),
|
||||
K=np.array([[K0_0], [K1_0]]))
|
||||
|
||||
kf = KF1D(x0=[[x0_0], [x1_0]],
|
||||
A=[[A0_0, A0_1], [A1_0, A1_1]],
|
||||
C=[C0_0, C0_1],
|
||||
K=[[K0_0], [K1_0]])
|
||||
"""
|
||||
kf_speed = timeit.timeit("kf.update(1234)", setup=setup, number=10000)
|
||||
kf_old_speed = timeit.timeit("kf_old.update(1234)", setup=setup, number=10000)
|
||||
self.assertTrue(kf_speed < kf_old_speed / 4)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -8,6 +8,7 @@ import uuid
|
||||
import socket
|
||||
import logging
|
||||
import traceback
|
||||
import numpy as np
|
||||
from threading import local
|
||||
from collections import OrderedDict
|
||||
from contextlib import contextmanager
|
||||
@@ -15,6 +16,8 @@ from contextlib import contextmanager
|
||||
LOG_TIMESTAMPS = "LOG_TIMESTAMPS" in os.environ
|
||||
|
||||
def json_handler(obj):
|
||||
if isinstance(obj, np.bool_):
|
||||
return bool(obj)
|
||||
# if isinstance(obj, (datetime.date, datetime.time)):
|
||||
# return obj.isoformat()
|
||||
return repr(obj)
|
||||
@@ -153,9 +156,9 @@ class SwagLogger(logging.Logger):
|
||||
def bind_global(self, **kwargs):
|
||||
self.global_ctx.update(kwargs)
|
||||
|
||||
def event(self, event_name, *args, **kwargs):
|
||||
def event(self, event, *args, **kwargs):
|
||||
evt = NiceOrderedDict()
|
||||
evt['event'] = event_name
|
||||
evt['event'] = event
|
||||
if args:
|
||||
evt['args'] = args
|
||||
evt.update(kwargs)
|
||||
@@ -196,8 +199,7 @@ class SwagLogger(logging.Logger):
|
||||
co = f.f_code
|
||||
filename = os.path.normcase(co.co_filename)
|
||||
|
||||
# TODO: is this pylint exception correct?
|
||||
if filename == _srcfile: # pylint: disable=comparison-with-callable
|
||||
if filename == _srcfile:
|
||||
f = f.f_back
|
||||
continue
|
||||
sinfo = None
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
HTML_REPLACEMENTS = [
|
||||
(r'&', r'&'),
|
||||
(r'"', r'"'),
|
||||
]
|
||||
|
||||
def parse_markdown(text: str, tab_length: int = 2) -> str:
|
||||
lines = text.split("\n")
|
||||
output: list[str] = []
|
||||
list_level = 0
|
||||
|
||||
def end_outstanding_lists(level: int, end_level: int) -> int:
|
||||
while level > end_level:
|
||||
level -= 1
|
||||
output.append("</ul>")
|
||||
if level > 0:
|
||||
output.append("</li>")
|
||||
return end_level
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if i + 1 < len(lines) and lines[i + 1].startswith("==="): # heading
|
||||
output.append(f"<h1>{line}</h1>")
|
||||
elif line.startswith("==="):
|
||||
pass
|
||||
elif line.lstrip().startswith("* "): # list
|
||||
line_level = 1 + line.count(" " * tab_length, 0, line.index("*"))
|
||||
if list_level >= line_level:
|
||||
list_level = end_outstanding_lists(list_level, line_level)
|
||||
else:
|
||||
list_level += 1
|
||||
if list_level > 1:
|
||||
output[-1] = output[-1].replace("</li>", "")
|
||||
output.append("<ul>")
|
||||
output.append(f"<li>{line.replace('*', '', 1).lstrip()}</li>")
|
||||
else:
|
||||
list_level = end_outstanding_lists(list_level, 0)
|
||||
if len(line) > 0:
|
||||
output.append(line)
|
||||
|
||||
end_outstanding_lists(list_level, 0)
|
||||
output_str = "\n".join(output) + "\n"
|
||||
|
||||
for (fr, to) in HTML_REPLACEMENTS:
|
||||
output_str = output_str.replace(fr, to)
|
||||
|
||||
return output_str
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct vec3 {
|
||||
float v[3];
|
||||
float v[3];
|
||||
} vec3;
|
||||
|
||||
typedef struct vec4 {
|
||||
@@ -9,7 +9,7 @@ typedef struct vec4 {
|
||||
} vec4;
|
||||
|
||||
typedef struct mat3 {
|
||||
float v[3*3];
|
||||
float v[3*3];
|
||||
} mat3;
|
||||
|
||||
typedef struct mat4 {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Utilities for generating mock messages for testing.
|
||||
example in common/tests/test_mock.py
|
||||
"""
|
||||
|
||||
|
||||
import functools
|
||||
import threading
|
||||
from cereal.messaging import PubMaster
|
||||
from cereal.services import SERVICE_LIST
|
||||
from openpilot.common.mock.generators import generate_livePose
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
|
||||
|
||||
MOCK_GENERATOR = {
|
||||
"livePose": generate_livePose
|
||||
}
|
||||
|
||||
|
||||
def generate_messages_loop(services: list[str], done: threading.Event):
|
||||
pm = PubMaster(services)
|
||||
rk = Ratekeeper(100)
|
||||
i = 0
|
||||
while not done.is_set():
|
||||
for s in services:
|
||||
should_send = i % (100/SERVICE_LIST[s].frequency) == 0
|
||||
if should_send:
|
||||
message = MOCK_GENERATOR[s]()
|
||||
pm.send(s, message)
|
||||
i += 1
|
||||
rk.keep_time()
|
||||
|
||||
|
||||
def mock_messages(services: list[str] | str):
|
||||
if isinstance(services, str):
|
||||
services = [services]
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
done = threading.Event()
|
||||
t = threading.Thread(target=generate_messages_loop, args=(services, done))
|
||||
t.start()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
done.set()
|
||||
t.join()
|
||||
return wrapper
|
||||
return decorator
|
||||
@@ -0,0 +1,14 @@
|
||||
from cereal import messaging
|
||||
|
||||
|
||||
def generate_livePose():
|
||||
msg = messaging.new_message('livePose')
|
||||
meas = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'xStd': 0.0, 'yStd': 0.0, 'zStd': 0.0, 'valid': True}
|
||||
msg.livePose.orientationNED = meas
|
||||
msg.livePose.velocityDevice = meas
|
||||
msg.livePose.angularVelocityDevice = meas
|
||||
msg.livePose.accelerationDevice = meas
|
||||
msg.livePose.inputsOK = True
|
||||
msg.livePose.posenetOK = True
|
||||
msg.livePose.sensorsOK = True
|
||||
return msg
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/mat.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
const int TRAJECTORY_SIZE = 33;
|
||||
const int LAT_MPC_N = 16;
|
||||
const int LON_MPC_N = 32;
|
||||
const float MIN_DRAW_DISTANCE = 10.0;
|
||||
const float MAX_DRAW_DISTANCE = 100.0;
|
||||
|
||||
template <typename T, size_t size>
|
||||
constexpr std::array<T, size> build_idxs(float max_val) {
|
||||
std::array<T, size> result{};
|
||||
for (int i = 0; i < size; ++i) {
|
||||
result[i] = max_val * ((i / (double)(size - 1)) * (i / (double)(size - 1)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr auto T_IDXS = build_idxs<double, TRAJECTORY_SIZE>(10.0);
|
||||
constexpr auto T_IDXS_FLOAT = build_idxs<float, TRAJECTORY_SIZE>(10.0);
|
||||
constexpr auto X_IDXS = build_idxs<double, TRAJECTORY_SIZE>(192.0);
|
||||
constexpr auto X_IDXS_FLOAT = build_idxs<float, TRAJECTORY_SIZE>(192.0);
|
||||
|
||||
const int TICI_CAM_WIDTH = 1928;
|
||||
|
||||
namespace tici_dm_crop {
|
||||
const int x_offset = -72;
|
||||
const int y_offset = -144;
|
||||
const int width = 954;
|
||||
};
|
||||
|
||||
const mat3 fcam_intrinsic_matrix =
|
||||
Hardware::EON() ? (mat3){{910., 0., 1164.0 / 2,
|
||||
0., 910., 874.0 / 2,
|
||||
0., 0., 1.}}
|
||||
: (mat3){{2648.0, 0.0, 1928.0 / 2,
|
||||
0.0, 2648.0, 1208.0 / 2,
|
||||
0.0, 0.0, 1.0}};
|
||||
|
||||
// tici ecam focal probably wrong? magnification is not consistent across frame
|
||||
// Need to retrain model before this can be changed
|
||||
const mat3 ecam_intrinsic_matrix = (mat3){{567.0, 0.0, 1928.0 / 2,
|
||||
0.0, 567.0, 1208.0 / 2,
|
||||
0.0, 0.0, 1.0}};
|
||||
|
||||
static inline mat3 get_model_yuv_transform(bool bayer = true) {
|
||||
float db_s = Hardware::EON() ? 0.5 : 1.0; // debayering does a 2x downscale on EON
|
||||
const mat3 transform = (mat3){{
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0
|
||||
}};
|
||||
return bayer ? transform_scale_buffer(transform, db_s) : transform;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user