feat: Squash all core features into min
This commit is contained in:
parent
c5d5c5d1f3
commit
0cfd6de2b3
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -4,5 +4,8 @@
|
||||
"ms-vscode.cpptools",
|
||||
"elagil.pre-commit-helper",
|
||||
"charliermarsh.ruff",
|
||||
"JamiTech.simply-blame",
|
||||
"k--kato.intellij-idea-keybindings",
|
||||
"trinm1709.dracula-theme-from-intellij"
|
||||
]
|
||||
}
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@ -3,10 +3,25 @@
|
||||
"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
|
||||
"**/__pycache__": true,
|
||||
"msgq_repo/": true,
|
||||
"opendbc/": true,
|
||||
"rednose/": true,
|
||||
"rednose_repo/": true,
|
||||
"openpilot/": true,
|
||||
"teleoprtc_repo/": true,
|
||||
"tinygrad/": true,
|
||||
"tinygrad_repo/": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
|
||||
30
LICENSE.md
Normal file
30
LICENSE.md
Normal file
@ -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.
|
||||
127
README.md
127
README.md
@ -1,107 +1,74 @@
|
||||
<div align="center" style="text-align: center;">
|
||||

|
||||
|
||||
<h1>openpilot</h1>
|
||||
[Read this in English](README_EN.md)
|
||||
|
||||
<p>
|
||||
<b>openpilot is an operating system for robotics.</b>
|
||||
<br>
|
||||
Currently, it upgrades the driver assistance system in 300+ supported cars.
|
||||
</p>
|
||||
# **🐲 dragonpilot - 賦予您的愛車「龍」之魂**
|
||||
|
||||
<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/selfdrive_tests.yaml)
|
||||
[](LICENSE)
|
||||
[](https://x.com/comma_ai)
|
||||
[](https://discord.comma.ai)
|
||||
`dragonpilot` 誕生於 2019 年,由三位早期的 openpilot 華人玩家共同創立。初衷很簡單:為廣大的華人用戶、玩家們提供一個友善的交流環境、更簡便的設定協助,並加入更多適合在地使用的貼心功能。
|
||||
|
||||
</div>
|
||||
我們深知在地化的重要性,特別是語言的親切感。因此,我們率先導入了完整的中文介面,讓 `dragonpilot` 迅速在華語地區累積了口碑,也讓華人的使用者數量在全球名列前茅。這份來自在地的支持,是我們持續前進的最大動力。
|
||||
|
||||
<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>
|
||||
我們以功能強大的 [openpilot](https://github.com/commaai/openpilot) 為基礎——這套據美國消費者報告評測優於市售車方案的開源輔助駕駛系統——融入了更多在地化的巧思與客製化的溫度,希望能打造出最符合您需求的駕駛夥伴。(您也可以參考我們 repo 中保留的 [openpilot 原始說明檔案](README_OPENPILOT.md))
|
||||
|
||||
取名 `dragonpilot`,是因為我們希望它能像神話中的「龍」一樣,既強大又充滿智慧,為您的行車安全保駕護航。龍,在我們華人文化中,更是吉祥與力量的象徵,也代表著我們的根源與驕傲。
|
||||
|
||||
Using openpilot in a car
|
||||
------
|
||||
## **✨ dragonpilot 的里程碑**
|
||||
|
||||
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.
|
||||
我們不僅保留了 openpilot 的核心優勢,更達成了許多從社群回饋中誕生的里程碑,這些是我們引以為傲的足跡:
|
||||
|
||||
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.
|
||||
* **🚘 全時置中車道維持 (ALKA)**
|
||||
|
||||
### Branches
|
||||
| branch | URL | description |
|
||||
|------------------|----------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| `release3` | openpilot.comma.ai | This is openpilot's release branch. |
|
||||
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
|
||||
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
|
||||
| `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. |
|
||||
這不只是一個功能,更是 `dragonpilot` 的哲學。我們最早於 [0.6.2 版本](https://github.com/dragonpilot-community/dragonpilot/blob/2861467183d62151024320447ba04d18fc3fe1e6/selfdrive/car/toyota/carstate.py#L199) 時便實現了這個功能,其開發歷程始於 2017 Lexus IS300h,接著擴展至 Toyota 全車系,並逐步延伸到其他支援的品牌。它能溫柔地輔助您,讓車輛始終穩定地保持在車道中央,提供一份額外的安心與從容。
|
||||
|
||||
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).
|
||||
在官方 openpilot 還未支援前,我們便已將多國語言介面實現。`dragonpilot` 完整支援繁體中文、簡體中文與英文,讓操作毫無隔閡。
|
||||
|
||||
* 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.
|
||||
我們是唯一曾致力於讓專案同時兼容 EON、comma two、comma 3 與 Jetson 平台的社群分支,這份努力是為了服務最廣大的玩家社群。
|
||||
此外,在 comma.ai 團隊於 0.10.0 版本宣布停止支持 comma 3 後,我們仍是唯一一個完整同時支援 comma 3、comma 3X 以及 O3、O3L、O3XL(O3 系列為副廠硬體)的社群分支。
|
||||
|
||||
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/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.
|
||||
基於活躍的社群與功能創新,`dragonpilot` 曾一度成長為 comma ai 官方認證的第一大 openpilot 分支,這份榮耀屬於每一位參與者。
|
||||
|
||||
<details>
|
||||
<summary>MIT Licensed</summary>
|
||||
## **🧑💻 設計理念 - 少即是多 (Less is More)**
|
||||
|
||||
openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified.
|
||||
隨著 openpilot 的 AI 模型日益強大,許多過去需要手動微調的功能,現在都已能透過更先進的模型來實現。因此,我們現在的開發重心回歸到 **「最小化修改」(minimal changes)** 的核心原則上。
|
||||
|
||||
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.
|
||||
我們的目標是為您提供最純粹、最接近官方的 openpilot 駕駛感受,同時保留 `dragonpilot` 那些經過時間考驗、最受社群喜愛的經典功能。我們相信,在強大的 AI 基礎上,簡潔即是力量。
|
||||
|
||||
**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>
|
||||
從最早的 **EON**,到官方的 **comma two / three (C2/C3/C3X)**,再到社群中各式各樣充滿智慧的**副廠機 (如 C1.5, O2, O3, O3L, O3XL 等)**,甚至我們也曾探索過在 [**Jetson Xavier NX**](https://github.com/eFiniLan/xnxpilot) 上的可能性。
|
||||
|
||||
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.
|
||||
目前最新版本主要支援: comma3 / 3X 以及 O3 / O3L / O3XL 等社群硬體。
|
||||
針對 EON / C1.5 / C2 等舊款硬體,最後支援的版本位於 [d2 分支](https://github.com/dragonpilot-community/dragonpilot/tree/d2)。
|
||||
無論您手上是哪一款設備,都代表著您對開源駕駛輔助的一份熱情。
|
||||
|
||||
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.
|
||||
`dragonpilot` 的成長,離不開每一位使用者的貢獻與回饋。我們是一個以**公開、透明**為原則的溫暖社群,希望在這裡能與所有對 openpilot / dragonpilot 有興趣的用戶分享、交流開發與使用上的經驗。
|
||||
|
||||
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>
|
||||
[**歡迎加入我們的 Facebook 社團進行交流!**](https://www.facebook.com/groups/930190251238639)
|
||||
|
||||
## **❤️ 特別感謝**
|
||||
|
||||
`dragonpilot` 從創立至今,從未打算透過 Patreon 等平台進行任何形式的募資。我們的初衷是建立一個讓大家能一起學習、一起成長的社群。It's all about fun, not money.
|
||||
|
||||
然而,我們仍要對那些自發性支持本專案的朋友們,致上最誠摯的感謝。正是因為有您們的鼓勵,我們才有更大的動力持續前進。
|
||||
|
||||
[**我們的贊助者名單**](SPONSORS.md)
|
||||
|
||||
### **安全聲明**
|
||||
|
||||
`dragonpilot` 是一種駕駛**輔助**系統,並非全自動駕駛。它旨在減輕您的駕駛疲勞,提升行車安全,但駕駛人仍需時刻保持專注,並隨時準備接管車輛。請務必遵守您所在地區的交通法規。
|
||||
|
||||
**最後,再次感謝您的到來。**
|
||||
|
||||
**期待與您一同在智慧駕駛的道路上,乘「龍」而行!**
|
||||
74
README_EN.md
Normal file
74
README_EN.md
Normal file
@ -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!**
|
||||
107
README_OPENPILOT.md
Normal file
107
README_OPENPILOT.md
Normal file
@ -0,0 +1,107 @@
|
||||
<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/selfdrive_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
|
||||
| branch | URL | description |
|
||||
|------------------|----------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| `release3` | openpilot.comma.ai | This is openpilot's release branch. |
|
||||
| `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. |
|
||||
| `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. |
|
||||
| `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/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.
|
||||
|
||||
<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>
|
||||
@ -28,7 +28,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ControlsReady", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||
{"CurrentBootlog", {PERSISTENT, STRING}},
|
||||
{"CurrentRoute", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||
{"DisableLogging", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||
{"DisableLogging", {PERSISTENT, BOOL, "0"}},
|
||||
{"DisablePowerDown", {PERSISTENT, BOOL}},
|
||||
{"DisableUpdates", {PERSISTENT, BOOL}},
|
||||
{"DisengageOnAccelerator", {PERSISTENT, BOOL, "0"}},
|
||||
@ -128,4 +128,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"UptimeOffroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||
{"UptimeOnroad", {PERSISTENT, FLOAT, "0.0"}},
|
||||
{"Version", {PERSISTENT, STRING}},
|
||||
{"dp_dev_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||
{"dp_dev_reset_conf", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
|
||||
};
|
||||
|
||||
0
dragonpilot/.gitignore
vendored
Normal file
0
dragonpilot/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/assets/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/assets/.gitignore
vendored
Normal file
BIN
dragonpilot/selfdrive/assets/dragonpilot.png
Normal file
BIN
dragonpilot/selfdrive/assets/dragonpilot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
35
dragonpilot/selfdrive/assets/icons/icon_empty.svg
Normal file
35
dragonpilot/selfdrive/assets/icons/icon_empty.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="icon_empty.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="3.0820312"
|
||||
inkscape:cx="128"
|
||||
inkscape:cy="114.37262"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1021"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
dragonpilot/selfdrive/assets/images/spinner_comma.png
Normal file
BIN
dragonpilot/selfdrive/assets/images/spinner_comma.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
0
dragonpilot/selfdrive/controls/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/controls/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/controls/lib/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/controls/lib/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/ui/.gitignore
vendored
Normal file
0
dragonpilot/selfdrive/ui/.gitignore
vendored
Normal file
87
dragonpilot/selfdrive/ui/layouts/settings/dragonpilot.py
Normal file
87
dragonpilot/selfdrive/ui/layouts/settings/dragonpilot.py
Normal file
@ -0,0 +1,87 @@
|
||||
import os
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.common.params import Params, UnknownKeyName
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item, simple_item, button_item, spin_button_item, double_spin_button_item, text_spin_button_item
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
|
||||
|
||||
|
||||
class DragonpilotLayout(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._params = Params()
|
||||
self._scroller: Scroller | None = None
|
||||
self._has_long_ctrl = False
|
||||
self._has_radar_unavailable = False
|
||||
|
||||
self._toggles = {}
|
||||
|
||||
if ui_state.CP is not None:
|
||||
self._has_long_ctrl = ui_state.CP.openpilotLongitudinalControl
|
||||
self._has_radar_unavailable = ui_state.CP.radarUnavailable
|
||||
match ui_state.CP.brand:
|
||||
case "toyota":
|
||||
self._toyota_toggles()
|
||||
case "volkswagen":
|
||||
self._vag_toggles()
|
||||
case "mazda":
|
||||
self._mazda_toggles()
|
||||
|
||||
self._lat_toggles()
|
||||
self._lon_toggles()
|
||||
self._ui_toggles()
|
||||
self._device_toggles()
|
||||
|
||||
self._reset_dp_conf_btn = button_item(
|
||||
lambda: tr("Reset DP Settings"),
|
||||
lambda: tr("RESET"),
|
||||
lambda: tr("Reset dragonpilot settings to default and restart the device."),
|
||||
callback=self._reset_dp_conf)
|
||||
|
||||
self._toggles['btn_reset_dp_conf'] = self._reset_dp_conf_btn
|
||||
|
||||
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
||||
|
||||
def _toyota_toggles(self):
|
||||
self._toggles["title_toyota"] = simple_item(title=lambda: tr("### Toyota / Lexus ###"))
|
||||
|
||||
def _vag_toggles(self):
|
||||
self._toggles["title_vag"] = simple_item(title=lambda: tr("### VAG ###"))
|
||||
|
||||
def _mazda_toggles(self):
|
||||
self._toggles["title_mazda"] = simple_item(title=lambda: tr("### Mazda ###"))
|
||||
|
||||
def _lat_toggles(self):
|
||||
self._toggles["title_lat"] = simple_item(title=lambda: tr("### Lateral ###"))
|
||||
|
||||
def _lon_toggles(self):
|
||||
self._toggles["title_lon"] = simple_item(title=lambda: tr("### Longitudinal ###"))
|
||||
|
||||
def _ui_toggles(self):
|
||||
self._toggles["title_ui"] = simple_item(title=lambda: tr("### UI ###"))
|
||||
|
||||
def _device_toggles(self):
|
||||
self._toggles["title_dev"] = simple_item(title=lambda: tr("### Device ###"))
|
||||
|
||||
def _reset_dp_conf(self):
|
||||
def reset_dp_conf(result: int):
|
||||
# Check engaged again in case it changed while the dialog was open
|
||||
if result != DialogResult.CONFIRM:
|
||||
return
|
||||
self._params.put_bool_nonblocking("dp_dev_reset_conf", True)
|
||||
|
||||
dialog = ConfirmDialog(tr("Are you sure you want to reset ALL DP SETTINGS to default?"), tr("Reset"))
|
||||
gui_app.set_modal_overlay(dialog, callback=reset_dp_conf)
|
||||
|
||||
def show_event(self):
|
||||
self._scroller.show_event()
|
||||
self._update_toggles()
|
||||
|
||||
def _update_toggles(self):
|
||||
ui_state.update_params()
|
||||
|
||||
def _render(self, rect):
|
||||
self._scroller.render(rect)
|
||||
@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.notCar = True
|
||||
ret.brand = "body"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.body)]
|
||||
|
||||
@ -149,7 +149,7 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
|
||||
|
||||
|
||||
def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
|
||||
is_release: bool, num_pandas: int = 1, cached_params: CarParamsT | None = None):
|
||||
is_release: bool, num_pandas: int = 1, dp_params: int = 0, cached_params: CarParamsT | None = None):
|
||||
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
|
||||
|
||||
if candidate is None:
|
||||
@ -157,7 +157,7 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
|
||||
candidate = "MOCK"
|
||||
|
||||
CarInterface = interfaces[candidate]
|
||||
CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, alpha_long_allowed, is_release, docs=False)
|
||||
CP: CarParams = CarInterface.get_params(candidate, fingerprints, car_fw, alpha_long_allowed, is_release, dp_params, docs=False)
|
||||
CP.carVin = vin
|
||||
CP.carFw = car_fw
|
||||
CP.fingerprintSource = source
|
||||
|
||||
@ -13,7 +13,7 @@ class CarInterface(CarInterfaceBase):
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "chrysler"
|
||||
ret.dashcamOnly = candidate in RAM_HD
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ def get_params_for_docs(platform) -> CarParams:
|
||||
cp_platform = platform if platform in interfaces else MOCK.MOCK
|
||||
CP: CarParams = interfaces[cp_platform].get_params(cp_platform, fingerprint=gen_empty_fingerprint(),
|
||||
car_fw=[CarParams.CarFw(ecu=CarParams.Ecu.unknown)],
|
||||
alpha_long=True, is_release=False, docs=True)
|
||||
alpha_long=True, is_release=False, dp_params=0, docs=True)
|
||||
return CP
|
||||
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return CarControllerParams.ACCEL_MIN, np.interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "ford"
|
||||
|
||||
ret.radarUnavailable = Bus.radar not in DBC[candidate]
|
||||
|
||||
@ -82,7 +82,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return self.lateral_accel_from_torque_linear
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "gm"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.gm)]
|
||||
ret.autoResumeSng = False
|
||||
|
||||
@ -31,7 +31,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return CarControllerParams.NIDEC_ACCEL_MIN, np.interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "honda"
|
||||
|
||||
CAN = CanBus(ret, fingerprint)
|
||||
|
||||
@ -23,7 +23,7 @@ class CarInterface(CarInterfaceBase):
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "hyundai"
|
||||
|
||||
# "LKA steering" if LKAS or LKAS_ALT messages are seen coming from the camera.
|
||||
|
||||
@ -51,7 +51,7 @@ class TestHyundaiFingerprint:
|
||||
if lka_steering:
|
||||
cam_can = CanBus(None, fingerprint).CAM
|
||||
fingerprint[cam_can] = [0x50, 0x110] # LKA steering messages
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], False, False, False)
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, fingerprint, [], False, False, 0, False)
|
||||
assert bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING) == lka_steering
|
||||
|
||||
# radar available
|
||||
@ -59,14 +59,14 @@ class TestHyundaiFingerprint:
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
if radar:
|
||||
fingerprint[1][RADAR_START_ADDR] = 8
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA, fingerprint, [], False, False, 0, False)
|
||||
assert CP.radarUnavailable != radar
|
||||
|
||||
def test_alternate_limits(self):
|
||||
# Alternate lateral control limits, for high torque cars, verify Panda safety mode flag is set
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
for car_model in CAR:
|
||||
CP = CarInterface.get_params(car_model, fingerprint, [], False, False, False)
|
||||
CP = CarInterface.get_params(car_model, fingerprint, [], False, False, 0, False)
|
||||
assert bool(CP.flags & HyundaiFlags.ALT_LIMITS) == bool(CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.ALT_LIMITS)
|
||||
|
||||
def test_can_features(self):
|
||||
|
||||
@ -122,11 +122,11 @@ class CarInterfaceBase(ABC):
|
||||
"""
|
||||
Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints.
|
||||
"""
|
||||
return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False, False)
|
||||
return cls.get_params(candidate, gen_empty_fingerprint(), list(), False, False, 0, False)
|
||||
|
||||
@classmethod
|
||||
def get_params(cls, candidate: str, fingerprint: dict[int, dict[int, int]], car_fw: list[structs.CarParams.CarFw],
|
||||
alpha_long: bool, is_release: bool, docs: bool) -> structs.CarParams:
|
||||
alpha_long: bool, is_release: bool, dp_params: int, docs: bool) -> structs.CarParams:
|
||||
ret = CarInterfaceBase.get_std_params(candidate)
|
||||
|
||||
platform = PLATFORMS[candidate]
|
||||
@ -139,7 +139,7 @@ class CarInterfaceBase(ABC):
|
||||
ret.tireStiffnessFactor = platform.config.specs.tireStiffnessFactor
|
||||
ret.flags |= int(platform.config.flags)
|
||||
|
||||
ret = cls._get_params(ret, candidate, fingerprint, car_fw, alpha_long, is_release, docs)
|
||||
ret = cls._get_params(ret, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs)
|
||||
|
||||
# Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload
|
||||
if not ret.notCar:
|
||||
@ -154,7 +154,7 @@ class CarInterfaceBase(ABC):
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint: dict[int, dict[int, int]],
|
||||
car_fw: list[structs.CarParams.CarFw], alpha_long: bool, is_release: bool, docs: bool) -> structs.CarParams:
|
||||
car_fw: list[structs.CarParams.CarFw], alpha_long: bool, is_release: bool, dp_params: int, docs: bool) -> structs.CarParams:
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -12,7 +12,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "mazda"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.mazda)]
|
||||
ret.radarUnavailable = True
|
||||
|
||||
@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "mock"
|
||||
ret.mass = 1700.
|
||||
ret.wheelbase = 2.70
|
||||
|
||||
@ -10,7 +10,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "nissan"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.nissan)]
|
||||
ret.autoResumeSng = False
|
||||
|
||||
@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = 'psa'
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.psa)]
|
||||
|
||||
@ -12,7 +12,7 @@ class CarInterface(CarInterfaceBase):
|
||||
RadarInterface = RadarInterface
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "rivian"
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.rivian)]
|
||||
|
||||
@ -18,3 +18,6 @@ CarStateT = capnp.lib.capnp._StructModule
|
||||
RadarDataT = capnp.lib.capnp._StructModule
|
||||
CarControlT = capnp.lib.capnp._StructModule
|
||||
CarParamsT = capnp.lib.capnp._StructModule
|
||||
|
||||
class DPFlags:
|
||||
pass
|
||||
|
||||
@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "subaru"
|
||||
ret.radarUnavailable = True
|
||||
# for HYBRID CARS to be upstreamed, we need:
|
||||
|
||||
@ -10,7 +10,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "tesla"
|
||||
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
|
||||
|
||||
@ -52,7 +52,7 @@ def get_fuzzy_car_interface(car_name: str, draw: DrawType) -> CarInterfaceBase:
|
||||
# initialize car interface
|
||||
CarInterface = interfaces[car_name]
|
||||
car_params = CarInterface.get_params(car_name, params['fingerprints'], params['car_fw'],
|
||||
alpha_long=params['alpha_long'], is_release=False, docs=False)
|
||||
alpha_long=params['alpha_long'], is_release=False, dp_params=0, docs=False)
|
||||
return CarInterface(car_params)
|
||||
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ class CarInterface(CarInterfaceBase):
|
||||
return CarControllerParams(CP).ACCEL_MIN, CarControllerParams(CP).ACCEL_MAX
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "toyota"
|
||||
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.toyota)]
|
||||
ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate]
|
||||
|
||||
@ -10,7 +10,7 @@ class CarInterface(CarInterfaceBase):
|
||||
CarController = CarController
|
||||
|
||||
@staticmethod
|
||||
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, docs) -> structs.CarParams:
|
||||
def _get_params(ret: structs.CarParams, candidate: CAR, fingerprint, car_fw, alpha_long, is_release, dp_params, docs) -> structs.CarParams:
|
||||
ret.brand = "volkswagen"
|
||||
ret.radarUnavailable = True
|
||||
|
||||
|
||||
BIN
selfdrive/assets/icons/plus.png
Normal file
BIN
selfdrive/assets/icons/plus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@ -82,6 +82,8 @@ class Car:
|
||||
|
||||
is_release = self.params.get_bool("IsReleaseBranch")
|
||||
|
||||
dp_params = 0
|
||||
|
||||
if CI is None:
|
||||
# wait for one pandaState and one CAN packet
|
||||
print("Waiting for CAN messages...")
|
||||
@ -99,7 +101,7 @@ class Car:
|
||||
with car.CarParams.from_bytes(cached_params_raw) as _cached_params:
|
||||
cached_params = _cached_params
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params)
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, dp_params, cached_params)
|
||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP)
|
||||
self.CP = self.CI.CP
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ V_CRUISE_MIN = 8
|
||||
V_CRUISE_MAX = 145
|
||||
V_CRUISE_UNSET = 255
|
||||
V_CRUISE_INITIAL = 40
|
||||
V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105
|
||||
V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 50
|
||||
IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding errors incrementing set speed
|
||||
|
||||
ButtonEvent = car.CarState.ButtonEvent
|
||||
|
||||
@ -149,7 +149,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
cls.openpilot_enabled = cls.car_safety_mode_frame is not None
|
||||
|
||||
cls.CarInterface = interfaces[cls.platform]
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, dp_params=0, docs=False)
|
||||
assert cls.CP
|
||||
assert cls.CP.carFingerprint == cls.platform
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ MIN_ALLOW_THROTTLE_SPEED = 2.5
|
||||
_A_TOTAL_MAX_V = [1.7, 3.2]
|
||||
_A_TOTAL_MAX_BP = [20., 40.]
|
||||
|
||||
class DPFlags:
|
||||
pass
|
||||
|
||||
def get_max_accel(v_ego):
|
||||
return np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS)
|
||||
@ -89,7 +91,7 @@ class LongitudinalPlanner:
|
||||
throttle_prob = 1.0
|
||||
return x, v, a, j, throttle_prob
|
||||
|
||||
def update(self, sm):
|
||||
def update(self, sm, dp_flags = 0):
|
||||
mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
|
||||
|
||||
if len(sm['carControl'].orientationNED) == 3:
|
||||
|
||||
@ -4,7 +4,7 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Priority, config_realtime_process
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.controls.lib.ldw import LaneDepartureWarning
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner, DPFlags
|
||||
import cereal.messaging as messaging
|
||||
|
||||
|
||||
@ -22,10 +22,12 @@ def main():
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
||||
poll='modelV2')
|
||||
|
||||
dp_flags = 0
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
if sm.updated['modelV2']:
|
||||
longitudinal_planner.update(sm)
|
||||
longitudinal_planner.update(sm, dp_flags)
|
||||
longitudinal_planner.publish(sm, pm)
|
||||
|
||||
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
|
||||
|
||||
@ -122,7 +122,7 @@ class SelfdriveD:
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
# Determine startup event
|
||||
self.startup_event = EventName.startup if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster
|
||||
self.startup_event = EventName.startup #if build_metadata.openpilot.comma_remote and build_metadata.tested_channel else EventName.startupMaster
|
||||
if not car_recognized:
|
||||
self.startup_event = EventName.startupNoCar
|
||||
elif car_recognized and self.CP.passive:
|
||||
|
||||
@ -2,13 +2,18 @@ from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.list_view import toggle_item
|
||||
from openpilot.system.ui.widgets.list_view import toggle_item, button_item
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
|
||||
# rick - for show error logs
|
||||
from openpilot.system.ui.widgets.html_render import HtmlModal
|
||||
import os
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
# Description constants
|
||||
DESCRIPTIONS = {
|
||||
'enable_adb': tr_noop(
|
||||
@ -33,6 +38,7 @@ class DeveloperLayout(Widget):
|
||||
super().__init__()
|
||||
self._params = Params()
|
||||
self._is_release = self._params.get_bool("IsReleaseBranch")
|
||||
self._last_error_log_dialog: HtmlModal | None = None
|
||||
|
||||
# Build items and keep references for callbacks/state updates
|
||||
self._adb_toggle = toggle_item(
|
||||
@ -82,6 +88,7 @@ class DeveloperLayout(Widget):
|
||||
self._joystick_toggle,
|
||||
self._long_maneuver_toggle,
|
||||
self._alpha_long_toggle,
|
||||
button_item(lambda: tr("Show Last Errors"), lambda: tr("Show"), callback=self._on_show_last_errors),
|
||||
]
|
||||
|
||||
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
||||
@ -170,3 +177,8 @@ class DeveloperLayout(Widget):
|
||||
self._params.put_bool("AlphaLongitudinalEnabled", False)
|
||||
self._params.put_bool("OnroadCycleRequested", True)
|
||||
self._update_toggles()
|
||||
|
||||
def _on_show_last_errors(self):
|
||||
if not self._last_error_log_dialog:
|
||||
self._last_error_log_dialog = HtmlModal(text=(self._params.get("dp_dev_last_log") or ""))
|
||||
gui_app.set_modal_overlay(self._last_error_log_dialog)
|
||||
|
||||
@ -13,6 +13,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.network import NetworkUI
|
||||
from dragonpilot.selfdrive.ui.layouts.settings.dragonpilot import DragonpilotLayout
|
||||
|
||||
# Settings close button
|
||||
SETTINGS_CLOSE_TEXT = "×"
|
||||
@ -40,6 +41,7 @@ class PanelType(IntEnum):
|
||||
SOFTWARE = 3
|
||||
FIREHOSE = 4
|
||||
DEVELOPER = 5
|
||||
DRAGONPILOT = 6
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -65,6 +67,7 @@ class SettingsLayout(Widget):
|
||||
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
|
||||
PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()),
|
||||
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
|
||||
PanelType.DRAGONPILOT: PanelInfo("dp", DragonpilotLayout()),
|
||||
}
|
||||
|
||||
self._font_medium = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
@ -68,7 +68,7 @@ class SoftwareLayout(Widget):
|
||||
self._version_item,
|
||||
self._download_btn,
|
||||
self._install_btn,
|
||||
self._branch_btn,
|
||||
# self._branch_btn, # rick - disable this for now
|
||||
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
|
||||
], line_separator=True, spacing=0)
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ DESCRIPTIONS = {
|
||||
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
|
||||
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
|
||||
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."),
|
||||
"DisableLogging": tr_noop("Disable logging service"),
|
||||
"DisableUpdates": tr_noop("Disable update service"),
|
||||
}
|
||||
|
||||
|
||||
@ -90,6 +92,18 @@ class TogglesLayout(Widget):
|
||||
"metric.png",
|
||||
False,
|
||||
),
|
||||
"DisableLogging": (
|
||||
lambda: tr("Disable Logging"),
|
||||
DESCRIPTIONS["DisableLogging"],
|
||||
"",
|
||||
False,
|
||||
),
|
||||
"DisableUpdates": (
|
||||
lambda: tr("Disable Updates"),
|
||||
DESCRIPTIONS["DisableUpdates"],
|
||||
"",
|
||||
False,
|
||||
),
|
||||
}
|
||||
|
||||
self._long_personality_setting = multiple_button_item(
|
||||
|
||||
@ -11,7 +11,7 @@ import cereal.messaging as messaging
|
||||
import openpilot.system.sentry as sentry
|
||||
from openpilot.common.params import Params, ParamKeyFlag
|
||||
from openpilot.common.text_window import TextWindow
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.hardware import HARDWARE, TICI
|
||||
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
|
||||
from openpilot.system.manager.process import ensure_running
|
||||
from openpilot.system.manager.process_config import managed_processes
|
||||
@ -171,8 +171,10 @@ def manager_thread() -> None:
|
||||
|
||||
# Exit main loop when uninstall/shutdown/reboot is needed
|
||||
shutdown = False
|
||||
for param in ("DoUninstall", "DoShutdown", "DoReboot"):
|
||||
for param in ("DoUninstall", "DoShutdown", "DoReboot", "dp_dev_reset_conf"):
|
||||
if params.get_bool(param):
|
||||
if TICI and param == "dp_dev_reset_conf":
|
||||
os.system("rm -fr /data/params/d/dp_*")
|
||||
shutdown = True
|
||||
params.put("LastManagerExitReason", f"{param} {datetime.datetime.now()}")
|
||||
cloudlog.warning(f"Shutting down manager - {param} set")
|
||||
@ -207,6 +209,9 @@ def main() -> None:
|
||||
elif params.get_bool("DoShutdown"):
|
||||
cloudlog.warning("shutdown")
|
||||
HARDWARE.shutdown()
|
||||
elif params.get_bool("dp_dev_reset_conf"):
|
||||
cloudlog.warning("reboot")
|
||||
HARDWARE.reboot()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -19,7 +19,7 @@ def iscar(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
return started and not CP.notCar
|
||||
|
||||
def logging(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||
run = (not CP.notCar) or not params.get_bool("DisableLogging")
|
||||
run = not params.get_bool("DisableLogging")
|
||||
return started and run
|
||||
|
||||
def ublox_available() -> bool:
|
||||
|
||||
@ -12,9 +12,9 @@ from openpilot.system.version import get_build_metadata, get_version
|
||||
|
||||
class SentryProject(Enum):
|
||||
# python project
|
||||
SELFDRIVE = "https://6f3c7076c1e14b2aa10f5dde6dda0cc4@o33823.ingest.sentry.io/77924"
|
||||
SELFDRIVE = "https://980a0cba712a4c3593c33c78a12446e1@o273754.ingest.sentry.io/1488600"
|
||||
# native project
|
||||
SELFDRIVE_NATIVE = "https://3e4b586ed21a4479ad5d85083b639bc6@o33823.ingest.sentry.io/157615"
|
||||
SELFDRIVE_NATIVE = "https://980a0cba712a4c3593c33c78a12446e1@o273754.ingest.sentry.io/1488600"
|
||||
|
||||
|
||||
def report_tombstone(fn: str, message: str, contents: str) -> None:
|
||||
@ -26,11 +26,16 @@ def report_tombstone(fn: str, message: str, contents: str) -> None:
|
||||
sentry_sdk.capture_message(message=message)
|
||||
sentry_sdk.flush()
|
||||
|
||||
def save_exception(exc_text):
|
||||
log = "\n".join(exc_text.splitlines()) + "\n"
|
||||
Params().put("dp_dev_last_log", log)
|
||||
|
||||
def capture_exception(*args, **kwargs) -> None:
|
||||
cloudlog.error("crash", exc_info=kwargs.get('exc_info', 1))
|
||||
|
||||
try:
|
||||
import traceback
|
||||
save_exception(traceback.format_exc())
|
||||
sentry_sdk.capture_exception(*args, **kwargs)
|
||||
sentry_sdk.flush() # https://github.com/getsentry/sentry-python/issues/291
|
||||
except Exception:
|
||||
|
||||
@ -26,7 +26,7 @@ def clamp(value, min_value, max_value):
|
||||
class Spinner(Widget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
|
||||
self._comma_texture = gui_app.texture("../../dragonpilot/selfdrive/assets/images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
|
||||
self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
|
||||
self._rotation = 0.0
|
||||
self._progress: int | None = None
|
||||
|
||||
@ -296,6 +296,12 @@ class ListItem(Widget):
|
||||
# Cached properties for performance
|
||||
self._prev_description: str | None = self.description
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
if self.action_item:
|
||||
return self.action_item.enabled
|
||||
return True
|
||||
|
||||
def show_event(self):
|
||||
self._set_description_visible(False)
|
||||
|
||||
@ -352,17 +358,20 @@ class ListItem(Widget):
|
||||
content_x = self._rect.x + ITEM_PADDING
|
||||
text_x = content_x
|
||||
|
||||
color = ITEM_TEXT_COLOR if self.enabled else ITEM_TEXT_VALUE_COLOR
|
||||
icon_tint = rl.WHITE if self.enabled else ITEM_TEXT_VALUE_COLOR
|
||||
|
||||
# Only draw title and icon for items that have them
|
||||
if self.title:
|
||||
# Draw icon if present
|
||||
if self.icon:
|
||||
rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.width) // 2), rl.WHITE)
|
||||
rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.width) // 2), icon_tint)
|
||||
text_x += ICON_SIZE + ITEM_PADDING
|
||||
|
||||
# Draw main text
|
||||
text_size = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE)
|
||||
item_y = self._rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR)
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, color)
|
||||
|
||||
# Draw description if visible
|
||||
if self.description_visible:
|
||||
@ -466,3 +475,288 @@ def multiple_button_item(title: str | Callable[[], str], description: str | Call
|
||||
button_width: int = BUTTON_WIDTH, callback: Callable = None, icon: str = ""):
|
||||
action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback)
|
||||
return ListItem(title=title, description=description, icon=icon, action_item=action)
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class BaseSpinBoxAction(ItemAction, ABC):
|
||||
def __init__(self, callback: Callable | None, enabled: bool | Callable[[], bool], width: int):
|
||||
super().__init__(width=width, enabled=enabled)
|
||||
self._callback = callback
|
||||
|
||||
icon_size = 60
|
||||
self._minus_icon = gui_app.texture("icons/minus.png", icon_size, icon_size)
|
||||
self._plus_icon = gui_app.texture("icons/plus.png", icon_size, icon_size)
|
||||
|
||||
self._minus_button = Button("", self._on_minus, icon=self._minus_icon, button_style=ButtonStyle.LIST_ACTION, multi_touch=True)
|
||||
self._plus_button = Button("", self._on_plus, icon=self._plus_icon, button_style=ButtonStyle.LIST_ACTION, multi_touch=True)
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(touch_callback)
|
||||
self._minus_button.set_touch_valid_callback(touch_callback)
|
||||
self._plus_button.set_touch_valid_callback(touch_callback)
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
is_enabled = _resolve_value(self._enabled_source, False)
|
||||
|
||||
button_width = 110
|
||||
button_height = BUTTON_HEIGHT
|
||||
spacing = 10
|
||||
button_y = rect.y + (rect.height - button_height) / 2
|
||||
|
||||
minus_rect = rl.Rectangle(rect.x, button_y, button_width, button_height)
|
||||
plus_rect = rl.Rectangle(rect.x + rect.width - button_width, button_y, button_width, button_height)
|
||||
|
||||
label_x = rect.x + button_width + spacing
|
||||
label_width = (plus_rect.x) - (label_x) - spacing
|
||||
|
||||
self._minus_button.set_enabled(is_enabled and self._get_minus_enabled())
|
||||
self._plus_button.set_enabled(is_enabled and self._get_plus_enabled())
|
||||
|
||||
self._minus_button.render(minus_rect)
|
||||
self._plus_button.render(plus_rect)
|
||||
|
||||
if label_width > 0:
|
||||
label_rect = rl.Rectangle(label_x, rect.y, label_width, rect.height)
|
||||
display_text = self._get_display_text()
|
||||
color = ITEM_TEXT_VALUE_COLOR if is_enabled else ITEM_DESC_TEXT_COLOR
|
||||
gui_label(label_rect, display_text, font_size=ITEM_TEXT_FONT_SIZE, color=color,
|
||||
font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def _on_minus(self):
|
||||
"""Called when the minus button is pressed."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _on_plus(self):
|
||||
"""Called when the plus button is pressed."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _get_minus_enabled(self) -> bool:
|
||||
"""Return True if the minus button should be enabled."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _get_plus_enabled(self) -> bool:
|
||||
"""Return True if the plus button should be enabled."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _get_display_text(self) -> str:
|
||||
"""Return the string to display in the center."""
|
||||
pass
|
||||
|
||||
|
||||
class SpinBoxAction(BaseSpinBoxAction):
|
||||
def __init__(self, initial_value: int, min_val: int, max_val: int, step: int = 1,
|
||||
suffix: str = "", special_value_text: str | None = None,
|
||||
callback: Callable[[int], None] | None = None, enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 320):
|
||||
super().__init__(callback, enabled, width)
|
||||
self._value = initial_value
|
||||
self._min_val = min_val
|
||||
self._max_val = max_val
|
||||
self._step = step
|
||||
self._suffix = suffix
|
||||
self._special_value_text = special_value_text
|
||||
|
||||
def set_value(self, value: int):
|
||||
self._value = max(self._min_val, min(self._max_val, value))
|
||||
|
||||
def get_value(self) -> int:
|
||||
return self._value
|
||||
|
||||
def _on_minus(self):
|
||||
new_val = max(self._min_val, self._value - self._step)
|
||||
if new_val != self._value:
|
||||
self._value = new_val
|
||||
if self._callback:
|
||||
self._callback(self._value)
|
||||
|
||||
def _on_plus(self):
|
||||
new_val = min(self._max_val, self._value + self._step)
|
||||
if new_val != self._value:
|
||||
self._value = new_val
|
||||
if self._callback:
|
||||
self._callback(self._value)
|
||||
|
||||
def _get_minus_enabled(self) -> bool:
|
||||
return self._value > self._min_val
|
||||
|
||||
def _get_plus_enabled(self) -> bool:
|
||||
return self._value < self._max_val
|
||||
|
||||
def _get_display_text(self) -> str:
|
||||
if self._special_value_text and self._value == self._min_val:
|
||||
return self._special_value_text
|
||||
return f"{self._value}{self._suffix}"
|
||||
|
||||
|
||||
class DoubleSpinBoxAction(BaseSpinBoxAction):
|
||||
def __init__(self, initial_value: float, min_val: float, max_val: float, step: float = 0.1,
|
||||
decimals: int = 1, suffix: str = "", special_value_text: str | None = None, # <-- 1. This is correct
|
||||
callback: Callable[[float], None] | None = None, enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 320):
|
||||
super().__init__(callback, enabled, width)
|
||||
self._value = initial_value
|
||||
self._min_val = min_val
|
||||
self._max_val = max_val
|
||||
self._step = step
|
||||
self._decimals = decimals
|
||||
self._suffix = suffix
|
||||
self._special_value_text = special_value_text # <-- 2. Store the variable
|
||||
|
||||
def set_value(self, value: float):
|
||||
self._value = max(self._min_val, min(self._max_val, value))
|
||||
|
||||
def get_value(self) -> float:
|
||||
return self._value
|
||||
|
||||
def _on_minus(self):
|
||||
new_val = max(self._min_val, self._value - self._step)
|
||||
if new_val < self._value:
|
||||
self._value = new_val
|
||||
if self._callback:
|
||||
self._callback(self._value)
|
||||
|
||||
def _on_plus(self):
|
||||
new_val = min(self._max_val, self._value + self._step)
|
||||
if new_val > self._value:
|
||||
self._value = new_val
|
||||
if self._callback:
|
||||
self._callback(self._value)
|
||||
|
||||
def _get_minus_enabled(self) -> bool:
|
||||
return self._value > self._min_val
|
||||
|
||||
def _get_plus_enabled(self) -> bool:
|
||||
return self._value < self._max_val
|
||||
|
||||
def _get_display_text(self) -> str:
|
||||
is_min_val = abs(self._value - self._min_val) < 1e-9
|
||||
if self._special_value_text and is_min_val:
|
||||
return self._special_value_text
|
||||
|
||||
return f"{self._value:.{self._decimals}f}{self._suffix}"
|
||||
|
||||
|
||||
class TextSpinBoxAction(BaseSpinBoxAction):
|
||||
def __init__(self, options: list[str], initial_index: int = 0,
|
||||
callback: Callable[[int], None] | None = None, enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 320):
|
||||
super().__init__(callback, enabled, width)
|
||||
self._options = options if options else [""]
|
||||
self._current_index = max(0, min(len(self._options) - 1, initial_index))
|
||||
self._initial_index = initial_index
|
||||
|
||||
def set_index(self, index: int):
|
||||
self._current_index = max(0, min(len(self._options) - 1, index))
|
||||
|
||||
def get_index(self) -> int:
|
||||
return self._current_index
|
||||
|
||||
def _on_minus(self):
|
||||
new_idx = max(0, self._current_index - 1)
|
||||
if new_idx != self._current_index:
|
||||
self._current_index = new_idx
|
||||
if self._callback:
|
||||
self._callback(self._current_index)
|
||||
|
||||
def _on_plus(self):
|
||||
new_idx = min(len(self._options) - 1, self._current_index + 1)
|
||||
if new_idx != self._current_index:
|
||||
self._current_index = new_idx
|
||||
if self._callback:
|
||||
self._callback(self._current_index)
|
||||
|
||||
def _get_minus_enabled(self) -> bool:
|
||||
return self._current_index > 0
|
||||
|
||||
def _get_plus_enabled(self) -> bool:
|
||||
return self._current_index < len(self._options) - 1
|
||||
|
||||
def _get_display_text(self) -> str:
|
||||
return self._options[self._current_index]
|
||||
|
||||
|
||||
def spin_button_item(title: str | Callable[[], str], callback: Callable[[int], None] | None,
|
||||
initial_value: int, min_val: int, max_val: int, step: int = 1,
|
||||
suffix: str = "", special_value_text: str | None = None,
|
||||
description: str | Callable[[], str] | None = None,
|
||||
icon: str = "", enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 500) -> ListItem:
|
||||
"""
|
||||
Creates a ListItem with a spinbox-style control (minus, value, plus).
|
||||
|
||||
:param title: The main title of the list item.
|
||||
:param callback: Function to call with the new integer value when it changes.
|
||||
:param initial_value: The starting value.
|
||||
:param min_val: The minimum allowed value.
|
||||
:param max_val: The maximum allowed value.
|
||||
:param step: The increment/decrement amount on each button press.
|
||||
:param suffix: A string to append to the value (e.g., " s").
|
||||
:param special_value_text: Text to display when the value is at min_val (e.g., "Auto").
|
||||
:param description: Optional description text shown when the item is expanded.
|
||||
:param icon: Optional icon for the list item.
|
||||
:param enabled: Whether the control is enabled.
|
||||
:return: A ListItem widget.
|
||||
"""
|
||||
action = SpinBoxAction(initial_value=initial_value, min_val=min_val, max_val=max_val, step=step,
|
||||
suffix=suffix, special_value_text=special_value_text,
|
||||
callback=callback, enabled=enabled, width=width)
|
||||
return ListItem(title=title, description=description, action_item=action, icon=icon)
|
||||
|
||||
def double_spin_button_item(title: str | Callable[[], str], callback: Callable[[float], None] | None,
|
||||
initial_value: float, min_val: float, max_val: float, step: float = 0.1,
|
||||
decimals: int = 1, suffix: str = "", special_value_text: str | None = None,
|
||||
description: str | Callable[[], str] | None = None,
|
||||
icon: str = "", enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 500) -> ListItem:
|
||||
"""
|
||||
Creates a ListItem with a spinbox-style control for float values.
|
||||
|
||||
:param decimals: Number of decimal places to display.
|
||||
:return: A ListItem widget.
|
||||
"""
|
||||
action = DoubleSpinBoxAction(initial_value=initial_value, min_val=min_val, max_val=max_val, step=step,
|
||||
decimals=decimals, suffix=suffix, special_value_text=special_value_text,
|
||||
callback=callback, enabled=enabled, width=width)
|
||||
return ListItem(title=title, description=description, action_item=action, icon=icon)
|
||||
|
||||
def text_spin_button_item(title: str | Callable[[], str], callback: Callable[[int], None] | None,
|
||||
options: list[str], initial_index: int = 0,
|
||||
description: str | Callable[[], str] | None = None,
|
||||
icon: str = "", enabled: bool | Callable[[], bool] = True,
|
||||
width: int = 500) -> ListItem:
|
||||
"""
|
||||
Creates a ListItem with a spinbox control for a list of text options.
|
||||
|
||||
:param options: A list of strings to cycle through (e.g., ['Low', 'Mid', 'High']).
|
||||
:param initial_index: The starting index in the options list.
|
||||
:param callback: Function to call with the new *index* (int) when it changes.
|
||||
:return: A ListItem widget.
|
||||
"""
|
||||
action = TextSpinBoxAction(options=options, initial_index=initial_index,
|
||||
callback=callback, enabled=enabled, width=width)
|
||||
return ListItem(title=title, description=description, action_item=action, icon=icon)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user