diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 6bbbbacb70..47eb6c216f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -8,6 +8,7 @@ body:
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.
@@ -22,23 +23,11 @@ body:
validations:
required: true
- - type: dropdown
- id: hw
- attributes:
- label: What hardware does this issue affect?
- multiple: true
- options:
- - comma three
- - comma two
- - EON Gold
- 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
+ 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
diff --git a/.github/ISSUE_TEMPLATE/car_bug_report.yml b/.github/ISSUE_TEMPLATE/car_bug_report.yml
index a48f984192..7f368f11b4 100644
--- a/.github/ISSUE_TEMPLATE/car_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/car_bug_report.yml
@@ -1,6 +1,6 @@
name: Car bug report
description: For issues with a particular car make or model
-labels: ["car bug"]
+labels: ["car", "bug"]
body:
- type: markdown
@@ -21,18 +21,6 @@ body:
validations:
required: true
- - type: dropdown
- id: hw
- attributes:
- label: What hardware does this issue affect?
- multiple: true
- options:
- - comma three
- - comma two
- - EON Gold
- validations:
- required: true
-
- type: input
id: car
attributes:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 2c2deb17ba..45a8af0aaf 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,11 @@
blank_issues_enabled: false
contact_links:
+ - name: Report model bugs
+ url: https://github.com/commaai/openpilot/discussions/categories/model-feedback
+ about: Provide feedback for the driving or driver monitoring models
- name: Discussions
url: https://github.com/commaai/openpilot/discussions
- about: For questions and discussion about openpilot
+ about: For questions and general discussion about openpilot
- name: Community Wiki
url: https://github.com/commaai/openpilot/wiki
about: Check out our community wiki
diff --git a/.github/PULL_REQUEST_TEMPLATE/car_bugfix.md b/.github/PULL_REQUEST_TEMPLATE/car_bugfix.md
index d0c8bc58e4..76c86346c8 100644
--- a/.github/PULL_REQUEST_TEMPLATE/car_bugfix.md
+++ b/.github/PULL_REQUEST_TEMPLATE/car_bugfix.md
@@ -1,6 +1,6 @@
---
name: Car Bug fix
-about: For vehicle/brand specifc bug fixes
+about: For vehicle/brand specific bug fixes
title: ''
labels: 'car bug fix'
assignees: ''
diff --git a/.github/PULL_REQUEST_TEMPLATE/car_port.md b/.github/PULL_REQUEST_TEMPLATE/car_port.md
index d275468395..4264363ba2 100644
--- a/.github/PULL_REQUEST_TEMPLATE/car_port.md
+++ b/.github/PULL_REQUEST_TEMPLATE/car_port.md
@@ -9,7 +9,7 @@ assignees: ''
**Checklist**
- [ ] added to README
-- [ ] test route added to [test_routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/test/test_models.py)
+- [ ] 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):
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 38a6c78643..efa947a91a 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -22,8 +22,8 @@ Route: [a route with the bug fix]
+
# Supported Cars
-## comma.ai supported cars
-
-| Make | Model (US Market Reference) | Supported Package | ACC | No ACC accel below | No ALC below |
-| ----------| ------------------------------| ------------------| -----------------| -------------------| ------------------|
-| Acura | ILX 2016-19 | AcuraWatch Plus | openpilot | 25mph1 | 25mph |
-| Acura | RDX 2016-18 | AcuraWatch Plus | openpilot | 25mph1 | 12mph |
-| Acura | RDX 2019-21 | All | Stock | 0mph | 3mph |
-| Honda | Accord 2018-21 | All | Stock | 0mph | 3mph |
-| Honda | Accord Hybrid 2018-21 | All | Stock | 0mph | 3mph |
-| Honda | Civic Hatchback 2017-21 | Honda Sensing | Stock | 0mph | 12mph |
-| Honda | Civic Coupe 2016-18 | Honda Sensing | openpilot | 0mph | 12mph |
-| Honda | Civic Coupe 2019-20 | All | Stock | 0mph | 2mph2 |
-| Honda | Civic Sedan 2016-18 | Honda Sensing | openpilot | 0mph | 12mph |
-| Honda | Civic Sedan 2019-20 | All | Stock | 0mph | 2mph2 |
-| Honda | CR-V 2015-16 | Touring | openpilot | 25mph1 | 12mph |
-| Honda | CR-V 2017-21 | Honda Sensing | Stock | 0mph | 12mph |
-| Honda | CR-V Hybrid 2017-2019 | Honda Sensing | Stock | 0mph | 12mph |
-| Honda | e 2020 | All | Stock | 0mph | 3mph |
-| Honda | Fit 2018-19 | Honda Sensing | openpilot | 25mph1 | 12mph |
-| Honda | Freed 2020 | Honda Sensing | openpilot | 25mph1 | 12mph |
-| Honda | HR-V 2019-20 | Honda Sensing | openpilot | 25mph1 | 12mph |
-| Honda | Insight 2019-21 | All | Stock | 0mph | 3mph |
-| Honda | Inspire 2018 | All | Stock | 0mph | 3mph |
-| Honda | Odyssey 2018-20 | Honda Sensing | openpilot | 25mph1 | 0mph |
-| Honda | Passport 2019-21 | All | openpilot | 25mph1 | 12mph |
-| Honda | Pilot 2016-21 | Honda Sensing | openpilot | 25mph1 | 12mph |
-| Honda | Ridgeline 2017-21 | Honda Sensing | openpilot | 25mph1 | 12mph |
-| Hyundai | Palisade 2020-21 | All | Stock | 0mph | 0mph |
-| Hyundai | Sonata 2020-22 | All | Stock | 0mph | 0mph |
-| Lexus | CT Hybrid 2017-18 | LSS | Stock3| 0mph | 0mph |
-| Lexus | ES 2019-21 | All | openpilot | 0mph | 0mph |
-| Lexus | ES Hybrid 2017-18 | LSS | Stock3| 0mph | 0mph |
-| Lexus | ES Hybrid 2019-21 | All | openpilot | 0mph | 0mph |
-| Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph |
-| Lexus | NX 2018-2019 | All | Stock3| 0mph | 0mph |
-| Lexus | NX 2020 | All | openpilot | 0mph | 0mph |
-| Lexus | NX Hybrid 2018-19 | All | Stock3| 0mph | 0mph |
-| Lexus | RC 2020 | All | Stock | 22mph | 0mph |
-| Lexus | RX 2016-18 | All | Stock3| 0mph | 0mph |
-| Lexus | RX 2020-21 | All | openpilot | 0mph | 0mph |
-| Lexus | RX Hybrid 2016-19 | All | Stock3| 0mph | 0mph |
-| Lexus | RX Hybrid 2020-21 | All | openpilot | 0mph | 0mph |
-| Lexus | UX Hybrid 2019-21 | All | openpilot | 0mph | 0mph |
-| Toyota | Alphard 2019-20 | All | openpilot | 0mph | 0mph |
-| Toyota | Avalon 2016-21 | TSS-P | Stock3| 20mph1 | 0mph |
-| Toyota | Avalon 2022 | All | openpilot | 0mph | 0mph |
-| Toyota | Avalon Hybrid 2019-21 | TSS-P | Stock3| 20mph1 | 0mph |
-| Toyota | Camry 2018-20 | All | Stock | 0mph4 | 0mph |
-| Toyota | Camry 2021-22 | All | openpilot | 0mph4 | 0mph |
-| Toyota | Camry Hybrid 2018-20 | All | Stock | 0mph4 | 0mph |
-| Toyota | Camry Hybrid 2021-22 | All | openpilot | 0mph | 0mph |
-| Toyota | C-HR 2017-21 | All | Stock | 0mph | 0mph |
-| Toyota | C-HR Hybrid 2017-19 | All | Stock | 0mph | 0mph |
-| Toyota | Corolla 2017-19 | All | Stock3| 20mph1 | 0mph |
-| Toyota | Corolla 2020-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Corolla Hatchback 2019-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Corolla Hybrid 2020-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Highlander 2017-19 | All | Stock3| 0mph | 0mph |
-| Toyota | Highlander 2020-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Highlander Hybrid 2017-19 | All | Stock3| 0mph | 0mph |
-| Toyota | Highlander Hybrid 2020-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Mirai 2021 | All | openpilot | 0mph | 0mph |
-| Toyota | Prius 2016-20 | TSS-P | Stock3| 0mph | 0mph |
-| Toyota | Prius 2021-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Prius v 2017 | TSS-P | Stock3| 20mph1 | 0mph |
-| Toyota | Prius Prime 2017-20 | All | Stock3| 0mph | 0mph |
-| Toyota | Prius Prime 2021-22 | All | openpilot | 0mph | 0mph |
-| Toyota | Rav4 2016-18 | TSS-P | Stock3| 20mph1 | 0mph |
-| Toyota | Rav4 2019-21 | All | openpilot | 0mph | 0mph |
-| Toyota | Rav4 Hybrid 2016-18 | TSS-P | Stock3| 0mph | 0mph |
-| Toyota | Rav4 Hybrid 2019-21 | All | openpilot | 0mph | 0mph |
-| Toyota | Sienna 2018-20 | All | Stock3| 0mph | 0mph |
-
-1[Comma Pedal](https://github.com/commaai/openpilot/wiki/comma-pedal) is used to provide stop-and-go capability to some of the openpilot-supported cars that don't currently support stop-and-go. ***NOTE: The Comma Pedal is not officially supported by [comma](https://comma.ai).***
-22019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-3When disconnecting the Driver Support Unit (DSU), openpilot ACC will replace stock ACC. ***NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).***
-428mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-
-## Community Maintained Cars and Features
-
-| Make | Model (US Market Reference) | Supported Package | ACC | No ACC accel below | No ALC below |
-| ----------| --------------------------------| ------------------| -----------------| -------------------| -------------|
-| Audi | A3 2014-19 | ACC + Lane Assist | Stock | 0mph | 0mph |
-| Audi | A3 Sportback e-tron 2017-18 | ACC + Lane Assist | Stock | 0mph | 0mph |
-| Audi | Q2 2018 | ACC + Lane Assist | Stock | 0mph | 0mph |
-| Audi | Q3 2020-21 | ACC + Lane Assist | Stock | 0mph | 0mph |
-| Audi | S3 2015 | ACC + Lane Assist | Stock | 0mph | 0mph |
-| Cadillac | Escalade ESV 20161 | ACC + LKAS | openpilot | 0mph | 7mph |
-| Chevrolet | Volt 2017-181 | Adaptive Cruise | openpilot | 0mph | 7mph |
-| Chrysler | Pacifica 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
-| Chrysler | Pacifica 2020 | Adaptive Cruise | Stock | 0mph | 39mph |
-| Chrysler | Pacifica Hybrid 2017-18 | Adaptive Cruise | Stock | 0mph | 9mph |
-| Chrysler | Pacifica Hybrid 2019-21 | Adaptive Cruise | Stock | 0mph | 39mph |
-| Genesis | G70 2018 | All | Stock | 0mph | 0mph |
-| Genesis | G70 2020 | All | Stock | 0mph | 0mph |
-| Genesis | G80 2018 | All | Stock | 0mph | 0mph |
-| Genesis | G90 2018 | All | Stock | 0mph | 0mph |
-| GMC | Acadia 20181 | Adaptive Cruise | openpilot | 0mph | 7mph |
-| Hyundai | Elantra 2017-19 | SCC + LKAS | Stock | 19mph | 34mph |
-| Hyundai | Elantra 2021-22 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Elantra Hybrid 2021 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Genesis 2015-16 | SCC + LKAS | Stock | 19mph | 37mph |
-| Hyundai | Ioniq Electric 2019 | SCC + LKAS | Stock | 0mph | 32mph |
-| Hyundai | Ioniq Electric 2020 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Ioniq Hybrid 2017-19 | SCC + LKAS | Stock | 0mph | 32mph |
-| Hyundai | Ioniq Hybrid 2020-22 | SCC + LFA | Stock | 0mph | 0mph |
-| Hyundai | Ioniq PHEV 2020-21 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Kona 2020 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Kona EV 2018-19 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Kona Hybrid 2020 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Santa Fe 2019-20 | All | Stock | 0mph | 0mph |
-| Hyundai | Santa Fe 2021-22 | All | Stock | 0mph | 0mph |
-| Hyundai | Santa Fe Hybrid 2022 | All | Stock | 0mph | 0mph |
-| Hyundai | Santa Fe Plug-in Hybrid 2022 | All | Stock | 0mph | 0mph |
-| Hyundai | Sonata 2018-2019 | SCC + LKAS | Stock | 0mph | 0mph |
-| Hyundai | Sonata Hybrid 2021-22 | All | Stock | 0mph | 0mph |
-| Hyundai | Veloster 2019-20 | SCC + LKAS | Stock | 5mph | 0mph |
-| Jeep | Grand Cherokee 2016-18 | Adaptive Cruise | Stock | 0mph | 9mph |
-| Jeep | Grand Cherokee 2019-20 | Adaptive Cruise | Stock | 0mph | 39mph |
-| Kia | Ceed 2019 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Forte 2018-21 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | K5 2021-22 | SCC + LFA | Stock | 0mph | 0mph |
-| Kia | Niro EV 2019-22 | All | Stock | 0mph | 0mph |
-| Kia | Niro Hybrid 2021 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Niro PHEV 2019 | SCC + LKAS | Stock | 10mph | 32mph |
-| Kia | Optima 2017 | SCC + LKAS | Stock | 0mph | 32mph |
-| Kia | Optima 2019 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Seltos 2021 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Sorento 2018-19 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Stinger 2018 | SCC + LKAS | Stock | 0mph | 0mph |
-| Kia | Telluride 2020 | SCC + LKAS | Stock | 0mph | 0mph |
-| Mazda | CX-5 2022 | All | Stock | 0mph | 0mph |
-| Mazda | CX-9 2021 | All | Stock | 0mph | 28mph |
-| Nissan | Altima 2019-20 | ProPILOT | Stock | 0mph | 0mph |
-| Nissan | Leaf 2018-22 | ProPILOT | Stock | 0mph | 0mph |
-| Nissan | Rogue 2018-20 | ProPILOT | Stock | 0mph | 0mph |
-| Nissan | X-Trail 2017 | ProPILOT | Stock | 0mph | 0mph |
-| SEAT | Ateca 2018 | Driver Assistance | Stock | 0mph | 0mph |
-| SEAT | Leon 2014-2020 | Driver Assistance | Stock | 0mph | 0mph |
-| Subaru | Ascent 2019 | EyeSight | Stock | 0mph | 0mph |
-| Subaru | Crosstrek 2018-20 | EyeSight | Stock | 0mph | 0mph |
-| Subaru | Forester 2019-21 | EyeSight | Stock | 0mph | 0mph |
-| Subaru | Impreza 2017-19 | EyeSight | Stock | 0mph | 0mph |
-| Škoda | Kamiq 20212 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Karoq 2019 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Kodiaq 2018-19 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Octavia 2015, 2018-19 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Octavia RS 2016 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Scala 2020 | Driver Assistance | Stock | 0mph | 0mph |
-| Škoda | Superb 2015-18 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Arteon 2018, 20214 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Atlas 2018-19, 20224 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Caravelle 20204 | Driver Assistance | Stock | 0mph | 32mph |
-| Volkswagen| California 20214 | Driver Assistance | Stock | 0mph | 32mph |
-| Volkswagen| e-Golf 2014, 2019-20 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf 2015-20 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf Alltrack 2017-18 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf GTE 2016 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf GTI 2018-20 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf R 2016-19 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf SportsVan 2016 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Golf SportWagen 2015 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Jetta 2018-20 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Jetta GLI 2021 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Passat 2016-183 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Polo 2020 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| T-Cross 20214 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| T-Roc 20214 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Taos 20224 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Tiguan 2020 | Driver Assistance | Stock | 0mph | 0mph |
-| Volkswagen| Touran 2017 | Driver Assistance | Stock | 0mph | 0mph |
-
-1Requires an [OBD-II car harness](https://comma.ai/shop/products/comma-car-harness) and [community built ASCM harness](https://github.com/commaai/openpilot/wiki/GM#hardware). ***NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).***
-2Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-3Not including the USA/China market Passat, which is based on the (currently) unsupported PQ35/NMS platform.
-4Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering,
-remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). For the newer design,
-in the interim, choose "VW J533 Development" from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.
-Community Maintained Cars and Features are not verified by comma to meet our [safety model](SAFETY.md). Be extra cautious using them.
-
-To promote a car from community maintained, it must meet a few requirements. We must own one from the brand, we must sell the harness for it, has full ISO26262 in both panda and openpilot, there must be a path forward for longitudinal control, it must have AEB still enabled, and it must support fingerprinting 2.0
+A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system.
+
+# 219 Supported Cars
+
+|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|
+|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
+|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[](##)|[](##)|Honda Nidec|
+|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[](##)|[](##)|OBD-II|
+|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|GM|
+|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|GM|
+|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|GM|
+|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[](##)|[](##)|OBD-II|
+|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|FCA|
+|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|FCA|
+|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[](##)|[](##)|FCA|
+|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|FCA|
+|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|FCA|
+|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None|
+|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai F|
+|Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai F|
+|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai C|
+|Genesis|GV70 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[](##)|[](##)|OBD-II|
+|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[](##)|[](##)|GM|
+|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[](##)|[](##)|Honda Bosch A|
+|Honda|Civic 2022|All|Stock|0 mph|0 mph|[](##)|[](##)|Honda Bosch B|
+|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[](##)|[](##)|Honda Bosch B|
+|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[](##)|[](##)|Honda Bosch A|
+|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|Honda Nidec|
+|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|Hyundai B|
+|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai K|
+|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Hyundai E|
+|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai K|
+|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|Hyundai J|
+|Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Hyundai E|
+|Hyundai|Ioniq 5 (with HDA II) 2022-23[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai Q|
+|Hyundai|Ioniq 5 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai K|
+|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Hyundai C|
+|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Hyundai C|
+|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[](##)|[](##)|Hyundai C|
+|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai B|
+|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai G|
+|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai O|
+|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai I|
+|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Hyundai|Santa Cruz 2021-22[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai D|
+|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Hyundai|Santa Fe Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai E|
+|Hyundai|Sonata 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai A|
+|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai A|
+|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Hyundai|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Hyundai|Tucson 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Hyundai|Tucson Hybrid 2022[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|Hyundai E|
+|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|FCA|
+|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|FCA|
+|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai E|
+|Kia|EV6 (with HDA II) 2022[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai P|
+|Kia|EV6 (without HDA II) 2022[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai L|
+|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai G|
+|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai A|
+|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai F|
+|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai C|
+|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai F|
+|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[](##)|[](##)|Hyundai C|
+|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[](##)|[](##)|Hyundai B|
+|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai G|
+|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai A|
+|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai C|
+|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai E|
+|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai A|
+|Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai N|
+|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Hyundai C|
+|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai K|
+|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Hyundai H|
+|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[](##)|[](##)|Mazda|
+|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|Mazda|
+|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Nissan B|
+|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Nissan A|
+|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Nissan A|
+|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|Nissan A|
+|Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[](##)|[](##)|Ram|
+|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[](##)|[](##)|Subaru B|
+|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[](##)|[](##)|Subaru B|
+|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[](##)|[](##)|Subaru A|
+|Škoda|Kamiq 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Camry 2021-22|All|openpilot|0 mph[6](#footnotes)|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[](##)|[](##)|Toyota|
+|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[](##)|[](##)|Toyota|
+|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[](##)|[](##)|J533|
+|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[](##)|[](##)|J533|
+|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Passat 2015-22[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533[10](#footnotes)|
+|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Tiguan 2019-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[](##)|[](##)|J533|
+
+
+1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+5Requires a red panda, additional harness box, additional OBD-C cable, USB-A to USB-A cable, and a USB-A to USB-C OTG dongle.
+6openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+7Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+8Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+9Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+10Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+## Community Maintained Cars
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
+
+# Don't see your car here?
+
+**openpilot can support many more cars than it currently does.** There are a few reasons your car may not be supported.
+If your car doesn't fit into any of the incompatibility criteria here, then there's a good chance it can be supported! We're adding support for new cars all the time. **We don't have a roadmap for car support**, and in fact, most car support comes from users like you!
+
+### Which cars are able to be supported?
+
+openpilot uses the existing steering, gas, and brake interfaces in your car. If your car lacks any one of these interfaces, openpilot will not be able to control the car. If your car has [ACC](https://en.wikipedia.org/wiki/Adaptive_cruise_control) and any form of [LKAS](https://en.wikipedia.org/wiki/Automated_Lane_Keeping_Systems)/[LCA](https://en.wikipedia.org/wiki/Lane_centering), then it almost certainly has these interfaces. These features generally started shipping on cars around 2016. Note that manufacturers will often make their own [marketing terms](https://en.wikipedia.org/wiki/Adaptive_cruise_control#Vehicle_models_supporting_adaptive_cruise_control) for these features, such as Hyundai's "Smart Cruise Control" branding of Adaptive Cruise Control.
+
+If your car has the following packages or features, then it's a good candidate for support.
+
+| Make | Required Package/Features |
+| ---- | ------------------------- |
+| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. |
+| Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. |
+| Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. |
+| Nissan | Any car with ProPILOT will likely work. |
+| Toyota & Lexus | Any car that has Toyota/Lexus Safety Sense with "Lane Departure Alert with Steering Assist (LDA w/SA)" and/or "Lane Tracing Assist (LTA)" will work. Note that LDA without Steering Assist will not work. These features come standard on most newer models. |
+| Hyundai, Kia, & Genesis | Any car with Smart Cruise Control (SCC) and Lane Following Assist (LFA) or Lane Keeping Assist (LKAS) will work. LKAS/LFA comes standard on most newer models. Any form of SCC will work, such as NSCC. |
+| Chrysler, Jeep, & Ram | Any car with LaneSense and Adaptive Cruise Control will likely work. These come standard on many newer models. |
+
+### FlexRay
+
+All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the cars in your computer can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars may one day be supported, but we have no immediate plans to support FlexRay.
+
+### Toyota Security
+
+openpilot does not yet support these Toyota models due to a new message authentication method.
+[Vote](https://comma.ai/shop/products/vote) if you'd like to see openpilot support on these models.
+
+* Toyota RAV4 Prime 2021+
+* Toyota Sienna 2021+
+* Toyota Venza 2021+
+* Toyota Sequoia 2023+
+* Toyota Tundra 2022+
+* Toyota Corolla Cross 2022+ (only US model)
+* Lexus NX 2022+
+* Toyota bZ4x 2023+
+* Subaru Solterra 2023+
diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md
index 97b72e39d3..ba6291c1e3 100644
--- a/docs/INTEGRATION.md
+++ b/docs/INTEGRATION.md
@@ -8,4 +8,4 @@ Additionally, on specific supported cars (see ACC column in [supported cars](CAR
* Stock ACC is replaced by openpilot ACC.
* openpilot FCW operates in addition to stock FCW.
-openpilot should preserve all other vehicle's stock features, including, but are not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning.
+openpilot should preserve all other vehicle's stock features, including, but not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning.
diff --git a/docs/Makefile b/docs/Makefile
index 02db7db2d3..d0aa841c4d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -32,7 +32,7 @@ clean:
@echo "Copying docs & config to build folder..."
cp -a "$(DOCSDIR)" "$(BUILDDIR)"
cd "$(OPENPILOT_ROOT)" && \
- find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.png" -o -name "*.jpg" \) \
+ find . -type f \( -name "*.md" -o -name "*.rst" -o -name "*.png" -o -name "*.jpg" -o -name "*.svg" \) \
-not -path "*/.*" \
-not -path "./build/*" \
-not -path "./docs/*" \
diff --git a/docs/SAFETY.md b/docs/SAFETY.md
index 23ee251af4..49f88df8c0 100644
--- a/docs/SAFETY.md
+++ b/docs/SAFETY.md
@@ -22,7 +22,7 @@ Following Hazard and Risk Analysis and FMEA, at a very high level, we have desig
ensuring two main safety requirements.
1. The driver must always be capable to immediately retake manual control of the vehicle,
- by stepping on either pedal or by pressing the cancel button.
+ by stepping on the brake pedal or by pressing the cancel button.
2. The vehicle must not alter its trajectory too quickly for the driver to safely
react. This means that while the system is engaged, the actuators are constrained
to operate within reasonable limits.
diff --git a/docs/assets/icon-star-empty.svg b/docs/assets/icon-star-empty.svg
new file mode 100644
index 0000000000..5d3c32d671
--- /dev/null
+++ b/docs/assets/icon-star-empty.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/docs/assets/icon-star-full.svg b/docs/assets/icon-star-full.svg
new file mode 100644
index 0000000000..294db2b7f2
--- /dev/null
+++ b/docs/assets/icon-star-full.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/docs/assets/icon-star-half.svg b/docs/assets/icon-star-half.svg
new file mode 100644
index 0000000000..ab905fddcb
--- /dev/null
+++ b/docs/assets/icon-star-half.svg
@@ -0,0 +1,66 @@
+
+
diff --git a/docs/c_docs.rst b/docs/c_docs.rst
index 6cf5f268c5..77be7e51d8 100644
--- a/docs/c_docs.rst
+++ b/docs/c_docs.rst
@@ -28,11 +28,9 @@ selfdrive
camerad
^^^^^^^
.. autodoxygenindex::
- :project: selfdrive_camerad_cameras
+ :project: system_camerad_cameras
.. autodoxygenindex::
- :project: selfdrive_camerad_transforms
-.. autodoxygenindex::
- :project: selfdrive_camerad_imgproc
+ :project: system_camerad_imgproc
locationd
^^^^^^^^^
@@ -50,15 +48,11 @@ soundd
.. autodoxygenindex::
:project: selfdrive_ui_soundd
-navd
-""""
-.. autodoxygenindex::
- :project: selfdrive_ui_navd
replay
""""""
.. autodoxygenindex::
- :project: selfdrive_ui_replay
+ :project: tools_replay
qt
""
@@ -70,7 +64,7 @@ qt
proclogd
^^^^^^^^
.. autodoxygenindex::
- :project: selfdrive_proclogd
+ :project: system_proclogd
modeld
^^^^^^
@@ -78,15 +72,13 @@ modeld
:project: selfdrive_modeld_transforms
.. autodoxygenindex::
:project: selfdrive_modeld_models
-.. autodoxygenindex::
- :project: selfdrive_modeld_thneed
.. autodoxygenindex::
:project: selfdrive_modeld_runners
common
^^^^^^
.. autodoxygenindex::
- :project: selfdrive_common
+ :project: common
sensorsd
^^^^^^^^
diff --git a/docs/conf.py b/docs/conf.py
index c11d17455d..fea921de1f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,10 +14,12 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
-from os.path import exists
import sys
-from selfdrive.version import get_version
+from os.path import exists
+
from common.basedir import BASEDIR
+from system.version import get_version
+
sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('..'))
diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile
index 124feb1bfc..0d5883f566 100644
--- a/docs/docker/Dockerfile
+++ b/docs/docker/Dockerfile
@@ -4,16 +4,11 @@ ENV PYTHONUNBUFFERED 1
ENV OPENPILOT_PATH /home/batman/openpilot/
ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH}
+ENV POETRY_VIRUALENVS_CREATE false
RUN mkdir -p ${OPENPILOT_PATH}
WORKDIR ${OPENPILOT_PATH}
-COPY Pipfile Pipfile.lock $OPENPILOT_PATH
-RUN pip install --no-cache-dir pipenv==2021.5.29 pip==21.3.1 && \
- pipenv install --system --deploy --dev --clear && \
- pip uninstall -y pipenv
-
-
COPY SConstruct ${OPENPILOT_PATH}
COPY ./pyextra ${OPENPILOT_PATH}/pyextra
@@ -30,6 +25,7 @@ COPY ./opendbc ${OPENPILOT_PATH}/opendbc
COPY ./cereal ${OPENPILOT_PATH}/cereal
COPY ./panda ${OPENPILOT_PATH}/panda
COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive
+COPY ./system ${OPENPILOT_PATH}/system
COPY ./*.md ${OPENPILOT_PATH}/
RUN scons -j$(nproc)
diff --git a/installer/updater/updater b/installer/updater/updater
deleted file mode 100755
index 8bf40708a6..0000000000
--- a/installer/updater/updater
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/bash
-echo "this is a compatability shim for old updaters"
diff --git a/laika_repo b/laika_repo
index dec99a0f77..e1049cde0a 160000
--- a/laika_repo
+++ b/laika_repo
@@ -1 +1 @@
-Subproject commit dec99a0f77328f7a9f104020d98d7227345d1288
+Subproject commit e1049cde0a68f7d4a70b1ebd76befdc0e163ad55
diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh
index 7ef671c265..911774a4eb 100755
--- a/launch_chffrplus.sh
+++ b/launch_chffrplus.sh
@@ -8,110 +8,13 @@ source "$BASEDIR/launch_env.sh"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-function two_init {
-
- # set IO scheduler
- setprop sys.io.scheduler noop
- for f in /sys/block/*/queue/scheduler; do
- echo noop > $f
- done
-
- # *** shield cores 2-3 ***
-
- # TODO: should we enable this?
- # offline cores 2-3 to force recurring timers onto the other cores
- #echo 0 > /sys/devices/system/cpu/cpu2/online
- #echo 0 > /sys/devices/system/cpu/cpu3/online
- #echo 1 > /sys/devices/system/cpu/cpu2/online
- #echo 1 > /sys/devices/system/cpu/cpu3/online
-
- # android gets two cores
- echo 0-1 > /dev/cpuset/background/cpus
- echo 0-1 > /dev/cpuset/system-background/cpus
- echo 0-1 > /dev/cpuset/foreground/cpus
- echo 0-1 > /dev/cpuset/foreground/boost/cpus
- echo 0-1 > /dev/cpuset/android/cpus
-
- # openpilot gets all the cores
- echo 0-3 > /dev/cpuset/app/cpus
-
- # mask off 2-3 from RPS and XPS - Receive/Transmit Packet Steering
- echo 3 | tee /sys/class/net/*/queues/*/rps_cpus
- echo 3 | tee /sys/class/net/*/queues/*/xps_cpus
-
- # *** set up governors ***
-
- # +50mW offroad, +500mW onroad for 30% more RAM bandwidth
- echo "performance" > /sys/class/devfreq/soc:qcom,cpubw/governor
- echo 1056000 > /sys/class/devfreq/soc:qcom,m4m/max_freq
- echo "performance" > /sys/class/devfreq/soc:qcom,m4m/governor
-
- # unclear if these help, but they don't seem to hurt
- echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu0/governor
- echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu2/governor
-
- # GPU
- echo "performance" > /sys/class/devfreq/b00000.qcom,kgsl-3d0/governor
-
- # /sys/class/devfreq/soc:qcom,mincpubw is the only one left at "powersave"
- # it seems to gain nothing but a wasted 500mW
-
- # *** set up IRQ affinities ***
-
- # Collect RIL and other possibly long-running I/O interrupts onto CPU 1
- echo 1 > /proc/irq/78/smp_affinity_list # qcom,smd-modem (LTE radio)
- echo 1 > /proc/irq/33/smp_affinity_list # ufshcd (flash storage)
- echo 1 > /proc/irq/35/smp_affinity_list # wifi (wlan_pci)
- echo 1 > /proc/irq/6/smp_affinity_list # MDSS
-
- # USB traffic needs realtime handling on cpu 3
- [ -d "/proc/irq/733" ] && echo 3 > /proc/irq/733/smp_affinity_list
-
- # GPU and camera get cpu 2
- CAM_IRQS="177 178 179 180 181 182 183 184 185 186 192"
- for irq in $CAM_IRQS; do
- echo 2 > /proc/irq/$irq/smp_affinity_list
- done
- echo 2 > /proc/irq/193/smp_affinity_list # GPU
-
- # give GPU threads RT priority
- for pid in $(pgrep "kgsl"); do
- chrt -f -p 52 $pid
- done
-
- # the flippening!
- LD_LIBRARY_PATH="" content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:1
-
- # disable bluetooth
- service call bluetooth_manager 8
-
- # wifi scan
- wpa_cli IFNAME=wlan0 SCAN
-
- # Check for NEOS update
- if [ $(< /VERSION) != "$REQUIRED_NEOS_VERSION" ]; then
- echo "Installing NEOS update"
- NEOS_PY="$DIR/selfdrive/hardware/eon/neos.py"
- MANIFEST="$DIR/selfdrive/hardware/eon/neos.json"
- $NEOS_PY --swap-if-ready $MANIFEST
- $DIR/selfdrive/hardware/eon/updater $NEOS_PY $MANIFEST
- fi
-}
-
-function tici_init {
+function agnos_init {
# wait longer for weston to come up
if [ -f "$BASEDIR/prebuilt" ]; then
sleep 3
fi
- # setup governors
- sudo su -c 'echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu0/governor'
- sudo su -c 'echo "performance" > /sys/class/devfreq/soc:qcom,memlat-cpu4/governor'
-
# TODO: move this to agnos
- # network manager config
- nmcli connection modify --temporary lte gsm.auto-config yes
- nmcli connection modify --temporary lte gsm.home-only yes
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta
# set success flag for current boot slot
@@ -119,12 +22,12 @@ function tici_init {
# Check if AGNOS update is required
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
- AGNOS_PY="$DIR/selfdrive/hardware/tici/agnos.py"
- MANIFEST="$DIR/selfdrive/hardware/tici/agnos.json"
+ AGNOS_PY="$DIR/system/hardware/tici/agnos.py"
+ MANIFEST="$DIR/system/hardware/tici/agnos.json"
if $AGNOS_PY --verify $MANIFEST; then
sudo reboot
fi
- $DIR/selfdrive/hardware/tici/updater $AGNOS_PY $MANIFEST
+ $DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST
fi
}
@@ -159,7 +62,6 @@ function launch {
cd $BASEDIR
echo "Restarting launch script ${LAUNCHER_LOCATION}"
- unset REQUIRED_NEOS_VERSION
unset AGNOS_VERSION
exec "${LAUNCHER_LOCATION}"
else
@@ -175,11 +77,7 @@ function launch {
export PYTHONPATH="$PWD:$PWD/pyextra"
# hardware specific init
- if [ -f /EON ]; then
- two_init
- elif [ -f /TICI ]; then
- tici_init
- fi
+ agnos_init
# write tmux scrollback to a file
tmux capture-pane -pq -S-1000 > /tmp/launch_log
diff --git a/launch_env.sh b/launch_env.sh
index cd0c27f643..3059ec268e 100755
--- a/launch_env.sh
+++ b/launch_env.sh
@@ -6,12 +6,8 @@ export NUMEXPR_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
-if [ -z "$REQUIRED_NEOS_VERSION" ]; then
- export REQUIRED_NEOS_VERSION="19.1"
-fi
-
if [ -z "$AGNOS_VERSION" ]; then
- export AGNOS_VERSION="4"
+ export AGNOS_VERSION="6.2"
fi
if [ -z "$PASSIVE" ]; then
diff --git a/models/README.md b/models/README.md
deleted file mode 100644
index 9f1bfdba02..0000000000
--- a/models/README.md
+++ /dev/null
@@ -1,109 +0,0 @@
-## Neural networks in openpilot
-To view the architecture of the ONNX networks, you can use [netron](https://netron.app/)
-
-## Supercombo
-### Supercombo input format (Full size: 393738 x float32)
-* **image stream**
- * Two consecutive images (256 * 512 * 3 in RGB) recorded at 20 Hz : 393216 = 2 * 6 * 128 * 256
- * Each 256 * 512 image is represented in YUV420 with 6 channels : 6 * 128 * 256
- * Channels 0,1,2,3 represent the full-res Y channel and are represented in numpy as Y[::2, ::2], Y[::2, 1::2], Y[1::2, ::2], and Y[1::2, 1::2]
- * Channel 4 represents the half-res U channel
- * Channel 5 represents the half-res V channel
-* **desire**
- * one-hot encoded vector to command model to execute certain actions, bit only needs to be sent for 1 frame : 8
-* **traffic convention**
- * one-hot encoded vector to tell model whether traffic is right-hand or left-hand traffic : 2
-* **recurrent state**
- * The recurrent state vector that is fed back into the GRU for temporal context : 512
-
-
-### Supercombo output format (Full size: 6472 x float32)
-* **plan**
- * 5 potential desired plan predictions : 4955 = 5 * 991
- * predicted mean and standard deviation of the following values at 33 timesteps : 990 = 2 * 33 * 15
- * x,y,z position in current frame (meters)
- * x,y,z velocity in local frame (meters/s)
- * x,y,z acceleration local frame (meters/(s*s))
- * roll, pitch , yaw in current frame (radians)
- * roll, pitch , yaw rates in local frame (radians/s)
- * probability[^1] of this plan hypothesis being the most likely: 1
-* **lanelines**
- * 4 lanelines (outer left, left, right, and outer right): 528 = 4 * 132
- * predicted mean and standard deviation for the following values at 33 x positions : 132 = 2 * 33 * 2
- * y position in current frame (meters)
- * z position in current frame (meters)
-* **laneline probabilties**
- * 2 probabilities[^1] that each of the 4 lanelines exists : 8 = 4 * 2
- * deprecated probability
- * used probability
-* **road-edges**
- * 2 road-edges (left and right): 264 = 2 * 132
- * predicted mean and standard deviation for the following values at 33 x positions : 132 = 2 * 33 * 2
- * y position in current frame (meters)
- * z position in current frame (meters)
-* **leads**
- * 2 hypotheses for potential lead cars : 102 = 2 * 51
- * predicted mean and stadard deviation for the following values at 0,2,4,6,8,10s : 48 = 2 * 6 * 4
- * x position of lead in current frame (meters)
- * y position of lead in current frame (meters)
- * speed of lead (meters/s)
- * acceleration of lead(meters/(s*s))
- * probabilities[^1] this hypothesis is the most likely hypothesis at 0s, 2s or 4s from now : 3
-* **lead probabilities**
- * probability[^1] that there is a lead car at 0s, 2s, 4s from now : 3 = 1 * 3
-* **desire state**
- * probability[^1] that the model thinks it is executing each of the 8 potential desire actions : 8
-* **meta** [^2]
- * Various metadata about the scene : 80 = 1 + 35 + 12 + 32
- * Probability[^1] that openpilot is engaged : 1
- * Probabilities[^1] of various things happening between now and 2,4,6,8,10s : 35 = 5 * 7
- * Disengage of openpilot with gas pedal
- * Disengage of openpilot with brake pedal
- * Override of openpilot steering
- * 3m/(s*s) of deceleration
- * 4m/(s*s) of deceleration
- * 5m/(s*s) of deceleration
- * Probabilities[^1] of left or right blinker being active at 0,2,4,6,8,10s : 12 = 6 * 2
- * Probabilities[^1] that each of the 8 desires is being executed at 0,2,4,6s : 32 = 4 * 8
-
-* **pose** [^2]
- * predicted mean and standard deviation of current translation and rotation rates : 12 = 2 * 6
- * x,y,z velocity in current frame (meters/s)
- * roll, pitch , yaw rates in current frame (radians/s)
-* **recurrent state**
- * The recurrent state vector that is fed back into the GRU for temporal context : 512
-
-[^1]: All probabilities are in logits, so you need to apply sigmoid or softmax functions to get actual probabilities
-[^2]: These outputs come directly from the vision blocks, they do not have access to temporal state or the desire input
-
-
-## Driver Monitoring Model
-* .onnx model can be run with onnx runtimes
-* .dlc file is a pre-quantized model and only runs on qualcomm DSPs
-
-### input format
-* single image (640 * 320 * 3 in RGB):
- * full input size is 6 * 640/2 * 320/2 = 307200
- * represented in YUV420 with 6 channels:
- * Channels 0,1,2,3 represent the full-res Y channel and are represented in numpy as Y[::2, ::2], Y[::2, 1::2], Y[1::2, ::2], and Y[1::2, 1::2]
- * Channel 4 represents the half-res U channel
- * Channel 5 represents the half-res V channel
- * normalized, ranging from -1.0 to 1.0
-
-### output format
-* 39 x float32 outputs ([parsing example](https://github.com/commaai/openpilot/blob/master/selfdrive/modeld/models/dmonitoring.cc#L165))
- * face pose: 12 = 6 + 6
- * face orientation [pitch, yaw, roll] in camera frame: 3
- * face position [dx, dy] relative to image center: 2
- * normalized face size: 1
- * standard deviations for above outputs: 6
- * face visible probability: 1
- * eyes: 20 = (8 + 1) + (8 + 1) + 1 + 1
- * eye position and size, and their standard deviations: 8
- * eye visible probability: 1
- * eye closed probability: 1
- * wearing sunglasses probability: 1
- * poor camera vision probability: 1
- * face partially out-of-frame probability: 1
- * (deprecated) distracted probabilities: 2
- * face covered probability: 1
diff --git a/models/big_supercombo.dlc b/models/big_supercombo.dlc
deleted file mode 100644
index a27e3d1180..0000000000
--- a/models/big_supercombo.dlc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ba3fe3e61853cc1434e3e220f40c8e9d1f1b9bab8458196ba3bea6a10b82c6ed
-size 72718099
diff --git a/models/big_supercombo.onnx b/models/big_supercombo.onnx
deleted file mode 100644
index 3039035fbc..0000000000
--- a/models/big_supercombo.onnx
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bda57c1a66944f5a633ecd739a24d62702c717a234f2fdcc499dfa1d61c3c19e
-size 73147489
diff --git a/models/dmonitoring_model.current b/models/dmonitoring_model.current
deleted file mode 100644
index b9e0c24751..0000000000
--- a/models/dmonitoring_model.current
+++ /dev/null
@@ -1,2 +0,0 @@
-4e19be90-bd5b-485d-b79a-2462f7f1b49e
-08f7ec37b78228cd1cb750b6ddb9c6ba1769e911
\ No newline at end of file
diff --git a/models/dmonitoring_model.onnx b/models/dmonitoring_model.onnx
deleted file mode 100644
index 9fe743334d..0000000000
--- a/models/dmonitoring_model.onnx
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:895ee32e2a1c77496e015270db475eef65034b25331f2859bac0ccf702f64298
-size 3294407
diff --git a/models/dmonitoring_model_q.dlc b/models/dmonitoring_model_q.dlc
deleted file mode 100644
index 1e34e0de3f..0000000000
--- a/models/dmonitoring_model_q.dlc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:6e4ac870984d11cd8e86cda4a63e3321fde837bacf4a055a27b7c8ba34facfe2
-size 916079
diff --git a/models/supercombo.dlc b/models/supercombo.dlc
deleted file mode 100644
index 2ebf4fa828..0000000000
--- a/models/supercombo.dlc
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:209e9544e456dbc2a7d60490da65154e129bc84830909d8d931f97b3df93949b
-size 56684955
diff --git a/models/supercombo.onnx b/models/supercombo.onnx
deleted file mode 100644
index 17d233dad7..0000000000
--- a/models/supercombo.onnx
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2365bae967cce21ce68707c30bf2981bb7081ee5c3e6a3dff793e660f23ff622
-size 57554657
diff --git a/mypy.ini b/mypy.ini
index 66b82bd5df..39b1b007a7 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,4 +1,16 @@
[mypy]
python_version = 3.8
+plugins = numpy.typing.mypy_plugin
+files = body, common, docs, scripts, selfdrive, site_scons, system, tools
+exclude = ^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)
+
+; third-party packages
ignore_missing_imports = True
+; helpful warnings
+warn_redundant_casts = True
+warn_unreachable = True
+warn_unused_ignores = True
+
+; restrict dynamic typing
+warn_return_any = True
diff --git a/opendbc b/opendbc
index 46a942d679..94fff4782b 160000
--- a/opendbc
+++ b/opendbc
@@ -1 +1 @@
-Subproject commit 46a942d6790531cf5b94b14266140e43afcfda3e
+Subproject commit 94fff4782be263efad10032a612b3c96a120c0b7
diff --git a/panda b/panda
index cb88a1756b..4edd1a6021 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit cb88a1756ba2b8b06e06102691c75f8aa87f687b
+Subproject commit 4edd1a602131ec2f09a604a4bd28e7d00e334458
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000000..39fb78cf2a
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,8030 @@
+[[package]]
+name = "adal"
+version = "1.2.7"
+description = "Note: This library is already replaced by MSAL Python, available here: https://pypi.org/project/msal/ .ADAL Python remains available here as a legacy. The ADAL for Python library makes it easy for python application to authenticate to Azure Active Directory (AAD) in order to access AAD protected web resources."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cryptography = ">=1.1.0"
+PyJWT = ">=1.0.0,<3"
+python-dateutil = ">=2.1.0,<3"
+requests = ">=2.0.0,<3"
+
+[[package]]
+name = "aenum"
+version = "3.1.11"
+description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "aiohttp"
+version = "3.8.3"
+description = "Async http client/server framework (asyncio)"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+aiosignal = ">=1.1.2"
+async-timeout = ">=4.0.0a3,<5.0"
+attrs = ">=17.3.0"
+charset-normalizer = ">=2.0,<3.0"
+frozenlist = ">=1.1.1"
+multidict = ">=4.5,<7.0"
+yarl = ">=1.0,<2.0"
+
+[package.extras]
+speedups = ["Brotli", "aiodns", "cchardet"]
+
+[[package]]
+name = "aiosignal"
+version = "1.2.0"
+description = "aiosignal: a list of registered asynchronous callbacks"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+frozenlist = ">=1.1.0"
+
+[[package]]
+name = "alabaster"
+version = "0.7.12"
+description = "A configurable sidebar-enabled Sphinx theme"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "albumentations"
+version = "1.3.0"
+description = "Fast image augmentation library and easy to use wrapper around other libraries"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+numpy = ">=1.11.1"
+opencv-python-headless = ">=4.1.1"
+PyYAML = "*"
+qudida = ">=0.0.4"
+scikit-image = ">=0.16.1"
+scipy = "*"
+
+[package.extras]
+develop = ["imgaug (>=0.4.0)", "pytest"]
+imgaug = ["imgaug (>=0.4.0)"]
+tests = ["pytest"]
+
+[[package]]
+name = "anyio"
+version = "3.6.2"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
+trio = ["trio (>=0.16,<0.22)"]
+
+[[package]]
+name = "apex"
+version = "0.1"
+description = "PyTorch Extensions written by NVIDIA"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.source]
+type = "url"
+url = "https://github.com/commaai/apex/releases/download/pytorch1.10.0%2Bcu11.1/apex-0.1-cp38-cp38-linux_x86_64.whl"
+
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "applicationinsights"
+version = "0.11.10"
+description = "This project extends the Application Insights API surface to support Python."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "appnope"
+version = "0.1.3"
+description = "Disable App Nap on macOS >= 10.9"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "argcomplete"
+version = "1.12.3"
+description = "Bash tab completion for argparse"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["coverage", "flake8", "pexpect", "wheel"]
+
+[[package]]
+name = "argon2-cffi"
+version = "21.3.0"
+description = "The secure Argon2 password hashing algorithm."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+argon2-cffi-bindings = "*"
+
+[package.extras]
+dev = ["cogapp", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pytest", "sphinx", "sphinx-notfound-page", "tomli"]
+docs = ["furo", "sphinx", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "21.2.0"
+description = "Low-level CFFI bindings for Argon2"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.0.1"
+
+[package.extras]
+dev = ["cogapp", "pre-commit", "pytest", "wheel"]
+tests = ["pytest"]
+
+[[package]]
+name = "astroid"
+version = "2.12.12"
+description = "An abstract syntax tree for Python with inference support."
+category = "main"
+optional = false
+python-versions = ">=3.7.2"
+
+[package.dependencies]
+lazy-object-proxy = ">=1.4.0"
+typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
+wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "asttokens"
+version = "2.0.8"
+description = "Annotate AST trees with source code positions"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[package.extras]
+test = ["astroid (<=2.5.3)", "pytest"]
+
+[[package]]
+name = "async-timeout"
+version = "4.0.2"
+description = "Timeout context manager for asyncio programs"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "atomicwrites"
+version = "1.4.1"
+description = "Atomic file writes."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "22.1.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
+docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
+tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
+tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "av"
+version = "9.2.0"
+description = "Pythonic bindings for FFmpeg's libraries."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "azure-cli-core"
+version = "2.41.0"
+description = "Microsoft Azure Command-Line Tools Core Module"
+category = "dev"
+optional = false
+python-versions = ">=3.7.0"
+
+[package.dependencies]
+argcomplete = ">=1.8,<2.0"
+azure-cli-telemetry = ">=1.0.0,<1.1.0"
+azure-mgmt-core = ">=1.2.0,<2"
+cryptography = "*"
+humanfriendly = ">=10.0,<11.0"
+jmespath = "*"
+knack = ">=0.10.0,<0.11.0"
+msal = {version = "1.20.0b1", extras = ["broker"]}
+msal-extensions = ">=1.0.0,<1.1.0"
+msrestazure = ">=0.6.4,<0.7.0"
+packaging = ">=20.9,<22.0"
+paramiko = ">=2.0.8,<3.0.0"
+pkginfo = ">=1.5.0.1"
+psutil = {version = ">=5.9,<6.0", markers = "sys_platform != \"cygwin\""}
+PyJWT = ">=2.1.0"
+pyopenssl = ">=17.1.0"
+requests = {version = "*", extras = ["socks"]}
+
+[[package]]
+name = "azure-cli-telemetry"
+version = "1.0.8"
+description = "Microsoft Azure CLI Telemetry Package"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+applicationinsights = ">=0.11.1,<0.12"
+portalocker = ">=1.6,<3"
+
+[[package]]
+name = "azure-common"
+version = "1.1.28"
+description = "Microsoft Azure Client Library for Python (Common)"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "azure-core"
+version = "1.26.0"
+description = "Microsoft Azure Core Library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+requests = ">=2.18.4"
+six = ">=1.11.0"
+typing-extensions = ">=4.0.1"
+
+[package.extras]
+aio = ["aiohttp (>=3.0)"]
+
+[[package]]
+name = "azure-mgmt-core"
+version = "1.3.2"
+description = "Microsoft Azure Management Core Library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+azure-core = ">=1.24.0,<2.0.0"
+
+[[package]]
+name = "azure-nspkg"
+version = "3.0.2"
+description = "Microsoft Azure Namespace Package [Internal]"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "azure-storage-blob"
+version = "2.1.0"
+description = "Microsoft Azure Storage Blob Client Library for Python"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+azure-common = ">=1.1.5"
+azure-storage-common = ">=2.1,<3.0"
+
+[[package]]
+name = "azure-storage-common"
+version = "2.1.0"
+description = "Microsoft Azure Storage Common Client Library for Python"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+azure-common = ">=1.1.5"
+cryptography = "*"
+python-dateutil = "*"
+requests = "*"
+
+[[package]]
+name = "azure-storage-nspkg"
+version = "3.1.0"
+description = "Microsoft Azure Storage Namespace Package [Internal]"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+azure-nspkg = ">=2.0.0"
+
+[[package]]
+name = "babel"
+version = "2.10.3"
+description = "Internationalization utilities"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pytz = ">=2015.7"
+
+[[package]]
+name = "backcall"
+version = "0.2.0"
+description = "Specifications for callback functions passed in to an API"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "bcrypt"
+version = "4.0.1"
+description = "Modern password hashing for your software and your servers"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+tests = ["pytest (>=3.2.1,!=3.3.0)"]
+typecheck = ["mypy"]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.11.1"
+description = "Screen-scraping library"
+category = "dev"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "bidict"
+version = "0.22.0"
+description = "The bidirectional mapping library for Python."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "bleach"
+version = "5.0.1"
+description = "An easy safelist-based HTML-sanitizing tool."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+six = ">=1.9.0"
+webencodings = "*"
+
+[package.extras]
+css = ["tinycss2 (>=1.1.0,<1.2)"]
+dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"]
+
+[[package]]
+name = "blosc"
+version = "1.9.2"
+description = "Blosc data compressor"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "breathe"
+version = "4.34.0"
+description = "Sphinx Doxygen renderer"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+docutils = ">=0.12"
+Sphinx = ">=4.0,<5.0.0 || >5.0.0,<6"
+
+[[package]]
+name = "cachecontrol"
+version = "0.12.11"
+description = "httplib2 caching for requests"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""}
+msgpack = ">=0.5.2"
+requests = "*"
+
+[package.extras]
+filecache = ["lockfile (>=0.9)"]
+redis = ["redis (>=2.10.5)"]
+
+[[package]]
+name = "cachy"
+version = "0.3.0"
+description = "Cachy provides a simple yet effective caching library."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+memcached = ["python-memcached (>=1.59,<2.0)"]
+msgpack = ["msgpack-python (>=0.5,<0.6)"]
+redis = ["redis (>=3.3.6,<4.0.0)"]
+
+[[package]]
+name = "carla"
+version = "0.9.13"
+description = "Python API for communicating with the CARLA server."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "casadi"
+version = "3.5.5"
+description = "CasADi -- framework for algorithmic differentiation and numeric optimization"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "certifi"
+version = "2022.9.24"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "cffi"
+version = "1.15.1"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "cfgv"
+version = "3.3.1"
+description = "Validate configuration and produce human readable error messages."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
+[[package]]
+name = "cleo"
+version = "1.0.0a5"
+description = "Cleo allows you to create beautiful and testable command-line interfaces."
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+crashtest = ">=0.3.1,<0.4.0"
+pylev = ">=1.3.0,<2.0.0"
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "cloudpickle"
+version = "2.2.0"
+description = "Extended pickling support for Python objects"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "colorama"
+version = "0.4.5"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coloredlogs"
+version = "15.0.1"
+description = "Colored terminal output for Python's logging module"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+humanfriendly = ">=9.1"
+
+[package.extras]
+cron = ["capturer (>=2.4)"]
+
+[[package]]
+name = "configargparse"
+version = "1.5.3"
+description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+test = ["PyYAML", "mock", "pytest"]
+yaml = ["PyYAML"]
+
+[[package]]
+name = "contourpy"
+version = "1.0.5"
+description = "Python library for calculating contours of 2D quadrilateral grids"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+numpy = ">=1.16"
+
+[package.extras]
+bokeh = ["bokeh", "selenium"]
+docs = ["docutils (<0.18)", "sphinx", "sphinx-rtd-theme"]
+test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"]
+test-minimal = ["pytest"]
+test-no-codebase = ["Pillow", "matplotlib", "pytest"]
+
+[[package]]
+name = "control"
+version = "0.9.2"
+description = "Python Control Systems Library"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+matplotlib = "*"
+numpy = "*"
+scipy = "*"
+
+[package.extras]
+slycot = ["slycot (>=0.4.0)"]
+test = ["pytest", "pytest-timeout"]
+
+[[package]]
+name = "coverage"
+version = "6.5.0"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "crashtest"
+version = "0.3.1"
+description = "Manage Python errors with ease"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "crcmod"
+version = "1.7"
+description = "CRC Generator"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "cryptography"
+version = "37.0.4"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.12"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
+docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools_rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
+
+[[package]]
+name = "cupy-cuda113"
+version = "10.6.0"
+description = "CuPy: NumPy & SciPy for GPU"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+fastrlock = ">=0.5"
+numpy = ">=1.18,<1.25"
+
+[package.extras]
+all = ["Cython (>=0.29.22,<3)", "optuna (>=2.0)", "scipy (>=1.4,<1.11)"]
+stylecheck = ["autopep8 (==1.5.5)", "flake8 (==3.8.4)", "mypy (==0.950)", "pbr (==5.5.1)", "pycodestyle (==2.6.0)", "types-setuptools (==57.4.14)"]
+test = ["hypothesis (>=6.37.2)", "pytest (>=6.2)"]
+
+[[package]]
+name = "cycler"
+version = "0.11.0"
+description = "Composable style cycles"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "cython"
+version = "0.29.32"
+description = "The Cython compiler for writing C extensions for the Python language."
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "datadog"
+version = "0.44.0"
+description = "The Datadog Python library"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+
+[package.dependencies]
+requests = ">=2.6.0"
+
+[[package]]
+name = "debugpy"
+version = "1.6.3"
+description = "An implementation of the Debug Adapter Protocol for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "decorator"
+version = "5.1.1"
+description = "Decorators for Humans"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+description = "XML bomb protection for Python stdlib modules"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "deprecated"
+version = "1.2.13"
+description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+wrapt = ">=1.10,<2"
+
+[package.extras]
+dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"]
+
+[[package]]
+name = "dictdiffer"
+version = "0.9.0"
+description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+all = ["Sphinx (>=3)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "tox (>=3.7.0)"]
+docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"]
+numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"]
+tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"]
+
+[[package]]
+name = "dill"
+version = "0.3.5.1"
+description = "serialize all of python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
+
+[package.extras]
+graph = ["objgraph (>=1.7.2)"]
+
+[[package]]
+name = "distlib"
+version = "0.3.6"
+description = "Distribution utilities"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "docutils"
+version = "0.17.1"
+description = "Docutils -- Python Documentation Utilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "dotmap"
+version = "1.3.30"
+description = "ordered, dynamically-expandable dot-access dictionary"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "dulwich"
+version = "0.20.46"
+description = "Python Git Library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+urllib3 = ">=1.25"
+
+[package.extras]
+fastimport = ["fastimport"]
+https = ["urllib3 (>=1.24.1)"]
+paramiko = ["paramiko"]
+pgp = ["gpg"]
+
+[[package]]
+name = "efficientnet-pytorch"
+version = "0.6.3"
+description = "EfficientNet implemented in PyTorch."
+category = "dev"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.dependencies]
+torch = "*"
+
+[[package]]
+name = "einops"
+version = "0.5.0"
+description = "A new flavour of deep learning operations"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "elastic-transport"
+version = "8.4.0"
+description = "Transport classes and utilities shared among Python Elastic client libraries"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+certifi = "*"
+urllib3 = ">=1.26.2,<2"
+
+[package.extras]
+develop = ["aiohttp", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "trustme"]
+
+[[package]]
+name = "elasticsearch"
+version = "8.4.3"
+description = "Python client for Elasticsearch"
+category = "dev"
+optional = false
+python-versions = ">=3.6, <4"
+
+[package.dependencies]
+elastic-transport = ">=8,<9"
+
+[package.extras]
+async = ["aiohttp (>=3,<4)"]
+requests = ["requests (>=2.4.0,<3.0.0)"]
+
+[[package]]
+name = "entrypoints"
+version = "0.4"
+description = "Discover and load entry points from installed packages."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "execnet"
+version = "1.9.0"
+description = "execnet: rapid multi-Python deployment"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+testing = ["pre-commit"]
+
+[[package]]
+name = "executing"
+version = "1.1.1"
+description = "Get the currently executing AST node of a frame, and other information"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+tests = ["asttokens", "littleutils", "pytest", "rich"]
+
+[[package]]
+name = "fastcluster"
+version = "1.2.6"
+description = "Fast hierarchical clustering routines for R and Python."
+category = "dev"
+optional = false
+python-versions = ">=3"
+
+[package.dependencies]
+numpy = ">=1.9"
+
+[package.extras]
+test = ["scipy (>=1.6.3)"]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.16.2"
+description = "Fastest Python implementation of JSON schema"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"]
+
+[[package]]
+name = "fastrlock"
+version = "0.8"
+description = "Fast, re-entrant optimistic lock implemented in Cython"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "filelock"
+version = "3.8.0"
+description = "A platform independent file lock."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
+testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
+
+[[package]]
+name = "flake8"
+version = "4.0.1"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.8.0,<2.9.0"
+pyflakes = ">=2.4.0,<2.5.0"
+
+[[package]]
+name = "flask"
+version = "2.2.2"
+description = "A simple framework for building complex web applications."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+click = ">=8.0"
+importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
+itsdangerous = ">=2.0"
+Jinja2 = ">=3.0"
+Werkzeug = ">=2.2.2"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "flask-cors"
+version = "3.0.10"
+description = "A Flask extension adding a decorator for CORS support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+Flask = ">=0.9"
+Six = "*"
+
+[[package]]
+name = "flask-socketio"
+version = "5.3.1"
+description = "Socket.IO integration for Flask applications"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+Flask = ">=0.9"
+python-socketio = ">=5.0.2"
+
+[[package]]
+name = "flatbuffers"
+version = "22.9.24"
+description = "The FlatBuffers serialization format for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "fonttools"
+version = "4.37.4"
+description = "Tools to manipulate font files"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"]
+graphite = ["lz4 (>=1.7.4.2)"]
+interpolatable = ["munkres", "scipy"]
+lxml = ["lxml (>=4.0,<5)"]
+pathops = ["skia-pathops (>=0.5.0)"]
+plot = ["matplotlib"]
+repacker = ["uharfbuzz (>=0.23.0)"]
+symfont = ["sympy"]
+type1 = ["xattr"]
+ufo = ["fs (>=2.2.0,<3)"]
+unicode = ["unicodedata2 (>=14.0.0)"]
+woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
+
+[[package]]
+name = "frozenlist"
+version = "1.3.1"
+description = "A list-like structure which implements collections.abc.MutableSequence"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "ft4222"
+version = "1.6.0"
+description = "Python wrapper around libFT4222."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "future"
+version = "0.18.2"
+description = "Clean single-source support for Python 3 and 2"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "future-fstrings"
+version = "1.2.0"
+description = "A backport of fstrings to python<3.6"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+rewrite = ["tokenize-rt (>=3)"]
+
+[[package]]
+name = "geoalchemy2"
+version = "0.12.5"
+description = "Using SQLAlchemy with Spatial Databases"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+packaging = "*"
+SQLAlchemy = ">=1.4"
+
+[[package]]
+name = "gevent"
+version = "22.10.1"
+description = "Coroutine-based network library"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5"
+
+[package.dependencies]
+cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
+greenlet = {version = ">=1.1.3,<2.0", markers = "platform_python_implementation == \"CPython\""}
+setuptools = "*"
+"zope.event" = "*"
+"zope.interface" = "*"
+
+[package.extras]
+dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"]
+docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"]
+monitor = ["psutil (>=5.7.0)"]
+recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"]
+test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"]
+
+[[package]]
+name = "greenlet"
+version = "1.1.3.post0"
+description = "Lightweight in-process concurrent programming"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+
+[package.extras]
+docs = ["Sphinx"]
+
+[[package]]
+name = "gunicorn"
+version = "20.1.0"
+description = "WSGI HTTP Server for UNIX"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+setuptools = ">=3.0"
+
+[package.extras]
+eventlet = ["eventlet (>=0.24.1)"]
+gevent = ["gevent (>=1.4.0)"]
+setproctitle = ["setproctitle"]
+tornado = ["tornado (>=0.2)"]
+
+[[package]]
+name = "h3"
+version = "3.7.4"
+description = "Hierarchical hexagonal geospatial indexing system"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+all = ["flake8", "numpy", "pylint", "pytest", "pytest-cov"]
+numpy = ["numpy"]
+test = ["flake8", "pylint", "pytest", "pytest-cov"]
+
+[[package]]
+name = "hatanaka"
+version = "2.4.0"
+description = "Effortlessly compress / decompress any RINEX file"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+importlib-resources = "*"
+ncompress = "*"
+
+[package.extras]
+dev = ["pytest"]
+tests = ["pytest"]
+
+[[package]]
+name = "hexdump"
+version = "3.3"
+description = "dump binary data to hex format and restore from there"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "html5lib"
+version = "1.1"
+description = "HTML parser based on the WHATWG HTML specification"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+six = ">=1.9"
+webencodings = "*"
+
+[package.extras]
+all = ["chardet (>=2.2)", "genshi", "lxml"]
+chardet = ["chardet (>=2.2)"]
+genshi = ["genshi"]
+lxml = ["lxml"]
+
+[[package]]
+name = "humanfriendly"
+version = "10.0"
+description = "Human friendly output for text interfaces using Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
+
+[[package]]
+name = "hypothesis"
+version = "6.46.7"
+description = "A library for property-based testing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+sortedcontainers = ">=2.1.0,<3.0.0"
+
+[package.extras]
+all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.1)"]
+cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
+codemods = ["libcst (>=0.3.16)"]
+dateutil = ["python-dateutil (>=1.4)"]
+django = ["django (>=2.2)"]
+dpcontracts = ["dpcontracts (>=0.4)"]
+ghostwriter = ["black (>=19.10b0)"]
+lark = ["lark-parser (>=0.6.5)"]
+numpy = ["numpy (>=1.9.0)"]
+pandas = ["pandas (>=0.25)"]
+pytest = ["pytest (>=4.6)"]
+pytz = ["pytz (>=2014.1)"]
+redis = ["redis (>=3.0.0)"]
+zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"]
+
+[[package]]
+name = "identify"
+version = "2.5.6"
+description = "File identification library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "imageio"
+version = "2.22.2"
+description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+numpy = "*"
+pillow = ">=8.3.2"
+
+[package.extras]
+all-plugins = ["astropy", "av", "imageio-ffmpeg", "opencv-python", "psutil", "tifffile"]
+all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"]
+build = ["wheel"]
+dev = ["black", "flake8", "fsspec[github]", "invoke", "pytest", "pytest-cov"]
+docs = ["numpydoc", "pydata-sphinx-theme", "sphinx"]
+ffmpeg = ["imageio-ffmpeg", "psutil"]
+fits = ["astropy"]
+full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "invoke", "itk", "numpydoc", "opencv-python", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx", "tifffile", "wheel"]
+gdal = ["gdal"]
+itk = ["itk"]
+linting = ["black", "flake8"]
+opencv = ["opencv-python"]
+pyav = ["av"]
+test = ["fsspec[github]", "invoke", "pytest", "pytest-cov"]
+tifffile = ["tifffile"]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "importlib-metadata"
+version = "4.13.0"
+description = "Read metadata from Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+perf = ["ipython"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+
+[[package]]
+name = "importlib-resources"
+version = "5.10.0"
+description = "Read resources from Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "influxdb-client"
+version = "1.33.0"
+description = "InfluxDB 2.0 Python client library"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+certifi = ">=14.05.14"
+python-dateutil = ">=2.5.3"
+reactivex = ">=4.0.4"
+setuptools = ">=21.0.0"
+urllib3 = ">=1.26.0"
+
+[package.extras]
+async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"]
+ciso = ["ciso8601 (>=2.1.1)"]
+extra = ["numpy", "pandas (>=0.25.3)"]
+test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.2)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"]
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "inputs"
+version = "0.5"
+description = "Cross-platform Python support for keyboards, mice and gamepads."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ipykernel"
+version = "6.16.1"
+description = "IPython Kernel for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+appnope = {version = "*", markers = "platform_system == \"Darwin\""}
+debugpy = ">=1.0"
+ipython = ">=7.23.1"
+jupyter-client = ">=6.1.12"
+matplotlib-inline = ">=0.1"
+nest-asyncio = "*"
+packaging = "*"
+psutil = "*"
+pyzmq = ">=17"
+tornado = ">=6.1"
+traitlets = ">=5.1.0"
+
+[package.extras]
+docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt"]
+test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "ipython"
+version = "8.5.0"
+description = "IPython: Productive Interactive Computing"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+appnope = {version = "*", markers = "sys_platform == \"darwin\""}
+backcall = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+decorator = "*"
+jedi = ">=0.16"
+matplotlib-inline = "*"
+pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
+pickleshare = "*"
+prompt-toolkit = ">3.0.1,<3.1.0"
+pygments = ">=2.4.0"
+stack-data = "*"
+traitlets = ">=5"
+
+[package.extras]
+all = ["Sphinx (>=1.3)", "black", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "testpath", "trio"]
+black = ["black"]
+doc = ["Sphinx (>=1.3)"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["ipywidgets", "notebook"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
+test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
+
+[[package]]
+name = "ipython-genutils"
+version = "0.2.0"
+description = "Vestigial utilities from IPython"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ipywidgets"
+version = "8.0.2"
+description = "Jupyter interactive widgets"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ipykernel = ">=4.5.1"
+ipython = ">=6.1.0"
+jupyterlab-widgets = ">=3.0,<4.0"
+traitlets = ">=4.3.1"
+widgetsnbextension = ">=4.0,<5.0"
+
+[package.extras]
+test = ["jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"]
+
+[[package]]
+name = "isodate"
+version = "0.6.1"
+description = "An ISO 8601 date/time/duration parser and formatter"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "main"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
+plugins = ["setuptools"]
+requirements-deprecated-finder = ["pip-api", "pipreqs"]
+
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+description = "Safely pass data to untrusted environments and back."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "jaraco-classes"
+version = "3.2.3"
+description = "Utility functions for Python class constructs"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+more-itertools = "*"
+
+[package.extras]
+docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "jedi"
+version = "0.18.1"
+description = "An autocompletion tool for Python that can be used for text editors."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+parso = ">=0.8.0,<0.9.0"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
+
+[[package]]
+name = "jeepney"
+version = "0.8.0"
+description = "Low-level, pure Python DBus protocol wrapper."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"]
+trio = ["async_generator", "trio"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+description = "A very fast and expressive template engine."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jmespath"
+version = "1.0.1"
+description = "JSON Matching Expressions"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "joblib"
+version = "1.2.0"
+description = "Lightweight pipelining with Python functions"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "json-logging-py"
+version = "0.2"
+description = "JSON / Logstash formatters for Python logging"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "json-rpc"
+version = "1.13.0"
+description = "JSON-RPC transport implementation"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "json5"
+version = "0.9.10"
+description = "A Python implementation of the JSON5 data format."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+dev = ["hypothesis"]
+
+[[package]]
+name = "jsonschema"
+version = "4.16.0"
+description = "An implementation of JSON Schema validation for Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""}
+pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2"
+
+[package.extras]
+format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"]
+
+[[package]]
+name = "jupyter"
+version = "1.0.0"
+description = "Jupyter metapackage. Install all the Jupyter components in one go."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ipykernel = "*"
+ipywidgets = "*"
+jupyter-console = "*"
+nbconvert = "*"
+notebook = "*"
+qtconsole = "*"
+
+[[package]]
+name = "jupyter-client"
+version = "7.4.3"
+description = "Jupyter protocol implementation and client libraries"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+entrypoints = "*"
+jupyter-core = ">=4.9.2"
+nest-asyncio = ">=1.5.4"
+python-dateutil = ">=2.8.2"
+pyzmq = ">=23.0"
+tornado = ">=6.2"
+traitlets = "*"
+
+[package.extras]
+doc = ["ipykernel", "myst-parser", "sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"]
+test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-commit", "pytest", "pytest-asyncio (>=0.18)", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "jupyter-console"
+version = "6.4.4"
+description = "Jupyter terminal console"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ipykernel = "*"
+ipython = "*"
+jupyter-client = ">=7.0.0"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+
+[package.extras]
+test = ["pexpect"]
+
+[[package]]
+name = "jupyter-core"
+version = "4.11.2"
+description = "Jupyter core package. A base package on which Jupyter projects rely."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""}
+traitlets = "*"
+
+[package.extras]
+test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "jupyter-server"
+version = "1.21.0"
+description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+anyio = ">=3.1.0,<4"
+argon2-cffi = "*"
+jinja2 = "*"
+jupyter-client = ">=6.1.12"
+jupyter-core = ">=4.7.0"
+nbconvert = ">=6.4.4"
+nbformat = ">=5.2.0"
+packaging = "*"
+prometheus-client = "*"
+pywinpty = {version = "*", markers = "os_name == \"nt\""}
+pyzmq = ">=17"
+Send2Trash = "*"
+terminado = ">=0.8.3"
+tornado = ">=6.1.0"
+traitlets = ">=5.1"
+websocket-client = "*"
+
+[package.extras]
+test = ["coverage", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-mock", "pytest-timeout", "pytest-tornasync", "requests"]
+
+[[package]]
+name = "jupyterlab"
+version = "3.4.8"
+description = "JupyterLab computational environment"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ipython = "*"
+jinja2 = ">=2.1"
+jupyter-core = "*"
+jupyter-server = ">=1.16,<2.0"
+jupyterlab-server = ">=2.10,<3.0"
+nbclassic = "*"
+notebook = "<7"
+packaging = "*"
+tomli = "*"
+tornado = ">=6.1.0"
+
+[package.extras]
+test = ["check-manifest", "coverage", "jupyterlab-server[test]", "pre-commit", "pytest (>=6.0)", "pytest-check-links (>=0.5)", "pytest-console-scripts", "pytest-cov", "requests", "requests-cache", "virtualenv"]
+ui-tests = ["build"]
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.2.2"
+description = "Pygments theme using JupyterLab CSS variables"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "jupyterlab-server"
+version = "2.16.1"
+description = "A set of server components for JupyterLab and JupyterLab like applications."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+babel = "*"
+importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""}
+jinja2 = ">=3.0.3"
+json5 = "*"
+jsonschema = ">=3.0.1"
+jupyter-server = ">=1.8,<3"
+packaging = "*"
+requests = "*"
+
+[package.extras]
+docs = ["autodoc-traits", "docutils (<0.19)", "jinja2 (<3.1.0)", "mistune (<1)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi"]
+openapi = ["openapi-core (>=0.14.2)", "ruamel-yaml"]
+test = ["codecov", "ipykernel", "jupyter-server[test]", "openapi-core (>=0.14.2,<0.15.0)", "openapi-spec-validator (<0.5)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "ruamel-yaml", "strict-rfc3339"]
+
+[[package]]
+name = "jupyterlab-vim"
+version = "0.15.1"
+description = "Code cell vim bindings"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "jupyterlab-widgets"
+version = "3.0.3"
+description = "Jupyter interactive widgets for JupyterLab"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "keyring"
+version = "23.9.3"
+description = "Store and access your passwords safely."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+"jaraco.classes" = "*"
+jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""}
+pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""}
+SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
+
+[package.extras]
+docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.4"
+description = "A fast implementation of the Cassowary constraint solver"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "knack"
+version = "0.10.0"
+description = "A Command-Line Interface framework"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+argcomplete = "*"
+jmespath = "*"
+pygments = "*"
+pyyaml = "*"
+tabulate = "*"
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.7.1"
+description = "A fast and thorough lazy object proxy."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "libusb1"
+version = "3.0.0"
+description = "Pure-python wrapper for libusb-1.0"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "lockfile"
+version = "0.12.2"
+description = "Platform-independent file locking module"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "lru-dict"
+version = "1.1.8"
+description = "An Dict like LRU container."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "lxml"
+version = "4.9.1"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=0.29.7)"]
+
+[[package]]
+name = "mako"
+version = "1.2.3"
+description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+MarkupSafe = ">=0.9.2"
+
+[package.extras]
+babel = ["Babel"]
+lingua = ["lingua"]
+testing = ["pytest"]
+
+[[package]]
+name = "markdown"
+version = "3.4.1"
+description = "Python implementation of Markdown."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+
+[package.extras]
+testing = ["coverage", "pyyaml"]
+
+[[package]]
+name = "markdown-it-py"
+version = "2.1.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
+code-style = ["pre-commit (==2.6)"]
+compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
+linkify = ["linkify-it-py (>=1.0,<2.0)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "matplotlib"
+version = "3.6.1"
+description = "Python plotting package"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+contourpy = ">=1.0.1"
+cycler = ">=0.10"
+fonttools = ">=4.22.0"
+kiwisolver = ">=1.0.1"
+numpy = ">=1.19"
+packaging = ">=20.0"
+pillow = ">=6.2.0"
+pyparsing = ">=2.2.1"
+python-dateutil = ">=2.7"
+setuptools_scm = ">=7"
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.6"
+description = "Inline Matplotlib backend for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+traitlets = "*"
+
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "mdit-py-plugins"
+version = "0.3.1"
+description = "Collection of plugins for markdown-it-py"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+markdown-it-py = ">=1.0.0,<3.0.0"
+
+[package.extras]
+code-style = ["pre-commit"]
+rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mistune"
+version = "2.0.4"
+description = "A sane Markdown parser with useful plugins and renderers"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "more-itertools"
+version = "9.0.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mpld3"
+version = "0.5.8"
+description = "D3 Viewer for Matplotlib"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+jinja2 = "*"
+matplotlib = "*"
+
+[[package]]
+name = "mpmath"
+version = "1.2.1"
+description = "Python library for arbitrary-precision floating-point arithmetic"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
+tests = ["pytest (>=4.6)"]
+
+[[package]]
+name = "msal"
+version = "1.20.0b1"
+description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cryptography = ">=0.6,<40"
+PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]}
+pymsalruntime = {version = ">=0.11.2,<0.12", optional = true, markers = "python_version >= \"3.6\" and platform_system == \"Windows\" and extra == \"broker\""}
+requests = ">=2.0.0,<3"
+
+[package.extras]
+broker = ["pymsalruntime (>=0.11.2,<0.12)"]
+
+[[package]]
+name = "msal-extensions"
+version = "1.0.0"
+description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+msal = ">=0.4.1,<2.0.0"
+portalocker = [
+ {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""},
+ {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""},
+]
+
+[[package]]
+name = "msgpack"
+version = "1.0.4"
+description = "MessagePack serializer"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "msgpack-numpy"
+version = "0.4.8"
+description = "Numpy data serialization using msgpack"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+msgpack = ">=0.5.2"
+numpy = ">=1.9.0"
+
+[[package]]
+name = "msgpack-python"
+version = "0.5.6"
+description = "MessagePack (de)serializer."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "msrest"
+version = "0.7.1"
+description = "AutoRest swagger generator Python client runtime."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+azure-core = ">=1.24.0"
+certifi = ">=2017.4.17"
+isodate = ">=0.6.0"
+requests = ">=2.16,<3.0"
+requests-oauthlib = ">=0.5.0"
+
+[package.extras]
+async = ["aiodns", "aiohttp (>=3.0)"]
+
+[[package]]
+name = "msrestazure"
+version = "0.6.4"
+description = "AutoRest swagger generator Python client runtime. Azure-specific module."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+adal = ">=0.6.0,<2.0.0"
+msrest = ">=0.6.0,<2.0.0"
+six = "*"
+
+[[package]]
+name = "multidict"
+version = "6.0.2"
+description = "multidict implementation"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "munch"
+version = "2.5.0"
+description = "A dot-accessible dictionary (a la JavaScript objects)"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[package.extras]
+testing = ["astroid (>=1.5.3,<1.6.0)", "astroid (>=2.0)", "coverage", "pylint (>=1.7.2,<1.8.0)", "pylint (>=2.3.1,<2.4.0)", "pytest"]
+yaml = ["PyYAML (>=5.1.0)"]
+
+[[package]]
+name = "mypy"
+version = "0.961"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "myst-parser"
+version = "0.18.1"
+description = "An extended commonmark compliant parser, with bridges to docutils & sphinx."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+docutils = ">=0.15,<0.20"
+jinja2 = "*"
+markdown-it-py = ">=1.0.0,<3.0.0"
+mdit-py-plugins = ">=0.3.1,<0.4.0"
+pyyaml = "*"
+sphinx = ">=4,<6"
+typing-extensions = "*"
+
+[package.extras]
+code-style = ["pre-commit (>=2.12,<3.0)"]
+linkify = ["linkify-it-py (>=1.0,<2.0)"]
+rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"]
+testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"]
+
+[[package]]
+name = "natsort"
+version = "8.2.0"
+description = "Simple yet flexible natural sorting in Python."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+fast = ["fastnumbers (>=2.0.0)"]
+icu = ["PyICU (>=1.0.0)"]
+
+[[package]]
+name = "nbclassic"
+version = "0.4.7"
+description = "A web-based notebook environment for interactive computing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+argon2-cffi = "*"
+ipykernel = "*"
+ipython-genutils = "*"
+jinja2 = "*"
+jupyter-client = ">=6.1.1"
+jupyter-core = ">=4.6.1"
+jupyter-server = ">=1.8"
+nbconvert = ">=5"
+nbformat = "*"
+nest-asyncio = ">=1.5"
+notebook-shim = ">=0.1.0"
+prometheus-client = "*"
+pyzmq = ">=17"
+Send2Trash = ">=1.8.0"
+terminado = ">=0.8.3"
+tornado = ">=6.1"
+traitlets = ">=4.2.1"
+
+[package.extras]
+docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"]
+json-logging = ["json-logging"]
+test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-tornasync", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"]
+
+[[package]]
+name = "nbclient"
+version = "0.7.0"
+description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
+category = "dev"
+optional = false
+python-versions = ">=3.7.0"
+
+[package.dependencies]
+jupyter-client = ">=6.1.5"
+nbformat = ">=5.0"
+nest-asyncio = "*"
+traitlets = ">=5.2.2"
+
+[package.extras]
+sphinx = ["Sphinx (>=1.7)", "autodoc-traits", "mock", "moto", "myst-parser", "sphinx-book-theme"]
+test = ["black", "check-manifest", "flake8", "ipykernel", "ipython", "ipywidgets", "mypy", "nbconvert", "pip (>=18.1)", "pre-commit", "pytest (>=4.1)", "pytest-asyncio", "pytest-cov (>=2.6.1)", "setuptools (>=60.0)", "testpath", "twine (>=1.11.0)", "xmltodict"]
+
+[[package]]
+name = "nbconvert"
+version = "7.2.2"
+description = "Converting Jupyter Notebooks"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+beautifulsoup4 = "*"
+bleach = "*"
+defusedxml = "*"
+importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+jinja2 = ">=3.0"
+jupyter-core = ">=4.7"
+jupyterlab-pygments = "*"
+markupsafe = ">=2.0"
+mistune = ">=2.0.3,<3"
+nbclient = ">=0.5.0"
+nbformat = ">=5.1"
+packaging = "*"
+pandocfilters = ">=1.4.1"
+pygments = ">=2.4.1"
+tinycss2 = "*"
+traitlets = ">=5.0"
+
+[package.extras]
+all = ["ipykernel", "ipython", "ipywidgets (>=7)", "myst-parser", "nbsphinx (>=0.2.12)", "pre-commit", "pyppeteer (>=1,<1.1)", "pyqtwebengine (>=5.15)", "pytest", "pytest-cov", "pytest-dependency", "sphinx (==5.0.2)", "sphinx-rtd-theme", "tornado (>=6.1)"]
+docs = ["ipython", "myst-parser", "nbsphinx (>=0.2.12)", "sphinx (==5.0.2)", "sphinx-rtd-theme"]
+qtpdf = ["pyqtwebengine (>=5.15)"]
+qtpng = ["pyqtwebengine (>=5.15)"]
+serve = ["tornado (>=6.1)"]
+test = ["ipykernel", "ipywidgets (>=7)", "pre-commit", "pyppeteer (>=1,<1.1)", "pytest", "pytest-cov", "pytest-dependency"]
+webpdf = ["pyppeteer (>=1,<1.1)"]
+
+[[package]]
+name = "nbformat"
+version = "5.7.0"
+description = "The Jupyter Notebook format"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+fastjsonschema = "*"
+jsonschema = ">=2.6"
+jupyter-core = "*"
+traitlets = ">=5.1"
+
+[package.extras]
+test = ["check-manifest", "pep440", "pre-commit", "pytest", "testpath"]
+
+[[package]]
+name = "ncompress"
+version = "1.0.0"
+description = "LZW compression and decompression"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+tests = ["pytest"]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.5.6"
+description = "Patch asyncio to allow nested event loops"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "networkx"
+version = "2.3"
+description = "Python package for creating and manipulating graphs and networks"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+decorator = ">=4.3.0"
+
+[package.extras]
+all = ["gdal", "lxml", "matplotlib", "nose", "numpy", "pandas", "pydot", "pygraphviz", "pyyaml", "scipy"]
+gdal = ["gdal"]
+lxml = ["lxml"]
+matplotlib = ["matplotlib"]
+nose = ["nose"]
+numpy = ["numpy"]
+pandas = ["pandas"]
+pydot = ["pydot"]
+pygraphviz = ["pygraphviz"]
+pyyaml = ["pyyaml"]
+scipy = ["scipy"]
+
+[[package]]
+name = "nodeenv"
+version = "1.7.0"
+description = "Node.js virtual environment builder"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+
+[package.dependencies]
+setuptools = "*"
+
+[[package]]
+name = "nose"
+version = "1.3.7"
+description = "nose extends unittest to make testing easier"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "notebook"
+version = "6.4.12"
+description = "A web-based notebook environment for interactive computing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+argon2-cffi = "*"
+ipykernel = "*"
+ipython-genutils = "*"
+jinja2 = "*"
+jupyter-client = ">=5.3.4"
+jupyter-core = ">=4.6.1"
+nbconvert = ">=5"
+nbformat = "*"
+nest-asyncio = ">=1.5"
+prometheus-client = "*"
+pyzmq = ">=17"
+Send2Trash = ">=1.8.0"
+terminado = ">=0.8.3"
+tornado = ">=6.1"
+traitlets = ">=4.2.1"
+
+[package.extras]
+docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"]
+json-logging = ["json-logging"]
+test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium", "testpath"]
+
+[[package]]
+name = "notebook-shim"
+version = "0.2.0"
+description = "A shim layer for notebook traits and config"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+jupyter-server = ">=1.8,<3"
+
+[package.extras]
+test = ["pytest", "pytest-console-scripts", "pytest-tornasync"]
+
+[[package]]
+name = "numpy"
+version = "1.23.4"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[[package]]
+name = "nvidia-ml-py3"
+version = "7.352.0"
+description = "Python Bindings for the NVIDIA Management Library"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "oauthlib"
+version = "3.2.2"
+description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+rsa = ["cryptography (>=3.0.0)"]
+signals = ["blinker (>=1.4.0)"]
+signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
+
+[[package]]
+name = "onnx"
+version = "1.12.0"
+description = "Open Neural Network Exchange"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+numpy = ">=1.16.6"
+protobuf = ">=3.12.2,<=3.20.1"
+typing-extensions = ">=3.6.2.1"
+
+[package.extras]
+lint = ["clang-format (==13.0.0)", "flake8", "mypy (==0.782)", "types-protobuf (==3.18.4)"]
+
+[[package]]
+name = "onnx2torch"
+version = "1.5.4"
+description = "Nice Onnx to Pytorch converter"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+numpy = ">=1.16.4"
+onnx = ">=1.9.0"
+torch = ">=1.8.0"
+torchvision = ">=0.9.0"
+
+[[package]]
+name = "onnxoptimizer"
+version = "0.3.1"
+description = "Open Neural Network Exchange"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+onnx = "*"
+
+[package.extras]
+mypy = ["mypy (==0.600)"]
+
+[[package]]
+name = "onnxruntime-gpu"
+version = "1.12.1"
+description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+coloredlogs = "*"
+flatbuffers = "*"
+numpy = ">=1.21.0"
+packaging = "*"
+protobuf = "*"
+sympy = "*"
+
+[[package]]
+name = "opencv-python-headless"
+version = "4.5.5.64"
+description = "Wrapper package for OpenCV python bindings."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+numpy = [
+ {version = ">=1.21.2", markers = "python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""},
+ {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\""},
+ {version = ">=1.14.5", markers = "python_version >= \"3.7\""},
+ {version = ">=1.17.3", markers = "python_version >= \"3.8\""},
+]
+
+[package.source]
+type = "url"
+url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl"
+
+[[package]]
+name = "osmium"
+version = "3.4.1"
+description = "Python bindings for libosmium, the data processing library for OSM data"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+requests = "*"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "pandas"
+version = "1.5.1"
+description = "Powerful data structures for data analysis, time series, and statistics"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = {version = ">=1.20.3", markers = "python_version < \"3.10\""}
+python-dateutil = ">=2.8.1"
+pytz = ">=2020.1"
+
+[package.extras]
+test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
+
+[[package]]
+name = "pandocfilters"
+version = "1.5.0"
+description = "Utilities for writing pandoc filters in python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "parameterized"
+version = "0.8.1"
+description = "Parameterized testing with any Python test framework"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+dev = ["jinja2"]
+
+[[package]]
+name = "paramiko"
+version = "2.11.0"
+description = "SSH2 protocol library"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+bcrypt = ">=3.1.3"
+cryptography = ">=2.5"
+pynacl = ">=1.0.1"
+six = "*"
+
+[package.extras]
+all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"]
+ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"]
+gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
+invoke = ["invoke (>=1.3)"]
+
+[[package]]
+name = "parso"
+version = "0.8.3"
+description = "A Python Parser"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["docopt", "pytest (<6.0.0)"]
+
+[[package]]
+name = "pexpect"
+version = "4.8.0"
+description = "Pexpect allows easy control of interactive console applications."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+name = "pickleshare"
+version = "0.7.5"
+description = "Tiny 'shelve'-like database with concurrency support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pillow"
+version = "9.2.0"
+description = "Python Imaging Library (Fork)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "pillow-avif-plugin"
+version = "1.2.2"
+description = "A pillow plugin that adds avif support via libavif"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pipenv"
+version = "2022.10.12"
+description = "Python Development Workflow for Humans."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+certifi = "*"
+setuptools = ">=36.2.1"
+virtualenv = "*"
+virtualenv-clone = ">=0.2.5"
+
+[package.extras]
+dev = ["black", "bs4", "flake8 (>=3.3.0,<4.0)", "invoke", "parver", "sphinx", "towncrier"]
+tests = ["flaky", "mock", "pytest (>=5.0)", "pytest-timeout", "pytest-xdist"]
+
+[[package]]
+name = "pkginfo"
+version = "1.8.3"
+description = "Query metadatdata from sdists / bdists / installed packages."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.extras]
+testing = ["coverage", "nose"]
+
+[[package]]
+name = "pkgutil-resolve-name"
+version = "1.3.10"
+description = "Resolve a name to an object."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "platformdirs"
+version = "2.5.2"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
+test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+
+[[package]]
+name = "plotly"
+version = "5.10.0"
+description = "An open-source, interactive data visualization library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+tenacity = ">=6.2.0"
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "poetry"
+version = "1.2.2"
+description = "Python dependency management and packaging made easy."
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+cachecontrol = {version = ">=0.12.9,<0.13.0", extras = ["filecache"]}
+cachy = ">=0.3.0,<0.4.0"
+cleo = ">=1.0.0a5,<2.0.0"
+crashtest = ">=0.3.0,<0.4.0"
+dulwich = ">=0.20.46,<0.21.0"
+html5lib = ">=1.0,<2.0"
+importlib-metadata = {version = ">=4.4,<5.0", markers = "python_version < \"3.10\""}
+jsonschema = ">=4.10.0,<5.0.0"
+keyring = ">=21.2.0"
+packaging = ">=20.4"
+pexpect = ">=4.7.0,<5.0.0"
+pkginfo = ">=1.5,<2.0"
+platformdirs = ">=2.5.2,<3.0.0"
+poetry-core = "1.3.2"
+poetry-plugin-export = ">=1.1.2,<2.0.0"
+requests = ">=2.18,<3.0"
+requests-toolbelt = ">=0.9.1,<0.10.0"
+shellingham = ">=1.5,<2.0"
+tomlkit = ">=0.11.1,<0.11.2 || >0.11.2,<0.11.3 || >0.11.3,<1.0.0"
+urllib3 = ">=1.26.0,<2.0.0"
+virtualenv = ">=20.4.3,<20.4.5 || >20.4.5,<20.4.6 || >20.4.6"
+xattr = {version = ">=0.9.7,<0.10.0", markers = "sys_platform == \"darwin\""}
+
+[[package]]
+name = "poetry-core"
+version = "1.3.2"
+description = "Poetry PEP 517 Build Backend"
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[[package]]
+name = "poetry-plugin-export"
+version = "1.1.2"
+description = "Poetry plugin to export the dependencies to various formats"
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+poetry = ">=1.2.0,<2.0.0"
+poetry-core = ">=1.1.0,<2.0.0"
+
+[[package]]
+name = "portalocker"
+version = "2.6.0"
+description = "Wraps the portalocker recipe for easy usage"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+docs = ["sphinx (>=1.7.1)"]
+redis = ["redis"]
+tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=3.0.3)"]
+
+[[package]]
+name = "pprofile"
+version = "2.1.0"
+description = "Line-granularity, thread-aware deterministic and statistic pure-python profiler"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pre-commit"
+version = "2.20.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+toml = "*"
+virtualenv = ">=20.0.8"
+
+[[package]]
+name = "pretrainedmodels"
+version = "0.7.4"
+description = "Pretrained models for Pytorch"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+munch = "*"
+torch = "*"
+torchvision = "*"
+tqdm = "*"
+
+[[package]]
+name = "prometheus-client"
+version = "0.15.0"
+description = "Python client for the Prometheus monitoring system."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+twisted = ["twisted"]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.31"
+description = "Library for building powerful interactive command lines in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+wcwidth = "*"
+
+[[package]]
+name = "protobuf"
+version = "3.20.1"
+description = "Protocol Buffers"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "psutil"
+version = "5.9.3"
+description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+description = "Run a subprocess in a pseudo terminal"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pure-eval"
+version = "0.2.2"
+description = "Safely evaluate AST nodes without side effects"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+tests = ["pytest"]
+
+[[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pycapnp"
+version = "1.1.0"
+description = "A cython wrapping of the C++ Cap'n Proto library"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pycodestyle"
+version = "2.8.0"
+description = "Python style guide checker"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pycryptodome"
+version = "3.15.0"
+description = "Cryptographic library for Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pycuda"
+version = "2022.1"
+description = "Python wrapper for Nvidia CUDA"
+category = "dev"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+appdirs = ">=1.4.0"
+mako = "*"
+pytools = ">=2011.2"
+
+[[package]]
+name = "pycurl"
+version = "7.45.1"
+description = "PycURL -- A Python Interface To The cURL library"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "pyflakes"
+version = "2.4.0"
+description = "passive checker of Python programs"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pygame"
+version = "2.1.2"
+description = "Python Game Development"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pygments"
+version = "2.13.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
+[[package]]
+name = "pyjwt"
+version = "2.6.0"
+description = "JSON Web Token implementation in Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
+
+[package.extras]
+crypto = ["cryptography (>=3.4.0)"]
+dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+
+[[package]]
+name = "pylev"
+version = "1.4.0"
+description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pylint"
+version = "2.15.4"
+description = "python code static checker"
+category = "main"
+optional = false
+python-versions = ">=3.7.2"
+
+[package.dependencies]
+astroid = ">=2.12.11,<=2.14.0-dev0"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+dill = ">=0.2"
+isort = ">=4.2.5,<6"
+mccabe = ">=0.6,<0.8"
+platformdirs = ">=2.2.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+tomlkit = ">=0.10.1"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+spelling = ["pyenchant (>=3.2,<4.0)"]
+testutils = ["gitpython (>3)"]
+
+[[package]]
+name = "pymsalruntime"
+version = "0.11.2"
+description = "The MSALRuntime Python Interop Package"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pymysql"
+version = "0.9.3"
+description = "Pure Python MySQL Driver"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+rsa = ["cryptography"]
+
+[[package]]
+name = "pynacl"
+version = "1.5.0"
+description = "Python binding to the Networking and Cryptography (NaCl) library"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = ">=1.4.1"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"]
+tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
+
+[[package]]
+name = "pyopencl"
+version = "2022.2.4"
+description = "Python wrapper for OpenCL"
+category = "main"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+numpy = "*"
+platformdirs = ">=2.2.0"
+pytools = ">=2021.2.7"
+
+[package.extras]
+oclgrind = ["oclgrind-binary-distribution (>=18.3)"]
+pocl = ["pocl-binary-distribution (>=1.2)"]
+test = ["Mako", "pytest (>=7.0.0)"]
+
+[[package]]
+name = "pyopenssl"
+version = "22.0.0"
+description = "Python wrapper module around the OpenSSL library"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cryptography = ">=35.0"
+
+[package.extras]
+docs = ["sphinx", "sphinx-rtd-theme"]
+test = ["flaky", "pretend", "pytest (>=3.0.1)"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "main"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pyprof2calltree"
+version = "1.4.5"
+description = "Help visualize profiling data from cProfile with kcachegrind and qcachegrind"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pyproj"
+version = "3.4.0"
+description = "Python interface to PROJ (cartographic projections and coordinate transformations library)"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+certifi = "*"
+
+[[package]]
+name = "pyreadline3"
+version = "3.4.1"
+description = "A python implementation of GNU readline."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyrsistent"
+version = "0.18.1"
+description = "Persistent/Functional/Immutable data structures"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pyserial"
+version = "3.5"
+description = "Python Serial Port Extension"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+cp2110 = ["hidapi"]
+
+[[package]]
+name = "pysocks"
+version = "1.7.1"
+description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pytest"
+version = "7.1.3"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+py = ">=1.8.2"
+tomli = ">=1.0.0"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-forked"
+version = "1.4.0"
+description = "run tests in isolated forked subprocesses"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+py = "*"
+pytest = ">=3.10"
+
+[[package]]
+name = "pytest-xdist"
+version = "2.5.0"
+description = "pytest xdist plugin for distributed testing and loop-on-failing modes"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+execnet = ">=1.1"
+pytest = ">=6.2.0"
+pytest-forked = "*"
+
+[package.extras]
+psutil = ["psutil (>=3.0)"]
+setproctitle = ["setproctitle"]
+testing = ["filelock"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-engineio"
+version = "4.3.4"
+description = "Engine.IO server and client for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+asyncio-client = ["aiohttp (>=3.4)"]
+client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
+
+[[package]]
+name = "python-logstash"
+version = "0.4.8"
+description = "Python logging handler for Logstash."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "python-socketio"
+version = "5.7.2"
+description = "Socket.IO server and client for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+bidict = ">=0.21.0"
+python-engineio = ">=4.3.0"
+
+[package.extras]
+asyncio-client = ["aiohttp (>=3.4)"]
+client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
+
+[[package]]
+name = "pytools"
+version = "2022.1.12"
+description = "A collection of tools for Python"
+category = "main"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+platformdirs = ">=2.2.0"
+typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+numpy = ["numpy (>=1.6.0)"]
+
+[[package]]
+name = "pytz"
+version = "2022.5"
+description = "World timezone definitions, modern and historical"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pywavelets"
+version = "1.4.1"
+description = "PyWavelets, wavelet transform module"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = ">=1.17.3"
+
+[[package]]
+name = "pywin32"
+version = "304"
+description = "Python for Window Extensions"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pywin32-ctypes"
+version = "0.2.0"
+description = ""
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pywinpty"
+version = "2.0.8"
+description = "Pseudo terminal support for Windows from Python."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyzmq"
+version = "23.2.1"
+description = "Python bindings for 0MQ"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = {version = "*", markers = "implementation_name == \"pypy\""}
+py = {version = "*", markers = "implementation_name == \"pypy\""}
+
+[[package]]
+name = "qtconsole"
+version = "5.3.2"
+description = "Jupyter Qt console"
+category = "dev"
+optional = false
+python-versions = ">= 3.7"
+
+[package.dependencies]
+ipykernel = ">=4.1"
+ipython-genutils = "*"
+jupyter-client = ">=4.1"
+jupyter-core = "*"
+pygments = "*"
+pyzmq = ">=17.1"
+qtpy = ">=2.0.1"
+traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2"
+
+[package.extras]
+doc = ["Sphinx (>=1.3)"]
+test = ["flaky", "pytest", "pytest-qt"]
+
+[[package]]
+name = "qtpy"
+version = "2.2.1"
+description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+packaging = "*"
+
+[package.extras]
+test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"]
+
+[[package]]
+name = "qudida"
+version = "0.0.4"
+description = "QUick and DIrty Domain Adaptation"
+category = "dev"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.dependencies]
+numpy = ">=0.18.0"
+opencv-python-headless = ">=4.0.1"
+scikit-learn = ">=0.19.1"
+typing-extensions = "*"
+
+[[package]]
+name = "reactivex"
+version = "4.0.4"
+description = "ReactiveX (Rx) for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7,<4.0"
+
+[package.dependencies]
+typing-extensions = ">=4.1.1,<5.0.0"
+
+[[package]]
+name = "redis"
+version = "4.3.4"
+description = "Python client for Redis database and key-value store"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+async-timeout = ">=4.0.2"
+deprecated = ">=1.2.3"
+packaging = ">=20.4"
+
+[package.extras]
+hiredis = ["hiredis (>=1.0.0)"]
+ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<3"
+idna = ">=2.5,<4"
+PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requests-oauthlib"
+version = "1.3.1"
+description = "OAuthlib authentication support for Requests."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+oauthlib = ">=3.0.0"
+requests = ">=2.0.0"
+
+[package.extras]
+rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
+
+[[package]]
+name = "requests-toolbelt"
+version = "0.9.1"
+description = "A utility belt for advanced users of python-requests"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+requests = ">=2.0.1,<3.0.0"
+
+[[package]]
+name = "reverse-geocoder"
+version = "1.5.1"
+description = "Fast, offline reverse geocoder"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+numpy = ">=1.11.0"
+scipy = ">=0.17.1"
+
+[[package]]
+name = "s2sphere"
+version = "0.2.5"
+description = "Python implementation of the S2 Geometry Library"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+future = ">=0.15"
+
+[package.extras]
+docs = ["Sphinx (>=1.6.5)", "sphinx-rtd-theme (>=0.1.9)"]
+tests = ["flake8 (>=2.5.4)", "hacking (>=0.11.0)", "nose (>=1.3.4)", "numpy (>=1.11.0)"]
+
+[[package]]
+name = "scikit-image"
+version = "0.19.3"
+description = "Image processing in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+imageio = ">=2.4.1"
+networkx = ">=2.2"
+numpy = ">=1.17.0"
+packaging = ">=20.0"
+pillow = ">=6.1.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1,<8.3.0 || >8.3.0"
+PyWavelets = ">=1.1.1"
+scipy = ">=1.4.1"
+tifffile = ">=2019.7.26"
+
+[package.extras]
+data = ["pooch (>=1.3.0)"]
+docs = ["cloudpickle (>=0.2.1)", "dask[array] (>=0.15.0,!=2.17.0)", "ipywidgets", "kaleido", "matplotlib (>=3.3)", "myst-parser", "numpydoc (>=1.0)", "pandas (>=0.23.0)", "plotly (>=4.14.0)", "pooch (>=1.3.0)", "pytest-runner", "scikit-learn", "seaborn (>=0.7.1)", "sphinx (>=1.8)", "sphinx-copybutton", "sphinx-gallery (>=0.10.1)", "tifffile (>=2020.5.30)"]
+optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pyamg", "qtpy"]
+test = ["asv", "codecov", "flake8", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"]
+
+[[package]]
+name = "scikit-learn"
+version = "1.1.2"
+description = "A set of python modules for machine learning and data mining"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+joblib = ">=1.0.0"
+numpy = ">=1.17.3"
+scipy = ">=1.3.2"
+threadpoolctl = ">=2.0.0"
+
+[package.extras]
+benchmark = ["matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"]
+docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=4.0.1)", "sphinx-gallery (>=0.7.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"]
+examples = ["matplotlib (>=3.1.2)", "pandas (>=1.0.5)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"]
+tests = ["black (>=22.3.0)", "flake8 (>=3.8.2)", "matplotlib (>=3.1.2)", "mypy (>=0.961)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pyamg (>=4.0.0)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "scikit-image (>=0.16.2)"]
+
+[[package]]
+name = "scipy"
+version = "1.9.3"
+description = "Fundamental algorithms for scientific computing in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = ">=1.18.5,<1.26.0"
+
+[package.extras]
+dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"]
+doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"]
+test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
+
+[[package]]
+name = "scons"
+version = "4.4.0"
+description = "Open Source next-generation build tool."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+setuptools = "*"
+
+[[package]]
+name = "secretstorage"
+version = "3.3.3"
+description = "Python bindings to FreeDesktop.org Secret Service API"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cryptography = ">=2.0"
+jeepney = ">=0.6"
+
+[[package]]
+name = "segmentation-models-pytorch"
+version = "0.2.1"
+description = "Image segmentation models with pre-trained backbones. PyTorch."
+category = "dev"
+optional = false
+python-versions = ">=3.0.0"
+
+[package.dependencies]
+efficientnet-pytorch = "0.6.3"
+pretrainedmodels = "0.7.4"
+timm = "0.4.12"
+torchvision = ">=0.5.0"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "send2trash"
+version = "1.8.0"
+description = "Send file to trash natively under Mac OS X, Windows and Linux."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+nativelib = ["pyobjc-framework-Cocoa", "pywin32"]
+objc = ["pyobjc-framework-Cocoa"]
+win32 = ["pywin32"]
+
+[[package]]
+name = "sentry-sdk"
+version = "1.10.0"
+description = "Python client for Sentry (https://sentry.io)"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+certifi = "*"
+urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""}
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.5)"]
+beam = ["apache-beam (>=2.12)"]
+bottle = ["bottle (>=0.12.13)"]
+celery = ["celery (>=3)"]
+chalice = ["chalice (>=1.16.0)"]
+django = ["django (>=1.8)"]
+falcon = ["falcon (>=1.4)"]
+fastapi = ["fastapi (>=0.79.0)"]
+flask = ["blinker (>=1.1)", "flask (>=0.11)"]
+httpx = ["httpx (>=0.16.0)"]
+pure-eval = ["asttokens", "executing", "pure-eval"]
+pyspark = ["pyspark (>=2.4.4)"]
+quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
+rq = ["rq (>=0.6)"]
+sanic = ["sanic (>=0.8)"]
+sqlalchemy = ["sqlalchemy (>=1.2)"]
+starlette = ["starlette (>=0.19.1)"]
+tornado = ["tornado (>=5)"]
+
+[[package]]
+name = "setproctitle"
+version = "1.3.2"
+description = "A Python module to customize the process title"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "setuptools"
+version = "65.5.0"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
+name = "setuptools-scm"
+version = "7.0.5"
+description = "the blessed package to manage your versions by scm tags"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+packaging = ">=20.0"
+setuptools = "*"
+tomli = ">=1.0.0"
+typing-extensions = "*"
+
+[package.extras]
+test = ["pytest (>=6.2)", "virtualenv (>20)"]
+toml = ["setuptools (>=42)"]
+
+[[package]]
+name = "shellingham"
+version = "1.5.0"
+description = "Tool to Detect Surrounding Shell"
+category = "main"
+optional = false
+python-versions = ">=3.4"
+
+[[package]]
+name = "simplejson"
+version = "3.17.6"
+description = "Simple, fast, extensible JSON encoder/decoder for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "smbus2"
+version = "0.4.2"
+description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+docs = ["sphinx (>=1.5.3)"]
+qa = ["flake8"]
+test = ["mock", "nose"]
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sounddevice"
+version = "0.4.5"
+description = "Play and Record Sound with Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+CFFI = ">=1.0"
+
+[package.extras]
+numpy = ["NumPy"]
+
+[[package]]
+name = "soupsieve"
+version = "2.3.2.post1"
+description = "A modern CSS selector implementation for Beautiful Soup."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "sphinx"
+version = "5.3.0"
+description = "Python documentation generator"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=2.9"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.14,<0.20"
+imagesize = ">=1.3"
+importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""}
+Jinja2 = ">=3.0"
+packaging = ">=21.0"
+Pygments = ">=2.12"
+requests = ">=2.5.0"
+snowballstemmer = ">=2.0"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.5"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"]
+test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"]
+
+[[package]]
+name = "sphinx-rtd-theme"
+version = "1.0.0"
+description = "Read the Docs theme for Sphinx"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+
+[package.dependencies]
+docutils = "<0.18"
+sphinx = ">=1.6"
+
+[package.extras]
+dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"]
+
+[[package]]
+name = "sphinx-sitemap"
+version = "2.2.0"
+description = "Sitemap generator for Sphinx"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+sphinx = ">=1.2"
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.2"
+description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.0"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.5"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "spidev"
+version = "3.6"
+description = "Python bindings for Linux SPI access through spidev"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "sqlalchemy"
+version = "1.4.42"
+description = "Database Abstraction Library"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+
+[package.dependencies]
+greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
+
+[package.extras]
+aiomysql = ["aiomysql", "greenlet (!=0.4.17)"]
+aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
+asyncio = ["greenlet (!=0.4.17)"]
+asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
+mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
+mssql = ["pyodbc"]
+mssql-pymssql = ["pymssql"]
+mssql-pyodbc = ["pyodbc"]
+mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
+mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
+mysql-connector = ["mysql-connector-python"]
+oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
+postgresql = ["psycopg2 (>=2.7)"]
+postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
+postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
+postgresql-psycopg2binary = ["psycopg2-binary"]
+postgresql-psycopg2cffi = ["psycopg2cffi"]
+pymysql = ["pymysql", "pymysql (<1)"]
+sqlcipher = ["sqlcipher3_binary"]
+
+[[package]]
+name = "stack-data"
+version = "0.5.1"
+description = "Extract data from python stack frames and tracebacks for informative displays"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+asttokens = "*"
+executing = "*"
+pure-eval = "*"
+
+[package.extras]
+tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
+
+[[package]]
+name = "subprocess32"
+version = "3.5.4"
+description = "A backport of the subprocess module from Python 3 for use on 2.x."
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
+
+[[package]]
+name = "sympy"
+version = "1.11.1"
+description = "Computer algebra system (CAS) in Python"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+mpmath = ">=0.19"
+
+[[package]]
+name = "tabulate"
+version = "0.8.10"
+description = "Pretty-print tabular data"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+widechars = ["wcwidth"]
+
+[[package]]
+name = "tenacity"
+version = "8.1.0"
+description = "Retry code until it succeeds"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+doc = ["reno", "sphinx", "tornado (>=4.5)"]
+
+[[package]]
+name = "terminado"
+version = "0.16.0"
+description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ptyprocess = {version = "*", markers = "os_name != \"nt\""}
+pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""}
+tornado = ">=6.1.0"
+
+[package.extras]
+test = ["pre-commit", "pytest (>=6.0)", "pytest-timeout"]
+
+[[package]]
+name = "threadpoolctl"
+version = "3.1.0"
+description = "threadpoolctl"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "tifffile"
+version = "2022.10.10"
+description = "Read and write TIFF files"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = ">=1.19.2"
+
+[package.extras]
+all = ["fsspec", "imagecodecs (>=2022.2.22)", "lxml", "matplotlib (>=3.3)", "zarr"]
+
+[[package]]
+name = "timezonefinder"
+version = "6.1.3"
+description = "fast python package for finding the timezone of any point on earth (coordinates) offline"
+category = "main"
+optional = false
+python-versions = ">=3.8,<3.11"
+
+[package.dependencies]
+cffi = ">=1.15.1,<2.0.0"
+h3 = ">=3.7.3,<4.0.0"
+numpy = ">=1.22,<2.0"
+setuptools = ">=65.3.0,<66.0.0"
+
+[package.extras]
+numba = ["numba (>=0.55.2,<0.56.0)"]
+
+[[package]]
+name = "timm"
+version = "0.4.12"
+description = "(Unofficial) PyTorch Image Models"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+torch = ">=1.4"
+torchvision = "*"
+
+[[package]]
+name = "tinycss2"
+version = "1.2.1"
+description = "A tiny CSS parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+webencodings = ">=0.4"
+
+[package.extras]
+doc = ["sphinx", "sphinx_rtd_theme"]
+test = ["flake8", "isort", "pytest"]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "tomlkit"
+version = "0.11.5"
+description = "Style preserving TOML library"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[[package]]
+name = "torch"
+version = "1.11.0+cu113"
+description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration"
+category = "dev"
+optional = false
+python-versions = ">=3.7.0"
+
+[package.dependencies]
+typing-extensions = "*"
+
+[package.source]
+type = "url"
+url = "https://download.pytorch.org/whl/cu113/torch-1.11.0%2Bcu113-cp38-cp38-linux_x86_64.whl"
+
+[[package]]
+name = "torchsummary"
+version = "1.5.1"
+description = "Model summary in PyTorch similar to `model.summary()` in Keras"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "torchvision"
+version = "0.12.0+cu113"
+description = "image and video datasets and models for torch deep learning"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+numpy = "*"
+pillow = ">=5.3.0,<8.3.0 || >=8.4.0"
+requests = "*"
+torch = "1.11.0"
+typing-extensions = "*"
+
+[package.extras]
+scipy = ["scipy"]
+
+[package.source]
+type = "url"
+url = "https://download.pytorch.org/whl/cu113/torchvision-0.12.0%2Bcu113-cp38-cp38-linux_x86_64.whl"
+
+[[package]]
+name = "tornado"
+version = "6.2"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+category = "dev"
+optional = false
+python-versions = ">= 3.7"
+
+[[package]]
+name = "tqdm"
+version = "4.64.1"
+description = "Fast, Extensible Progress Meter"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["py-make (>=0.1.0)", "twine", "wheel"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
+[[package]]
+name = "traitlets"
+version = "5.5.0"
+description = ""
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
+test = ["pre-commit", "pytest"]
+
+[[package]]
+name = "triton"
+version = "1.1.1"
+description = "A language and compiler for custom Deep Learning operations"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+filelock = "*"
+torch = "*"
+
+[[package]]
+name = "types-atomicwrites"
+version = "1.4.5.1"
+description = "Typing stubs for atomicwrites"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-certifi"
+version = "2021.10.8.3"
+description = "Typing stubs for certifi"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-pycurl"
+version = "7.45.1"
+description = "Typing stubs for pycurl"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-pyyaml"
+version = "6.0.12"
+description = "Typing stubs for PyYAML"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "types-requests"
+version = "2.28.11.2"
+description = "Typing stubs for requests"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+types-urllib3 = "<1.27"
+
+[[package]]
+name = "types-urllib3"
+version = "1.26.25.1"
+description = "Typing stubs for urllib3"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "typing-extensions"
+version = "4.4.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "utm"
+version = "0.7.0"
+description = "Bidirectional UTM-WGS84 converter for python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "virtualenv"
+version = "20.16.5"
+description = "Virtual Python Environment builder"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+distlib = ">=0.3.5,<1"
+filelock = ">=3.4.1,<4"
+platformdirs = ">=2.4,<3"
+
+[package.extras]
+docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
+testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
+
+[[package]]
+name = "virtualenv-clone"
+version = "0.5.7"
+description = "script to clone virtualenvs."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "websocket-client"
+version = "1.4.1"
+description = "WebSocket client for Python with low level API options"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"]
+optional = ["python-socks", "wsaccel"]
+test = ["websockets"]
+
+[[package]]
+name = "werkzeug"
+version = "2.2.2"
+description = "The comprehensive WSGI web application library."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+MarkupSafe = ">=2.1.1"
+
+[package.extras]
+watchdog = ["watchdog"]
+
+[[package]]
+name = "widgetsnbextension"
+version = "4.0.3"
+description = "Jupyter interactive widgets for Jupyter Notebook"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "wrapt"
+version = "1.14.1"
+description = "Module for decorators, wrappers and monkey patching."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
+name = "xattr"
+version = "0.9.9"
+description = "Python wrapper for extended filesystem attributes"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cffi = ">=1.0"
+
+[[package]]
+name = "yarl"
+version = "1.8.1"
+description = "Yet another URL library"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+idna = ">=2.0"
+multidict = ">=4.0"
+
+[[package]]
+name = "zerorpc"
+version = "0.6.3"
+description = "zerorpc is a flexible RPC based on zeromq."
+category = "dev"
+optional = false
+python-versions = "*"
+develop = false
+
+[package.dependencies]
+future = "*"
+gevent = ">=1.1"
+msgpack = ">=0.5.2"
+msgpack-numpy = ">=0.4.3"
+pyzmq = ">=13.1.0"
+
+[package.source]
+type = "git"
+url = "https://github.com/commaai/zerorpc-python.git"
+reference = "master"
+resolved_reference = "0e27fd795bb9a7ec9cab81a771a2e35a91e397c9"
+
+[[package]]
+name = "zipp"
+version = "3.9.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "zope-event"
+version = "4.5.0"
+description = "Very basic event publishing system"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+setuptools = "*"
+
+[package.extras]
+docs = ["Sphinx"]
+test = ["zope.testrunner"]
+
+[[package]]
+name = "zope-interface"
+version = "5.5.0"
+description = "Interfaces for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+setuptools = "*"
+
+[package.extras]
+docs = ["Sphinx", "repoze.sphinx.autointerface"]
+test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
+testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "~3.8"
+content-hash = "331a7b700c17c618a4f0fa7d0ec3e5b585c1dc467f6f03261c3778f729058f55"
+
+[metadata.files]
+adal = [
+ {file = "adal-1.2.7-py2.py3-none-any.whl", hash = "sha256:2a7451ed7441ddbc57703042204a3e30ef747478eea022c70f789fc7f084bc3d"},
+ {file = "adal-1.2.7.tar.gz", hash = "sha256:d74f45b81317454d96e982fd1c50e6fb5c99ac2223728aea8764433a39f566f1"},
+]
+aenum = [
+ {file = "aenum-3.1.11-py2-none-any.whl", hash = "sha256:525b4870a27d0b471c265bda692bc657f1e0dd7597ad4186d072c59f9db666f6"},
+ {file = "aenum-3.1.11-py3-none-any.whl", hash = "sha256:12ae89967f2e25c0ce28c293955d643f891603488bc3d9946158ba2b35203638"},
+ {file = "aenum-3.1.11.tar.gz", hash = "sha256:aed2c273547ae72a0d5ee869719c02a643da16bf507c80958faadc7e038e3f73"},
+]
+aiohttp = [
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"},
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"},
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"},
+ {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"},
+ {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"},
+ {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"},
+ {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"},
+ {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"},
+ {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"},
+ {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"},
+ {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"},
+ {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
+]
+aiosignal = [
+ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
+ {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
+]
+alabaster = [
+ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
+ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
+]
+albumentations = [
+ {file = "albumentations-1.3.0-py3-none-any.whl", hash = "sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf"},
+ {file = "albumentations-1.3.0.tar.gz", hash = "sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed"},
+]
+anyio = [
+ {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
+ {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
+]
+apex = []
+appdirs = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+applicationinsights = [
+ {file = "applicationinsights-0.11.10-py2.py3-none-any.whl", hash = "sha256:e89a890db1c6906b6a7d0bcfd617dac83974773c64573147c8d6654f9cf2a6ea"},
+ {file = "applicationinsights-0.11.10.tar.gz", hash = "sha256:0b761f3ef0680acf4731906dfc1807faa6f2a57168ae74592db0084a6099f7b3"},
+]
+appnope = [
+ {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
+ {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
+]
+argcomplete = [
+ {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
+ {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
+]
+argon2-cffi = [
+ {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"},
+ {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"},
+]
+argon2-cffi-bindings = [
+ {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"},
+ {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"},
+]
+astroid = [
+ {file = "astroid-2.12.12-py3-none-any.whl", hash = "sha256:72702205200b2a638358369d90c222d74ebc376787af8fb2f7f2a86f7b5cc85f"},
+ {file = "astroid-2.12.12.tar.gz", hash = "sha256:1c00a14f5a3ed0339d38d2e2e5b74ea2591df5861c0936bb292b84ccf3a78d83"},
+]
+asttokens = [
+ {file = "asttokens-2.0.8-py2.py3-none-any.whl", hash = "sha256:e3305297c744ae53ffa032c45dc347286165e4ffce6875dc662b205db0623d86"},
+ {file = "asttokens-2.0.8.tar.gz", hash = "sha256:c61e16246ecfb2cde2958406b4c8ebc043c9e6d73aaa83c941673b35e5d3a76b"},
+]
+async-timeout = [
+ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
+ {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
+]
+atomicwrites = [
+ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
+]
+attrs = [
+ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+ {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+av = [
+ {file = "av-9.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29373aa86a055a07eebb14d253cb202033f63ba98c5a4b0233d6d4c07fc7a292"},
+ {file = "av-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:343b11d9b03e71da29f3ce56bc0a6c2d40aba448225dcf8296ab53c10527fff0"},
+ {file = "av-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ea180bfd89bc0a9e392c32de204cf4e51648aefe2f375d430ce39c04e3ed625"},
+ {file = "av-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b07b91f534ee7a096068149404c67c3c0e5b4c373580b016151de0fcb440cd3f"},
+ {file = "av-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9983bc45dab65d2416d2f8a63785caa076a751590823fc8c199617d0dbad390"},
+ {file = "av-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:0340cc68f3d222bc9438b4fde12e3d68f949eeb5de9e090db182f2cb06e23d53"},
+ {file = "av-9.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e3e4a28fa0eabd3ab5b0915e9c005e9155039f9e1a4466212434c40eb69a33fb"},
+ {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a3c9126d658029b151484b48c656b73af1b145b143c50de5b8b983ac60e095"},
+ {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3facfe8dc5ba7f9ec7fd7e4c0466e577b84d5f2a1671428f7e28ebcd2cb0ccd3"},
+ {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af951271d998f736a20e54fbc0d944f263db7b17592f11cd489947957bf46aa8"},
+ {file = "av-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:109526152e658921731018c50a05db802e7c9f3eb04a7a5fcbd8321fb3b73134"},
+ {file = "av-9.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:587dd492a2ef3eb20324a0a8d67e6a2e686845d8c1dfdcad058377ac84268d67"},
+ {file = "av-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28d85b8476f7d8fb18e3af9bd6d22bb292f1d810a20f8910fe481f648372e798"},
+ {file = "av-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6a35e6028dec677caed97d19bfab3b66182690d43b0ec3c355778d740ce0509"},
+ {file = "av-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a616a6eb46b62f41ff69569cafe12b0005a6dd14389f597dee115340336a910f"},
+ {file = "av-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d730f3ed30eda46d06849bd71ad87d480cf0cad9fd064f33a0386dee95461e31"},
+ {file = "av-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:6b01fbe8047da81892f8bd2aee5690f00465bf5215e3f6b6372863ac9408d75f"},
+ {file = "av-9.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba3d9e3fe23fd8a14e810f291386225acbdef1c7e5376cc10c8e85c2d4280771"},
+ {file = "av-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6be9388618af978304b56d1cf6b74c811db4f220dd320da5bd79640aa443358"},
+ {file = "av-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e59e4ab0e8832bf87707e5024283b3a24cc01784604f0b0e96fbfbadbd8d9fc0"},
+ {file = "av-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b0f124e335561cf4de6b7cdc461283c5eba5f05cccb1a5e1b8ceb1cd15393d8"},
+ {file = "av-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49481c2d5bc296f451ccd3f93b1cb692d7f58a804b794b99c8b7743e058cae71"},
+ {file = "av-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1cbf031f650f89943023eef80e8b2c99588bf9ba26ffef8b3b54bef7102ea3dc"},
+ {file = "av-9.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:48819e401cea5be57bd03299d8e5f700082c411746d1ac23eb5e5a931d3d3ced"},
+ {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9cad890e6eccf2697b1c932761bee6f5e1e7faf9b8c03cf10f18f697d29ba3"},
+ {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080f34ddfde551de3a5f2d0d06d7518718e3115af81e56182e158cc03111662"},
+ {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b46b54ddf64409d4455f408b5970f8494c27c0273181b81c2f7d5072c9afb55"},
+ {file = "av-9.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf941896b4c800ee707211c802f94c6e0b4642d3000e25d1974d0b6032af4f66"},
+ {file = "av-9.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d380925732e7497c1c11545107eabe1f498cab214f49f32d1b5d6abe01a2b36b"},
+ {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1c2c1dcc1947473ea1e2cbbf50549e2655e49e08bdd2a6427a97276d7a92c8"},
+ {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2a50a146b3f33a24ea059af913ad368dbb61ed494234debe140a09f1076950"},
+ {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45816a39255b39e514a72125e0b6e29eb24fe0994bef3f4f87f3b9d9960b3fa8"},
+ {file = "av-9.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ab90aa3ac2cbdf1f22087fc0fa439f643e96979f169ecfa1d496e114c3c3a8b3"},
+ {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24dac414eafcc20423f2ec7e873706489433648f0e9af08a537996880aa55979"},
+ {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17a7b6617d4201214f3dd5f628041b4fe56f4244dcd48399ed8d0cf324ca24d1"},
+ {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8671fa01648ce7aac76e71816c2421ddb1939bf706e2e14684608ab1ce9dbbbb"},
+ {file = "av-9.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a5dc26b9df656bed5e1bdeaf8bcc4ff4a2e009ee90b3b3024a86cf8476b2cbf"},
+ {file = "av-9.2.0.tar.gz", hash = "sha256:f2a7c226724d7f7745b376b459c500d9d17bd8d0473b7ea6bf8ddb4f7957c69d"},
+]
+azure-cli-core = [
+ {file = "azure-cli-core-2.41.0.tar.gz", hash = "sha256:f76fa9a44fd1e858397ba2951d260309e70710f367dd609eea03d6c0d8b05d3e"},
+ {file = "azure_cli_core-2.41.0-py3-none-any.whl", hash = "sha256:b4cdf765106ff481361d6d84ae0403c46ea6c414ea511b38a316241c03f69252"},
+]
+azure-cli-telemetry = [
+ {file = "azure-cli-telemetry-1.0.8.tar.gz", hash = "sha256:ca996d162ab689c865f6b60be23b9757c26c3d97928e3319858eea83462df08d"},
+ {file = "azure_cli_telemetry-1.0.8-py3-none-any.whl", hash = "sha256:502cbd90723a16603822909befd096ca0b1707de1e70cf730e7b4700ddd7a456"},
+]
+azure-common = [
+ {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"},
+ {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"},
+]
+azure-core = [
+ {file = "azure-core-1.26.0.zip", hash = "sha256:b0036a0d256329e08d1278dff7df36be30031d2ec9b16c691bc61e4732f71fe0"},
+ {file = "azure_core-1.26.0-py3-none-any.whl", hash = "sha256:578ea3ae56bca48880c96797871b6c954b5ae78d10d54360182c7604dc837f25"},
+]
+azure-mgmt-core = [
+ {file = "azure-mgmt-core-1.3.2.zip", hash = "sha256:07f4afe823a55d704b048d61edfdc1318c051ed59f244032126350be95e9d501"},
+ {file = "azure_mgmt_core-1.3.2-py3-none-any.whl", hash = "sha256:fd829f67086e5cf6f7eb016c9e80bb0fb293cbbbd4d8738dc90af9aa1055fb7b"},
+]
+azure-nspkg = [
+ {file = "azure-nspkg-3.0.2.zip", hash = "sha256:e7d3cea6af63e667d87ba1ca4f8cd7cb4dfca678e4c55fc1cedb320760e39dd0"},
+ {file = "azure_nspkg-3.0.2-py2-none-any.whl", hash = "sha256:1d0bbb2157cf57b1bef6c8c8e5b41133957364456c43b0a43599890023cca0a8"},
+ {file = "azure_nspkg-3.0.2-py3-none-any.whl", hash = "sha256:31a060caca00ed1ebd369fc7fe01a56768c927e404ebc92268f4d9d636435e28"},
+]
+azure-storage-blob = [
+ {file = "azure-storage-blob-2.1.0.tar.gz", hash = "sha256:b90323aad60f207f9f90a0c4cf94c10acc313c20b39403398dfba51f25f7b454"},
+ {file = "azure_storage_blob-2.1.0-py2.py3-none-any.whl", hash = "sha256:a8e91a51d4f62d11127c7fd8ba0077385c5b11022f0269f8a2a71b9fc36bef31"},
+]
+azure-storage-common = [
+ {file = "azure-storage-common-2.1.0.tar.gz", hash = "sha256:ccedef5c67227bc4d6670ffd37cec18fb529a1b7c3a5e53e4096eb0cf23dc73f"},
+ {file = "azure_storage_common-2.1.0-py2.py3-none-any.whl", hash = "sha256:b01a491a18839b9d05a4fe3421458a0ddb5ab9443c14e487f40d16f9a1dc2fbe"},
+]
+azure-storage-nspkg = [
+ {file = "azure-storage-nspkg-3.1.0.tar.gz", hash = "sha256:6f3bbe8652d5f542767d8433e7f96b8df7f518774055ac7c92ed7ca85f653811"},
+ {file = "azure_storage_nspkg-3.1.0-py2.py3-none-any.whl", hash = "sha256:7da3bd6c73b8c464a57f53ae9af8328490d2267c66430d8a7621997e52a9703e"},
+]
+babel = [
+ {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
+ {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
+]
+backcall = [
+ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+ {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
+bcrypt = [
+ {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
+ {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
+ {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
+ {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
+ {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
+ {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
+ {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
+ {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
+ {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
+ {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
+ {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
+ {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
+ {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
+ {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
+ {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
+ {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
+]
+beautifulsoup4 = [
+ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"},
+ {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"},
+]
+bidict = [
+ {file = "bidict-0.22.0-py3-none-any.whl", hash = "sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0"},
+ {file = "bidict-0.22.0.tar.gz", hash = "sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8"},
+]
+bleach = [
+ {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"},
+ {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"},
+]
+blosc = [
+ {file = "blosc-1.9.2.tar.gz", hash = "sha256:89196a2112035836f027a29835ee247b0c7a45cece4cd9e4b740a1428aa174bf"},
+]
+breathe = [
+ {file = "breathe-4.34.0-py3-none-any.whl", hash = "sha256:48804dcf0e607a89fb6ad88c729ef12743a42db03ae9489be4ef8f7c4011774a"},
+ {file = "breathe-4.34.0.tar.gz", hash = "sha256:ac0768a5e84addad3e632028fe67749c567aba2b29088493b64c2c1634bcdba1"},
+]
+cachecontrol = [
+ {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"},
+ {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"},
+]
+cachy = [
+ {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"},
+ {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"},
+]
+carla = [
+ {file = "carla-0.9.13-cp27-cp27mu-manylinux_2_27_x86_64.whl", hash = "sha256:339fcb1e392f3ade1be82b7258de19c533e2efae111e954a6eb174efb296903d"},
+ {file = "carla-0.9.13-cp36-cp36m-manylinux_2_27_x86_64.whl", hash = "sha256:5f065825ce812343bf27a80a19d647b3200b31b44a9e80cea0340e3bd20cdf81"},
+ {file = "carla-0.9.13-cp36-cp36m-win_amd64.whl", hash = "sha256:1210cce213e968a644effd4e2e48458a072481459d073424b05725056ba3d77d"},
+ {file = "carla-0.9.13-cp37-cp37m-manylinux_2_27_x86_64.whl", hash = "sha256:a95d2d4218ea388c863c66b7c2ab3fe49ffefe53999305cfcb6a8107042f79af"},
+ {file = "carla-0.9.13-cp37-cp37m-win_amd64.whl", hash = "sha256:954ca34d5bdd4516ceca353db907fee8cec6630d6b31a732b17dd1554e0f0f94"},
+ {file = "carla-0.9.13-cp38-cp38-manylinux_2_27_x86_64.whl", hash = "sha256:d2bfaea2d6824a2d758cbe813856c69420494f5c97d2a2dfb45653ccf976f1ce"},
+ {file = "carla-0.9.13-cp38-cp38-win_amd64.whl", hash = "sha256:a64ee78fe91137fa7d4828c7fc06d5824bd7312e29e4ea4f31a5d74dd28bff40"},
+]
+casadi = [
+ {file = "casadi-3.5.5-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:d4e49cb46404cef61f83b30bb20ec9597c50ae7f55cfd6b89c17facc74675437"},
+ {file = "casadi-3.5.5-cp27-none-manylinux1_i686.whl", hash = "sha256:f08a99e98b0a15083f06b1e221f064a29b3ed9e20617dc55aa8e823f2f732ace"},
+ {file = "casadi-3.5.5-cp27-none-manylinux1_x86_64.whl", hash = "sha256:09e103bb597d46aa338fc57bc49270068a1f07be35f9494c9f796dea4b801aeb"},
+ {file = "casadi-3.5.5-cp27-none-win32.whl", hash = "sha256:a4ce51e988570160af9ccfbbb1b9679546cbb1865d3a74ef0276f37fd94d91d9"},
+ {file = "casadi-3.5.5-cp27-none-win_amd64.whl", hash = "sha256:54d89442058271007ae8573dfa33360bea10e26603545481090b45e8b90c9d10"},
+ {file = "casadi-3.5.5-cp34-none-manylinux1_x86_64.whl", hash = "sha256:4143803af909f284400c02f59de4d97e5ba9319de28366215ef55ef261914f9a"},
+ {file = "casadi-3.5.5-cp34-none-win32.whl", hash = "sha256:7a624d40c7b5ded7916f6cc65998af4585b4557c9ea65dc1e3a6273ebb2313ec"},
+ {file = "casadi-3.5.5-cp34-none-win_amd64.whl", hash = "sha256:3aec6737c282e7fb5be41f6c7d0649e52ce49efb3508f30bada707e809bbbb5f"},
+ {file = "casadi-3.5.5-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6192e2ed81c15a7dab2554f5f69b134df8d1a982f8d9f13e57bdef93364d2120"},
+ {file = "casadi-3.5.5-cp35-none-manylinux1_i686.whl", hash = "sha256:49a8b713f0ff0bbc2f2af2e71c515cdced238786e25ef504f5982618c84c67a7"},
+ {file = "casadi-3.5.5-cp35-none-manylinux1_x86_64.whl", hash = "sha256:13277151efc76b221de8ca6b5ab7b8bbdd2b0e139f282866840adf88dfe53bc9"},
+ {file = "casadi-3.5.5-cp35-none-manylinux2014_aarch64.whl", hash = "sha256:253569c85f881a6a8fe5e1c0758858edb1ecb4c3d8bce4aee4b52e5dc59fc091"},
+ {file = "casadi-3.5.5-cp35-none-win32.whl", hash = "sha256:5de5c3c1381ac303e71fdef75dace34af6e1d50b46ac081051cd209b8b933837"},
+ {file = "casadi-3.5.5-cp35-none-win_amd64.whl", hash = "sha256:4932b2b5361013420189dbc8d30e970672d036b37cb382f1c09c3b6cfe651a37"},
+ {file = "casadi-3.5.5-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:2322748a8d5e88750fd2fc0abcdc56cfbad1a8cd538fe0e7d7b6d8ce0cb3fa62"},
+ {file = "casadi-3.5.5-cp36-none-manylinux1_i686.whl", hash = "sha256:ab6a600a9b2ea27453d56fd4464ad0db0ae69f5cea42595fcbdaabcd40396440"},
+ {file = "casadi-3.5.5-cp36-none-manylinux1_x86_64.whl", hash = "sha256:5f6eb8de31735c14ecc777e3ad77b57767b5f2dbea29265909ef696f51e8be92"},
+ {file = "casadi-3.5.5-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:adf20c34ba2cec1840a026023d93cc6d9b3581dfda6a044f434fc75b50c9a2ce"},
+ {file = "casadi-3.5.5-cp36-none-win32.whl", hash = "sha256:7309a75b27c57f09b00a61815fb38c40da8e62e3004598e55ea1b8f713d96221"},
+ {file = "casadi-3.5.5-cp36-none-win_amd64.whl", hash = "sha256:ab85c7cf772ba54f2718ebe366b836fffff868443f7c0c02389ed0a288cbde1f"},
+ {file = "casadi-3.5.5-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:ec26244f9d9047f1bb401f1b86ff4775e1ddf638f4b4992bbc362a27a6f56673"},
+ {file = "casadi-3.5.5-cp37-none-manylinux1_i686.whl", hash = "sha256:1c451a07b2440c00d552e040b6285b6e79b677d2978212368b28b86f5d267669"},
+ {file = "casadi-3.5.5-cp37-none-manylinux1_x86_64.whl", hash = "sha256:24fbac649ee26572884029dcd0e108b4a2412cad003a84ed915c4e44a94ecae7"},
+ {file = "casadi-3.5.5-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:a06c0b96eb9d3bc88c627eec6e465726934ca0394347dc33efc742b8c91db83d"},
+ {file = "casadi-3.5.5-cp37-none-win32.whl", hash = "sha256:36db4c84d8f3aad328faaeaeaa454a633c95a854d78ea188791b147888379342"},
+ {file = "casadi-3.5.5-cp37-none-win_amd64.whl", hash = "sha256:643e48f92eaf65eb82964816bb7e7064ddb8239959210fa6168e8bce6fe6ef94"},
+ {file = "casadi-3.5.5-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6ce7ac8a301a145f98d46db0bfd13bc8b3831a5bb92e8054d531a1f233bb4b93"},
+ {file = "casadi-3.5.5-cp38-none-manylinux1_i686.whl", hash = "sha256:473bb86fa64ac9703d74a474514703b4665fa9a384221ced620b5025e64532a7"},
+ {file = "casadi-3.5.5-cp38-none-manylinux1_x86_64.whl", hash = "sha256:292e2768280393bad406256e0ef9c30ddcd4867dbd42148b36f9d92a32d9e199"},
+ {file = "casadi-3.5.5-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:353a79e50aa84ac5e0d9f04bc3b2d78a2cc8edae3b842d757756449682778944"},
+ {file = "casadi-3.5.5-cp38-none-win32.whl", hash = "sha256:77f33cb95be6a49b93d8d6b81f05193676ae09857699cedf8f1a14a4285d077e"},
+ {file = "casadi-3.5.5-cp38-none-win_amd64.whl", hash = "sha256:fbf39dcd63f1d3b63c300fce59b7ea678bd5ea1d014e1e090a5226600a4132cb"},
+ {file = "casadi-3.5.5-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4086669280b2335d235c664373db46dcd7f6485dba4663ce1944ea01753c5e8b"},
+ {file = "casadi-3.5.5-cp39-none-manylinux1_i686.whl", hash = "sha256:c3440c90c31b61ae1df82f6c784643393f723354dc08013f9d5cedf25507c67c"},
+ {file = "casadi-3.5.5-cp39-none-manylinux1_x86_64.whl", hash = "sha256:bd94048388b602fc30fdac2fecb986c034110ed8d2d17af7fd13b0de45c58bd7"},
+ {file = "casadi-3.5.5-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:cd630a2e6ec6df6a4977af63080fa8d63a0053ff8c06ea0200959b47ae75201c"},
+ {file = "casadi-3.5.5-cp39-none-win32.whl", hash = "sha256:ac45b91616e9b8afbe266ca08e80770b28e9e6d7a5852e3677fb37e42bde2047"},
+ {file = "casadi-3.5.5-cp39-none-win_amd64.whl", hash = "sha256:55df534d003efdd120c4ebfeb6b252c443d273cdc4b97a394eb0268367477795"},
+]
+certifi = [
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+cffi = [
+ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+]
+cfgv = [
+ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
+ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
+]
+charset-normalizer = [
+ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+ {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
+cleo = [
+ {file = "cleo-1.0.0a5-py3-none-any.whl", hash = "sha256:ff53056589300976e960f75afb792dfbfc9c78dcbb5a448e207a17b643826360"},
+ {file = "cleo-1.0.0a5.tar.gz", hash = "sha256:097c9d0e0332fd53cc89fc11eb0a6ba0309e6a3933c08f7b38558555486925d3"},
+]
+click = [
+ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+cloudpickle = [
+ {file = "cloudpickle-2.2.0-py3-none-any.whl", hash = "sha256:7428798d5926d8fcbfd092d18d01a2a03daf8237d8fcdc8095d256b8490796f0"},
+ {file = "cloudpickle-2.2.0.tar.gz", hash = "sha256:3f4219469c55453cfe4737e564b67c2a149109dabf7f242478948b895f61106f"},
+]
+colorama = [
+ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
+ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
+]
+coloredlogs = [
+ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
+ {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
+]
+configargparse = [
+ {file = "ConfigArgParse-1.5.3-py3-none-any.whl", hash = "sha256:18f6535a2db9f6e02bd5626cc7455eac3e96b9ab3d969d366f9aafd5c5c00fe7"},
+ {file = "ConfigArgParse-1.5.3.tar.gz", hash = "sha256:1b0b3cbf664ab59dada57123c81eff3d9737e0d11d8cf79e3d6eb10823f1739f"},
+]
+contourpy = [
+ {file = "contourpy-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:87121b9428ac568fb84fae4af5e7852fc34f02eadc4e3e91f6c8989327692186"},
+ {file = "contourpy-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fb782982c42cee667b892a0b0c52a9f6c7ecf1da5c5f4345845f04eaa862f93"},
+ {file = "contourpy-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:689d7d2a840619915d0abd1ecc6e399fee202f8ad315acda2807f4ca420d0802"},
+ {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88814befbd1433152c5f6dd536905149ba028d795a22555b149ae0a36024d9e"},
+ {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df65f4b2b4e74977f0336bef12a88051ab24e6a16873cd9249f34d67cb3e345d"},
+ {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6b4c0c723664f65c2a47c8cb6ebbf660b0b2e2d936adf2e8503d4e93359465"},
+ {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bcc98d397c3dea45d5b262029564b29cb8e945f2607a38bee6163694c0a8b4ef"},
+ {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2bf5c846c257578b03d498b20f54f53551616a507d8e5463511c58bb58e9a9cf"},
+ {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdacddb18d55ffec42d1907079cdc04ec4fa8a990cdf5b9d9fe67d281fc0d12e"},
+ {file = "contourpy-1.0.5-cp310-cp310-win32.whl", hash = "sha256:434942fa2f9019b9ae525fb752dc523800c49a1a28fbd6d9240b0fa959573dcc"},
+ {file = "contourpy-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:3b3082ade8849130203d461b98c2a061b382c46074b43b4edd5cefd81af92b8a"},
+ {file = "contourpy-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:057114f698ffb9e54657e8fda6802e2f5c8fad609845cf6afaf31590ef6a33c0"},
+ {file = "contourpy-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:218722a29c5c26677d37c44f5f8a372daf6f07870aad793a97d47eb6ad6b3290"},
+ {file = "contourpy-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c02e22cf09996194bcb3a4784099975cf527d5c29caf759abadf29ebdb2fe27"},
+ {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d5ee865b5fd16bf62d72122aadcc90aab296c30c1adb0a32b4b66bd843163e"},
+ {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45822b0a2a452327ab4f95efe368d234d5294bbf89a99968be27c7938a21108"},
+ {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dca5be83a6dfaf933a46e3bc2b9f2685e5ec61b22f6a38ad740aac9c16e9a0ff"},
+ {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c3f2f6b898a40207843ae01970e57e33d22a26b22f23c6a5e07b4716751085f"},
+ {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2b4eab7c12f9cb460509bc34a3b086f9802f0dba27c89a63df4123819ad64af"},
+ {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09ed9b63f4df8a7591b7a4a26c1ad066dcaafda1f846250fdcb534074a411692"},
+ {file = "contourpy-1.0.5-cp311-cp311-win32.whl", hash = "sha256:f670686d99c867d0f24b28ce8c6f02429c6eef5e2674aab287850d0ee2d20437"},
+ {file = "contourpy-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:c51568e94f7f232296de30002f2a50f77a7bd346673da3e4f2aaf9d2b833f2e5"},
+ {file = "contourpy-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c9e99aac7b430f6a9f15eebf058c742097cea3369f23a2bfc5e64d374b67e3a"},
+ {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3210d93ad2af742b6a96cf39792f7181822edbb8fe11c3ef29d1583fe637a8d8"},
+ {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128bd7acf569f8443ad5b2227f30ac909e4f5399ed221727eeacf0c6476187e6"},
+ {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:813c2944e940ef8dccea71305bacc942d4b193a021140874b3e58933ec44f5b6"},
+ {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a74afd8d560eaafe0d9e3e1db8c06081282a05ca4de00ee416195085a79d7d3d"},
+ {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d0ad9a85f208473b1f3613c45756c7aa6fcc288266a8c7b873f896aaf741b6b"},
+ {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:60f37acd4e4227c5a29f737d9a85ca3145c529a8dd4bf70af7f0637c61b49222"},
+ {file = "contourpy-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:b50e481a4317a8efcfffcfddcd4c9b36eacba440440e70cbe0256aeb6fd6abae"},
+ {file = "contourpy-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:0395ae71164bfeb2dedd136e03c71a2718a5aa9873a46f518f4133be0d63e1d2"},
+ {file = "contourpy-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3ca40d7844b391d90b864c6a6d1bb6b88b09035fb4d866d64d43c4d26fb0ab64"},
+ {file = "contourpy-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3109fa601d2a448cec4643abd3a31f972bf05b7c2f2e83df9d3429878f8c10ae"},
+ {file = "contourpy-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06c4d1dde5ee4f909a8a95ba1eb04040c6c26946b4f3b5beaf10d45f14e940ee"},
+ {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54dcc9bb9390fd0636301ead134d46d5229fe86da0db4d974c0fda349f560e"},
+ {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b8e24813e2fb5a3e598c1f8b9ae403e1438cb846a80cc2b33cddf19dddd7f2"},
+ {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:061e1f066c419ffe25b615a1df031b4832ea1d7f2676937e69e8e00e24512005"},
+ {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:19ea64fa0cf389d2ebc10974616acfa1fdecbd73d1fd9c72215b782f3c40f561"},
+ {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfe924e5a63861c82332a12adeeab955dc8c8009ddbbd80cc2fcca049ff89a49"},
+ {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bed3a2a823a041e8d249b1a7ec132933e1505299329b5cfe1b2b5ec689ec7675"},
+ {file = "contourpy-1.0.5-cp38-cp38-win32.whl", hash = "sha256:0389349875424aa8c5e61f757e894687916bc4e9616cc6afcbd8051aa2428952"},
+ {file = "contourpy-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:2b5e334330d82866923015b455260173cb3b9e3b4e297052d758abd262031289"},
+ {file = "contourpy-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:def9a01b73c9e27d70ea03b381fb3e7aadfac1f398dbd63751313c3a46747ef5"},
+ {file = "contourpy-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:59c827e536bb5e3ef58e06da0faba61fd89a14f30b68bcfeca41f43ca83a1942"},
+ {file = "contourpy-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f05d311c937da03b0cd26ac3e14cb991f6ff8fc94f98b3df9713537817539795"},
+ {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:970a4be7ec84ccda7c27cb4ae74930bbbd477bc8d849ed55ea798084dd5fca8c"},
+ {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7672148f8fca48e4efc16aba24a7455b40c22d4f8abe42475dec6a12b0bb9a"},
+ {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eba62b7c21a33e72dd8adab2b92dd5610d8527f0b2ac28a8e0770e71b21a13f9"},
+ {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dd084459ecdb224e617e4ab3f1d5ebe4d1c48facb41f24952b76aa6ba9712bb0"},
+ {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c5158616ab39d34b76c50f40c81552ee180598f7825dc7a66fd187d29958820f"},
+ {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f856652f9b533c6cd2b9ad6836a7fc0e43917d7ff15be46c5baf1350f8cdc5d9"},
+ {file = "contourpy-1.0.5-cp39-cp39-win32.whl", hash = "sha256:f1cc623fd6855b25da52b3275e0c9e51711b86a9dccc75f8c9ab4432fd8e42c7"},
+ {file = "contourpy-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:e67dcaa34dcd908fcccbf49194211d847c731b6ebaac661c1c889f1bf6af1e44"},
+ {file = "contourpy-1.0.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfd634cb9685161b2a51f73a7fc4736fd0d67a56632d52319317afaa27f08243"},
+ {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79908b9d02b1d6c1c71ff3b7ad127f3f82e14a8e091ab44b3c7e34b649fea733"},
+ {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4963cf08f4320d98ae72ec7694291b8ab85cb7da3b0cd824bc32701bc992edf"},
+ {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cfc067ddde78b76dcbc9684d82688b7d3c5158fa2254a085f9bcb9586c1e2d8"},
+ {file = "contourpy-1.0.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9939796abcadb2810a63dfb26ff8ca4595fe7dd70a3ceae7f607a2639b714307"},
+ {file = "contourpy-1.0.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d8150579bf30cdf896906baf256aa200cd50dbe6e565c17d6fd3d678e21ff5de"},
+ {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed9c91bf4ce614efed5388c3f989a7cfe08728ab871d995a486ea74ff88993db"},
+ {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46a04588ceb7cf132568e0e564a854627ef87a1ed3bf536234540a79ced44b0"},
+ {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b85553699862c09937a7a5ea14ee6229087971a7d51ae97d5f4b407f571a2c17"},
+ {file = "contourpy-1.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99a8071e351b50827ad976b92ed91845fb614ac67a3c41109b24f3d8bd3afada"},
+ {file = "contourpy-1.0.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fb0458d74726937ead9e2effc91144aea5a58ecee9754242f8539a782bed685a"},
+ {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f89f0608a5aa8142ed0e53957916623791a88c7f5e5f07ae530c328beeb888f"},
+ {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce763369e646e59e4ca2c09735cd1bdd3048d909ad5f2bc116e83166a9352f3c"},
+ {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c16fa267740d67883899e054cccb4279e002f3f4872873b752c1ba15045ff49"},
+ {file = "contourpy-1.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a30e95274f5c0e007ccc759ec258aa5708c534ec058f153ee25ac700a2f1438b"},
+ {file = "contourpy-1.0.5.tar.gz", hash = "sha256:896631cd40222aef3697e4e51177d14c3709fda49d30983269d584f034acc8a4"},
+]
+control = [
+ {file = "control-0.9.2.tar.gz", hash = "sha256:0891d2d32d6006ac1faa4e238ed8223ca342a4721d202dfeccae24fb02563183"},
+]
+coverage = [
+ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+ {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+ {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+ {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+ {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+ {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+ {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+ {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+ {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+ {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+ {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+ {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+ {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+ {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+ {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+ {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+]
+crashtest = [
+ {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"},
+ {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"},
+]
+crcmod = [
+ {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"},
+]
+cryptography = [
+ {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
+ {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
+ {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
+ {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
+ {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
+ {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
+ {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
+ {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
+ {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
+ {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
+ {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
+ {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
+ {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
+ {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
+ {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
+ {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
+ {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
+ {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
+ {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
+ {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
+ {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
+ {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
+]
+cupy-cuda113 = [
+ {file = "cupy_cuda113-10.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:27e5efe2c3afa80ff48654cb27f9e0eddb36f8b26ef0d32d3ba0a233e1359b51"},
+ {file = "cupy_cuda113-10.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b96076d1ddd33fdb2c908ed0f8109caf69d37d36f839a8a8cdae1312508336f"},
+ {file = "cupy_cuda113-10.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22363c2863727cae5154aa4bab9e8a648d7fe66c9e2195d81dd4e8693c2e61ce"},
+ {file = "cupy_cuda113-10.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8cc69b9d5735372477a7af3822c8f8e996ffe6de05cfc917500af9dc0117ca3e"},
+ {file = "cupy_cuda113-10.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:10dc6899577e445426d81f0960ba9059d9aaa750426997c61fad882d6345264c"},
+ {file = "cupy_cuda113-10.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:c6893ac9040a11610e63973063dfd715dbda8bd07ef99951bab7a09c7f335e1e"},
+ {file = "cupy_cuda113-10.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4bf4bc06d991c06b95f6fe558d117cafd93bd4eeaf80606f18dd31d20d2eff25"},
+ {file = "cupy_cuda113-10.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:3745fc42dca86ba8a1109ddc7964aed8e1efc0ce8085cb2f140dcd6429f26354"},
+]
+cycler = [
+ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"},
+ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
+]
+cython = [
+ {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"},
+ {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"},
+ {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3"},
+ {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd"},
+ {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833"},
+ {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b"},
+ {file = "Cython-0.29.32-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c"},
+ {file = "Cython-0.29.32-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98"},
+ {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276"},
+ {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6"},
+ {file = "Cython-0.29.32-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928"},
+ {file = "Cython-0.29.32-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf"},
+ {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673"},
+ {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9"},
+ {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094"},
+ {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457"},
+ {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65"},
+ {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1"},
+ {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64"},
+ {file = "Cython-0.29.32-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3"},
+ {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3"},
+ {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b"},
+ {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd"},
+ {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3"},
+ {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754"},
+ {file = "Cython-0.29.32-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da"},
+ {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754"},
+ {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0"},
+ {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0"},
+ {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9"},
+ {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b"},
+ {file = "Cython-0.29.32-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd"},
+ {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b"},
+ {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3"},
+ {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9"},
+ {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a"},
+ {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831"},
+ {file = "Cython-0.29.32-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b"},
+ {file = "Cython-0.29.32-py2.py3-none-any.whl", hash = "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b"},
+ {file = "Cython-0.29.32.tar.gz", hash = "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7"},
+]
+datadog = [
+ {file = "datadog-0.44.0-py2.py3-none-any.whl", hash = "sha256:57c4878d3a8351f652792cdba78050274789dcc44313adec096e87f9d3ca5992"},
+ {file = "datadog-0.44.0.tar.gz", hash = "sha256:071170f0c7ef22511dbf7f9bd76c4be500ee2d3d52072900a5c87b5495d2c733"},
+]
+debugpy = [
+ {file = "debugpy-1.6.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3"},
+ {file = "debugpy-1.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585"},
+ {file = "debugpy-1.6.3-cp310-cp310-win32.whl", hash = "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7"},
+ {file = "debugpy-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2"},
+ {file = "debugpy-1.6.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a"},
+ {file = "debugpy-1.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d"},
+ {file = "debugpy-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161"},
+ {file = "debugpy-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25"},
+ {file = "debugpy-1.6.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b"},
+ {file = "debugpy-1.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4"},
+ {file = "debugpy-1.6.3-cp38-cp38-win32.whl", hash = "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6"},
+ {file = "debugpy-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734"},
+ {file = "debugpy-1.6.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae"},
+ {file = "debugpy-1.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d"},
+ {file = "debugpy-1.6.3-cp39-cp39-win32.whl", hash = "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908"},
+ {file = "debugpy-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16"},
+ {file = "debugpy-1.6.3-py2.py3-none-any.whl", hash = "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f"},
+ {file = "debugpy-1.6.3.zip", hash = "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf"},
+]
+decorator = [
+ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+]
+defusedxml = [
+ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
+ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
+]
+deprecated = [
+ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
+ {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
+]
+dictdiffer = [
+ {file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
+ {file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
+]
+dill = [
+ {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
+ {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
+]
+distlib = [
+ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
+ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
+]
+docutils = [
+ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
+ {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
+]
+dotmap = [
+ {file = "dotmap-1.3.30-py3-none-any.whl", hash = "sha256:bd9fa15286ea2ad899a4d1dc2445ed85a1ae884a42effb87c89a6ecce71243c6"},
+ {file = "dotmap-1.3.30.tar.gz", hash = "sha256:5821a7933f075fb47563417c0e92e0b7c031158b4c9a6a7e56163479b658b368"},
+]
+dulwich = [
+ {file = "dulwich-0.20.46-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6676196e9cf377cde62aa2f5d741e93207437343e0c62368bd0d784c322a3c49"},
+ {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a1ca555a3eafe7388d6cb81bb08f34608a1592500f0bd4c26734c91d208a546"},
+ {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:769442c9657b10fc35ac625beeaf440540c9288c96fcfaba3e58adf745c5cafd"},
+ {file = "dulwich-0.20.46-cp310-cp310-win32.whl", hash = "sha256:de22a54f68c6c4e97f9b924abd46da4618536d7934b9849066be9fc5cd31205d"},
+ {file = "dulwich-0.20.46-cp310-cp310-win_amd64.whl", hash = "sha256:42fa5a68908556eb6c40f231a67caf6a4660588aad707a9d6b334fa1d8f04bf7"},
+ {file = "dulwich-0.20.46-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:3e16376031466848e44aabf3489fafb054482143744b21167dbd168731041c74"},
+ {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153c7512587384a290c60fef330f1ab397a59559e19e8b02a0169ff21b4c69fb"},
+ {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b68bd815cd2769c75e5a78708eb0440612df19b370a977aa9e01a056baa9ed"},
+ {file = "dulwich-0.20.46-cp311-cp311-win32.whl", hash = "sha256:b1339bca70764eb8e780d80c72e7c1cb4651201dc9e43ec5d616bf51eb3bb3a6"},
+ {file = "dulwich-0.20.46-cp311-cp311-win_amd64.whl", hash = "sha256:1162fdafb2abdfe66649617061f3853cb26384fade1f6884f6fe6e9c570a7552"},
+ {file = "dulwich-0.20.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6826512f778eaa47e2e8c0a46cdc555958f9f5286771490b8642b4b508ea5d25"},
+ {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:100d39bc18196a07c521fd5f60f78f397493303daa0b8690216864bbc621cd5d"},
+ {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4cd2cd7baa81246bdc8c5272d4e9224e2255da7a0618a220aab5e07b9888e9b"},
+ {file = "dulwich-0.20.46-cp36-cp36m-win32.whl", hash = "sha256:6eed5a3194d64112605fc0f638f4fa91771495e8674fa3e6d6b33bf150d297d5"},
+ {file = "dulwich-0.20.46-cp36-cp36m-win_amd64.whl", hash = "sha256:9ca4d73987f5b0e2e843497876f9bb39a47384a2e50597a85542285f5c890293"},
+ {file = "dulwich-0.20.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b9f49de83911eed7adbe83136229837ef9d102e42dbe6aacb1a18be45c997ace"},
+ {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38be7d3a78d608ecab3348f7920d6b9002e7972dd245206dc8075cfdb91621d"},
+ {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4b7a7feb966a4669c254b18385fe0b3c639f3b1f5ddef0d9e083364cc762847"},
+ {file = "dulwich-0.20.46-cp37-cp37m-win32.whl", hash = "sha256:f9552ac246bceab1c5cdd1ec3cfe9446fe76b9853eaf59d3244df03eb27fd3fe"},
+ {file = "dulwich-0.20.46-cp37-cp37m-win_amd64.whl", hash = "sha256:90a075aeb0fdbad7e18b9db3af161e3d635e2b7697b7a4b467e6844a13b0b210"},
+ {file = "dulwich-0.20.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:8d6fee82cedb2362942d9ef94061901f7e07d7d8674e4c7b6fceeef7822ae275"},
+ {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669c6b3d82996518a7fec4604771bd285e23f0860f41f565fef5987265d431d9"},
+ {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3eac228117487a959ac8f49ea2787eac34acc69999fe7adae70b23e3c3571c"},
+ {file = "dulwich-0.20.46-cp38-cp38-win32.whl", hash = "sha256:92024f572d32680e021219f77015c8b443c38022e502b7f51ad7cf51a6285a36"},
+ {file = "dulwich-0.20.46-cp38-cp38-win_amd64.whl", hash = "sha256:d928de1eba0326a2a8a52ed94c9bf7c315ff4db606a1aa3ae688d39574f93267"},
+ {file = "dulwich-0.20.46-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:a5d1b7a3a7d84a5dedbb90092e00097357106b9642ac08a96c2ae89ccd8afd9a"},
+ {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b739d759c10e2af7c964dcc97fd4e5dc49e8567d080eed8906fc422c79b7fdcf"},
+ {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc7a4f633f5468453d5dd84a753cd99d4433f0397437229a0a8b10347935591"},
+ {file = "dulwich-0.20.46-cp39-cp39-win32.whl", hash = "sha256:525115c4d1fbf60a5fe98f340b4ca597ba47b2c75d9c5ec750dd0e9115ef8ec6"},
+ {file = "dulwich-0.20.46-cp39-cp39-win_amd64.whl", hash = "sha256:73e2585a9fcf1f8cdad8597a0c384c0b365b2e8346463130c96d9ea1478587ae"},
+ {file = "dulwich-0.20.46.tar.gz", hash = "sha256:4f0e88ffff5db1523d93d92f1525fe5fa161318ffbaad502c1b9b3be7a067172"},
+]
+efficientnet-pytorch = [
+ {file = "efficientnet_pytorch-0.6.3.tar.gz", hash = "sha256:6667459336893e9bf6367de3788ba449fed97f65da3b6782bf2204b6273a319f"},
+]
+einops = [
+ {file = "einops-0.5.0-py3-none-any.whl", hash = "sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1"},
+ {file = "einops-0.5.0.tar.gz", hash = "sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3"},
+]
+elastic-transport = [
+ {file = "elastic-transport-8.4.0.tar.gz", hash = "sha256:b9ad708ceb7fcdbc6b30a96f886609a109f042c0b9d9f2e44403b3133ba7ff10"},
+ {file = "elastic_transport-8.4.0-py3-none-any.whl", hash = "sha256:19db271ab79c9f70f8c43f8f5b5111408781a6176b54ab2e54d713b6d9ceb815"},
+]
+elasticsearch = [
+ {file = "elasticsearch-8.4.3-py3-none-any.whl", hash = "sha256:14c68a96b7bbbf150dd9fca5ff65da9c50e791c0fdba474a328e43828fdd7f42"},
+ {file = "elasticsearch-8.4.3.tar.gz", hash = "sha256:d34d43a6c349d15c9d91840f791eeba80fc50ee070caf6695130f56b7f41a02d"},
+]
+entrypoints = [
+ {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"},
+ {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"},
+]
+execnet = [
+ {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
+ {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
+]
+executing = [
+ {file = "executing-1.1.1-py2.py3-none-any.whl", hash = "sha256:236ea5f059a38781714a8bfba46a70fad3479c2f552abee3bbafadc57ed111b8"},
+ {file = "executing-1.1.1.tar.gz", hash = "sha256:b0d7f8dcc2bac47ce6e39374397e7acecea6fdc380a6d5323e26185d70f38ea8"},
+]
+fastcluster = [
+ {file = "fastcluster-1.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d0e8faef0437a25fd083df70fb86cc65ce3c9c9780d4ae377cbe6521e7746ce0"},
+ {file = "fastcluster-1.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8be01f97bc2bf11a9188537864f8e520e1103cdc6007aa2c5d7979b1363b121"},
+ {file = "fastcluster-1.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:855ab2b7e6fa9b05f19c4f3023dedfb1a35a88d831933d65d0d9e10a070a9e85"},
+ {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72503e727887a61a15f9aaa13178798d3994dfec58aa7a943e42dcfda07c0149"},
+ {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcb0973ca0e6978e3242046338c350cbed1493108929231fae9bd35ad05a6d6"},
+ {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9020899b67fe492d0ed87a3e993ec9962c5a0b51ea2df71d86b1766f065f1cef"},
+ {file = "fastcluster-1.2.6-cp310-cp310-win32.whl", hash = "sha256:6cf156d4203708348522393c523c2e61c81f5a6a500e0411dcba2b064551ea2f"},
+ {file = "fastcluster-1.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:1801c9daa9aa5bbbb0830efe8bd3034b4b7a417e4b8dd353683999be29797df2"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cf5acfe1156849279ebd44a8d1fbcbe8b8e21334f7538eda782ae31e7dade9e2"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb27c13225f5f77f3c5986a27ca27277cce7db12844330cf535019cd38021257"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe543b6d45da27e84e5af6248722475b88943d8ef40d835cbabbb0ba5ee786b"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c12224da0b1f2f9d2b3d715dc82ecb1a3a33b990606f97da075cc46bc6d9576f"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-win32.whl", hash = "sha256:86a1ad972e83ba48144884fa849f87626346308b650002157123aee67d3b16fe"},
+ {file = "fastcluster-1.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:8d3c9eab8e69cb36dcdd64c8b3200e008aedf88e34d39e01ae6af98a9605ad18"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c61be0bad81a21ee3e5bef91469fdd11968f33d41d142c656accba9b2992babe"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06df1d97edca68b2ffa43d81c3b5f4e4147bc12ab241c6585fadcdeb0bfa23ca"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab9337b0a6a9b07b6f86fc724972d1ad729c890e2f539c1b33271c2f1f00af8b"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4093d5454bcbe48b30e32da5db43056a08889480a96e4555f28c1f7004fc5323"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-win32.whl", hash = "sha256:58958a0333e3dfbad198394e9b7dd6254de0a3e622019b057288405b2a4a6bba"},
+ {file = "fastcluster-1.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:03f8efe6435a7b947fa4a420676942d0267dac0d323ec5ead50f1856cc7cf96f"},
+ {file = "fastcluster-1.2.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a5ceb39379327316d34613f7c16c06d7a3816aa38f4437b5e8433aa1bf149e2c"},
+ {file = "fastcluster-1.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0bb54283b4d5ec96f167c7fd31921f169226c1261637434fdae7a69ee3c69573"},
+ {file = "fastcluster-1.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e51db0067e65687a5c46f00a11843d0bb15ca759e8a1767eebac8c4f6e3f4df"},
+ {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11748a4e245c745030e9ddd8c2c37e378f8ad8bd8e869d954c84ff674495499f"},
+ {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7254f81dc71cd29ef6f2d9747cf97ff907b569c9ef9d9760352391be5b57118c"},
+ {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa4a4c01c5fbec3623e92bc33a9f712ca416ce93255c402f5c904ac4b890ac3c"},
+ {file = "fastcluster-1.2.6-cp38-cp38-win32.whl", hash = "sha256:ffdb00782cd63bbf2c45bb048897531e868326dff5081ab9b752d294b0426c1d"},
+ {file = "fastcluster-1.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:a952a84453123db0c2336b9a9c86162e99ad0b897bae8213107c055a64effd41"},
+ {file = "fastcluster-1.2.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a085e7e13f1afc517358981b2b7ed774dc9abf95f2be0da9a495d9e6b58c4409"},
+ {file = "fastcluster-1.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a7c7f51a6d2f5ab58b1d85e9d0af2af9600ec13bb43bc6aafc9085d2c4ccd93"},
+ {file = "fastcluster-1.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8bac5cf64691060cf86b0752dd385ef1eccff6d24bdb8b60691cf8cbf0e4f9ef"},
+ {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060c1cb3c84942d8d3618385e2c25998ba690c46ec8c73d64477f808abfac3f2"},
+ {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03a228e018457842eb81de85be7af0b5fe8065d666dd093193e3bdcf1f13d2e"},
+ {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6f8da329c0032f2acaf4beaef958a2db0dae43d3f946f592dad5c29aa82c832"},
+ {file = "fastcluster-1.2.6-cp39-cp39-win32.whl", hash = "sha256:eb3f98791427d5d5d02d023b66bcef61e48954edfadae6527ef72d70cf32ec86"},
+ {file = "fastcluster-1.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:4b9cfd426966b8037bec2fc03a0d7a9c87313482c699b36ffa1432b49f84ed2e"},
+ {file = "fastcluster-1.2.6.tar.gz", hash = "sha256:aab886efa7b6bba7ac124f4498153d053e5a08b822d2254926b7206cdf5a8aa6"},
+]
+fastjsonschema = [
+ {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"},
+ {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"},
+]
+fastrlock = [
+ {file = "fastrlock-0.8-cp27-cp27m-win32.whl", hash = "sha256:4d414a5e97a545fee64437586c70bd295d22ac49614eeb76fb67980e32a0d1ae"},
+ {file = "fastrlock-0.8-cp27-cp27m-win_amd64.whl", hash = "sha256:b991766baec0113829c5a10bec4c5ec987cab31dfcb1fabf53e370b83b939ef9"},
+ {file = "fastrlock-0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5356a15375fab5a8090f255b54122eead504ebcec8a67ac369489e3e315dfd21"},
+ {file = "fastrlock-0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8369ce6d228fc3efbfcff5499468200bd12c35efdbc787eeaf8064153bcf0d72"},
+ {file = "fastrlock-0.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09314c5c0f63fe252a3ee038af855d7fb5668d0ccbae94846a35b32487c6256c"},
+ {file = "fastrlock-0.8-cp35-cp35m-win32.whl", hash = "sha256:b724a7f5c1d5f9cdc30a13022e6e8cf319812c40dad0fd722b5ea18d2cfc4c4d"},
+ {file = "fastrlock-0.8-cp35-cp35m-win_amd64.whl", hash = "sha256:c7ec22a5726467af53950a8baba92327620c0f73c34fab930ce29b94c8645c7e"},
+ {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcd7a05ba34702d0f0e9751d35afb06be21034fcf00197567ff0dcdf48cde8e7"},
+ {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:997bc6c4fafda396449e2410a5d32b69d231fdccc5af6de6cefe9d67e5f7157e"},
+ {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ba3d3dc0dbb1cff2eee033a1f6369c24a86e8c20b427edfceff3f38b48b391c"},
+ {file = "fastrlock-0.8-cp36-cp36m-win32.whl", hash = "sha256:f365fc1de226ce96fedbb475c4523b304ce00f9a59beea620dba34e21239f5f3"},
+ {file = "fastrlock-0.8-cp36-cp36m-win_amd64.whl", hash = "sha256:7c9c66ab93877272169b017dc24f63c9c94b3f9d55b0b0b8c3f6d9644280120c"},
+ {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5d5fd2a76aa9cd451a8820e2427e512620062ffa62ea5732da0ab8888b6be6e"},
+ {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:340ae0734305d780aaad0893dba8185f4dbb0575d5fd696b5720eaf800526bbc"},
+ {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:719743b5b3c2b214fdf63096ac6a68615b4d9b8cb210e9e6ddf0293b38da2c9a"},
+ {file = "fastrlock-0.8-cp37-cp37m-win32.whl", hash = "sha256:1cfb5ac6336ff327359a31b690898adf9a813fd44a76196115a3f0afb2367c81"},
+ {file = "fastrlock-0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:5aa6b442b12fa1f9041248506c8ea5ae909a92aedd5a139647dce2ee87e5b944"},
+ {file = "fastrlock-0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af9df4c17c3142c9774cd1b9edea586f709c6421f02bcf7614507937b921ea7f"},
+ {file = "fastrlock-0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:951a5c4dc6fbe4481082752ce9f21aabcaa1cf6185f683d7b5cbae254f10bb46"},
+ {file = "fastrlock-0.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c75985ba70fb4e8af595a0f67f58b96e0dbc53562768208890b4b2ac25b4b52"},
+ {file = "fastrlock-0.8-cp38-cp38-win32.whl", hash = "sha256:0b459511349dc96c8961f1d43635bb94cfc2404a8899792a1f9aaa9998fa0eca"},
+ {file = "fastrlock-0.8-cp38-cp38-win_amd64.whl", hash = "sha256:7b612474ddacf808c663aa8020db0a78f54a665ccabd9dbe505d3d4c4b3f6044"},
+ {file = "fastrlock-0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe85f98fa66de41f929b8e01e4a9c5a7d20e660f605054b847c4ac54fadc8f79"},
+ {file = "fastrlock-0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d2029fb44eac8ab5f7aafb3fcde9419cbdaa07a08f72dac8cd7790dc0f73e596"},
+ {file = "fastrlock-0.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bba2a866c9f6eb1923ead5e70e1da02b3e57c531830eb4fcbd2d8e9530a2d7e"},
+ {file = "fastrlock-0.8-cp39-cp39-win32.whl", hash = "sha256:85c36ac55dc917c7e1f4fb03a841fe84b3fb2669dd0306bb94bf62c5749b1a7f"},
+ {file = "fastrlock-0.8-cp39-cp39-win_amd64.whl", hash = "sha256:4bb33625117bc9f1a66a5049a5d03ac4b38a44cb8eefdff1a394f7a435da54c9"},
+ {file = "fastrlock-0.8.tar.gz", hash = "sha256:9cc100ed0924b32173d7de705a82fdf1257cdf60af1952a13f64759307b40931"},
+]
+filelock = [
+ {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
+ {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
+]
+flake8 = [
+ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
+ {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
+]
+flask = [
+ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
+ {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
+]
+flask-cors = [
+ {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
+ {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
+]
+flask-socketio = [
+ {file = "Flask-SocketIO-5.3.1.tar.gz", hash = "sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9"},
+ {file = "Flask_SocketIO-5.3.1-py3-none-any.whl", hash = "sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353"},
+]
+flatbuffers = [
+ {file = "flatbuffers-22.9.24-py2.py3-none-any.whl", hash = "sha256:fc30f024e2eee55922d610f4d68626002fcd3c8f87d8058ec5ae9edd86993bcb"},
+]
+fonttools = [
+ {file = "fonttools-4.37.4-py3-none-any.whl", hash = "sha256:afae1b39555f9c3f0ad1f0f1daf678e5ad157e38c8842ecb567951bf1a9b9fd7"},
+ {file = "fonttools-4.37.4.zip", hash = "sha256:86918c150c6412798e15a0de6c3e0d061ddefddd00f97b4f7b43dfa867ad315e"},
+]
+frozenlist = [
+ {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"},
+ {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"},
+ {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"},
+ {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"},
+ {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"},
+ {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"},
+ {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"},
+ {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"},
+ {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"},
+ {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"},
+ {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"},
+ {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"},
+ {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"},
+ {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"},
+ {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"},
+ {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"},
+ {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"},
+ {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"},
+ {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"},
+ {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"},
+ {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"},
+ {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"},
+ {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"},
+ {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"},
+ {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"},
+ {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"},
+ {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"},
+ {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"},
+ {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"},
+ {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"},
+ {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"},
+ {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"},
+ {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"},
+ {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"},
+ {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"},
+ {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"},
+ {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"},
+ {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"},
+ {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"},
+ {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"},
+ {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"},
+ {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"},
+ {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"},
+ {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"},
+ {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"},
+ {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"},
+ {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"},
+]
+ft4222 = [
+ {file = "ft4222-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:64c80402e19ada10f142cf9d5f5b343a121689b94dfc31fafc7864db13ac7f79"},
+ {file = "ft4222-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5c713b6527513a77e674a6db60d97f67b18ce9f85727168ecbeef82557f2b2d1"},
+ {file = "ft4222-1.6.0-cp310-cp310-win32.whl", hash = "sha256:324d6330d501bf6f7746aa1b81f9540a2b385e9318bd4e6e4f744d7107f90c67"},
+ {file = "ft4222-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:1279115892441e4a2ab468429e5003c72f47423daa4d7179c0da4af3522c056a"},
+ {file = "ft4222-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:719ec8f9c05636d8306a64f8f6f86dd9ec27d80afb3fc07e7d0b01c9b3d83d1e"},
+ {file = "ft4222-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:886f3b53bad1dc238a3d6e5105b6d7442244ea854afea38daf84529491ad6e3d"},
+ {file = "ft4222-1.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c28fb526ec98afefebb24f0e333bfef0def3ca46f8b4f84712c5e6e3c7e0fe9c"},
+ {file = "ft4222-1.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f4b3ced3e5603f6bb22fcfcf9567a95ec47a93dd453a911e438c02d665bf657c"},
+ {file = "ft4222-1.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:376ddba0e44a24237d926bc28a519ef7877174726629c3335529f548a829e857"},
+ {file = "ft4222-1.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3dff18e3d95027fb7407748d9a92eece9a2e88c3f7521be59fc848fe32a3780f"},
+ {file = "ft4222-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c0673a1420bd10b3dd782d307c60516bcd4ff24f7c27661b319c781e8a0618df"},
+ {file = "ft4222-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de38894f69cd0253a4b683612006912c5d98fae94f07f910650ab5027dc3df8e"},
+ {file = "ft4222-1.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e3aae2cbd0a15f7dd9506971f32ab67dcf192c24b012258ccf2e0daeb099d8e5"},
+ {file = "ft4222-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f4d6d21e8e00b9c5aabaff48489973cf45452cb88a9d6946eaa6aea1d48c0794"},
+ {file = "ft4222-1.6.0-cp38-cp38-win32.whl", hash = "sha256:eeb1f8cf04262e2bcdaf159a767148a61ef91aa8b86950ae883bb8ba8f0e82da"},
+ {file = "ft4222-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:06419df94566e5e62358f7d68055fd4155b916646ead2c479e75b964548714e3"},
+ {file = "ft4222-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7a91689f9e44c6a8afedb6e5a11ba70d0c661e879607fd8635c76ebaa503cd90"},
+ {file = "ft4222-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:31976a15a7cd649814887e3887480f3f625e82e7e2d354f33d582a0faf4c7689"},
+ {file = "ft4222-1.6.0-cp39-cp39-win32.whl", hash = "sha256:b4c8309bbc2d0cf2704dd8737bcf2a6ae27284f7cf45746e79d3dbd4810e0cb3"},
+ {file = "ft4222-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:395596fd03120015b7a049928ba31983f7e71596bcdb85bd78eff4293e955266"},
+ {file = "ft4222-1.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:937124275e50adc87213fa3bda52e3ac08348454eee21b23e23ce4afd656035b"},
+ {file = "ft4222-1.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:eb9752990913ee327ebf1279f4a4847b54cacbf95aa920d24f07cb687a77572b"},
+ {file = "ft4222-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:37696c46340064e7518e4d77c8f8b044d72af26850f2e3370369201dd44b08b9"},
+ {file = "ft4222-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea7fdf9f5757e981445bbf80669f4f059912963f5718d16736c0f2b341a05610"},
+ {file = "ft4222-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:f242776c497d49f0533c643138375a59fe1745c159e43ec55845fe59c1f17020"},
+ {file = "ft4222-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5ba637e9c43c7aaa82915b06c4098ac885f2ae45152b1f01584307b8e6e5b005"},
+ {file = "ft4222-1.6.0.tar.gz", hash = "sha256:537de8f49c21fe49f4be16e0a359315e72322a6a513b153effc95c8105c6af06"},
+]
+future = [
+ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
+]
+future-fstrings = [
+ {file = "future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63"},
+ {file = "future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089"},
+]
+geoalchemy2 = [
+ {file = "GeoAlchemy2-0.12.5-py2.py3-none-any.whl", hash = "sha256:3a59eb651df95b3dfee8e1d82f4d18c80b75f712860a0a3080defc6b0435070d"},
+ {file = "GeoAlchemy2-0.12.5.tar.gz", hash = "sha256:31c2502dce317b57b335e4eb87562d501fa39e46c728be514d9b86091e08dd67"},
+]
+gevent = [
+ {file = "gevent-22.10.1-cp27-cp27m-win32.whl", hash = "sha256:702a51b8f21bad1976b0893f90ade466e8c27039b846b611ad2beb8c6e6ac701"},
+ {file = "gevent-22.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:af7baec79a5f8ad1cc132d3b14edd12661c628d8094e501b089b1fe2d3df7f6e"},
+ {file = "gevent-22.10.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf6dd33052919de8fb56e0bea0e6a7c7d6545281fe280ea78e311621c7adb50e"},
+ {file = "gevent-22.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f16c6937d47593f051fc3ac7996c819919082a1e7e0dec927cdae8771d26ed45"},
+ {file = "gevent-22.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21dbeb6d3b47093f40ca97aab493b2fb64b6f22112f88f56d79cf6f52a8c1c16"},
+ {file = "gevent-22.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:acb21bee2e66da45b8916073c8ae54c44629beb94d49120c188d27aff4ebf8dd"},
+ {file = "gevent-22.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2ea4ce36c09355379bc038be2bd50118f97d2eb6381b7096de4d05aa4c3e241"},
+ {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e73c9f71aa2a6795ecbec9b57282b002375e863e283558feb87b62840c8c1ac"},
+ {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc3758f0dc95007c1780d28a9fd2150416a79c50f308f62a674d78a845ea1b9"},
+ {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c10ca0beeab0c6be516030471ea630447ddd1f649d3335e5b162097cd4130a"},
+ {file = "gevent-22.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe2c0ff095171c49f78f1d4e6dc89fa58253783c7b6dccab9f1d76e2ee391f10"},
+ {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d18fcc324f39a3b21795022eb47c7752d6e4f4ed89d8cca41f1cc604553265b3"},
+ {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ea39c70ce166c4a1d4386c7fae96cb8d84ad799527b3378406051104d15443"},
+ {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a920a812b6c0c36d4613a15c254ca1ce415ee75ade0df3b8941ab61ae7ce3f"},
+ {file = "gevent-22.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:33c4cd095f99768ecc4b3bb75a12f52ea9df5c40a58671c667f86ea087a075e1"},
+ {file = "gevent-22.10.1-cp36-cp36m-win32.whl", hash = "sha256:db592cfe5106730667ac36f43554e7a869d757e411f8a08116c3739cee507145"},
+ {file = "gevent-22.10.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a12443b7326e40d00fb445d37bae154fd1f4693055330c6b4e68670ca3b6e6bf"},
+ {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5d64b208bec99adc7e0b6e08a3e2c296065c665ca725ca8da434c4ffc5aa302e"},
+ {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ac816f5e8e318c5fa27091ee43fcf4caaa6ae73a487e875a1a296a04c3da5af"},
+ {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0d9d6f869ba7c49d7f9b7d244dd20daec5cc87cd3e2e90209d6ed8172e0cad"},
+ {file = "gevent-22.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3c1cfd9f9fb6a2a5a9a04132d315db7fb819db019dea260695fe6e4012416f96"},
+ {file = "gevent-22.10.1-cp37-cp37m-win32.whl", hash = "sha256:4b0d29fc18ee338a85396facfc508e5f26e2e0e90f4c2889f8a9e74d341ad467"},
+ {file = "gevent-22.10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:36e47ca663081a71fca137b7c852e99e7ee3761082070c13aa2ae3b5b6234af6"},
+ {file = "gevent-22.10.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a838437b7b629328ad457cd36df454500afe7f3df4b971a6ff85851dfcf8c844"},
+ {file = "gevent-22.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c06c0f3f4f1b147f51a934fbf541880cee769492b98c4ebd3e930b5ff862646"},
+ {file = "gevent-22.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d701208d4d65dbdf9feb02561a75ecc5bd28300e47b59f74033a07b593887806"},
+ {file = "gevent-22.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e8c4ccc544f6e6c26ab10d0d6a7be86bd522222ce40f00bfafa01289f04bffc"},
+ {file = "gevent-22.10.1-cp38-cp38-win32.whl", hash = "sha256:4be5859af086de1ed85702c0a84479387087ddf49e38332c41861b0a10e96d8f"},
+ {file = "gevent-22.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:be43278781d39b4081f7f4d3e8ebb1dac188c9fe98f25da817325cb12c01887a"},
+ {file = "gevent-22.10.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cafb8f5399224990a5329bca3606bf399ee3604ae711b2d9238129b44551ad5"},
+ {file = "gevent-22.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e1c609f9e4171588006bea7ff41bb830ff27c27d071bbd311f91860fb5ef4cc"},
+ {file = "gevent-22.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dcfd8ef9dcca78c51dd266d0f4b48d9b7ea2592ae881bf66d8dbe59bb16631a"},
+ {file = "gevent-22.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefcb21fda3055f2e7eaad2e8098885a7bbddd83b174b012e2142db6b2b4c09d"},
+ {file = "gevent-22.10.1-cp39-cp39-win32.whl", hash = "sha256:3f7d11136b3ae6312effbc2ac0ed902ae718d86e7acb9a51cf927262cfb2931e"},
+ {file = "gevent-22.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ec5f77f629d997668983be53bad2a90d1b858a00e43b9e75e1c9a118c3a840b"},
+ {file = "gevent-22.10.1-pp27-pypy_73-win_amd64.whl", hash = "sha256:d8df3f628c8a9fb339b87a849dc2076e56d124e2169261fa58b4a01db3a335b6"},
+ {file = "gevent-22.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0569e133bb620de1001ac807ad9a8abaadedd25349c6d695f80c9048a3f59d42"},
+ {file = "gevent-22.10.1.tar.gz", hash = "sha256:df3042349c9a4460eeaec8d0e56d737cb183eed055e75a6af9dbda94aaddaf4d"},
+]
+greenlet = [
+ {file = "greenlet-1.1.3.post0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:949c9061b8c6d3e6e439466a9be1e787208dec6246f4ec5fffe9677b4c19fcc3"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d7815e1519a8361c5ea2a7a5864945906f8e386fa1bc26797b4d443ab11a4589"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9649891ab4153f217f319914455ccf0b86986b55fc0573ce803eb998ad7d6854"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27m-win32.whl", hash = "sha256:11fc7692d95cc7a6a8447bb160d98671ab291e0a8ea90572d582d57361360f05"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27m-win_amd64.whl", hash = "sha256:05ae7383f968bba4211b1fbfc90158f8e3da86804878442b4fb6c16ccbcaa519"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ccbe7129a282ec5797df0451ca1802f11578be018a32979131065565da89b392"},
+ {file = "greenlet-1.1.3.post0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8b58232f5b72973350c2b917ea3df0bebd07c3c82a0a0e34775fc2c1f857e9"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f6661b58412879a2aa099abb26d3c93e91dedaba55a6394d1fb1512a77e85de9"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c6e942ca9835c0b97814d14f78da453241837419e0d26f7403058e8db3e38f8"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a812df7282a8fc717eafd487fccc5ba40ea83bb5b13eb3c90c446d88dbdfd2be"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a7a6560df073ec9de2b7cb685b199dfd12519bc0020c62db9d1bb522f989fa"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17a69967561269b691747e7f436d75a4def47e5efcbc3c573180fc828e176d80"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:60839ab4ea7de6139a3be35b77e22e0398c270020050458b3d25db4c7c394df5"},
+ {file = "greenlet-1.1.3.post0-cp310-cp310-win_amd64.whl", hash = "sha256:8926a78192b8b73c936f3e87929931455a6a6c6c385448a07b9f7d1072c19ff3"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c6f90234e4438062d6d09f7d667f79edcc7c5e354ba3a145ff98176f974b8132"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814f26b864ed2230d3a7efe0336f5766ad012f94aad6ba43a7c54ca88dd77cba"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fda1139d87ce5f7bd80e80e54f9f2c6fe2f47983f1a6f128c47bf310197deb6"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0643250dd0756f4960633f5359884f609a234d4066686754e834073d84e9b51"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cb863057bed786f6622982fb8b2c122c68e6e9eddccaa9fa98fd937e45ee6c4f"},
+ {file = "greenlet-1.1.3.post0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8c0581077cf2734569f3e500fab09c0ff6a2ab99b1afcacbad09b3c2843ae743"},
+ {file = "greenlet-1.1.3.post0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:695d0d8b5ae42c800f1763c9fce9d7b94ae3b878919379150ee5ba458a460d57"},
+ {file = "greenlet-1.1.3.post0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5662492df0588a51d5690f6578f3bbbd803e7f8d99a99f3bf6128a401be9c269"},
+ {file = "greenlet-1.1.3.post0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:bffba15cff4802ff493d6edcf20d7f94ab1c2aee7cfc1e1c7627c05f1102eee8"},
+ {file = "greenlet-1.1.3.post0-cp35-cp35m-win32.whl", hash = "sha256:7afa706510ab079fd6d039cc6e369d4535a48e202d042c32e2097f030a16450f"},
+ {file = "greenlet-1.1.3.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:3a24f3213579dc8459e485e333330a921f579543a5214dbc935bc0763474ece3"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:64e10f303ea354500c927da5b59c3802196a07468332d292aef9ddaca08d03dd"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:eb6ac495dccb1520667cfea50d89e26f9ffb49fa28496dea2b95720d8b45eb54"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:88720794390002b0c8fa29e9602b395093a9a766b229a847e8d88349e418b28a"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39464518a2abe9c505a727af7c0b4efff2cf242aa168be5f0daa47649f4d7ca8"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0914f02fcaa8f84f13b2df4a81645d9e82de21ed95633765dd5cc4d3af9d7403"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96656c5f7c95fc02c36d4f6ef32f4e94bb0b6b36e6a002c21c39785a4eec5f5d"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4f74aa0092602da2069df0bc6553919a15169d77bcdab52a21f8c5242898f519"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3aeac044c324c1a4027dca0cde550bd83a0c0fbff7ef2c98df9e718a5086c194"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-win32.whl", hash = "sha256:fe7c51f8a2ab616cb34bc33d810c887e89117771028e1e3d3b77ca25ddeace04"},
+ {file = "greenlet-1.1.3.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:70048d7b2c07c5eadf8393e6398595591df5f59a2f26abc2f81abca09610492f"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:66aa4e9a726b70bcbfcc446b7ba89c8cec40f405e51422c39f42dfa206a96a05"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:025b8de2273d2809f027d347aa2541651d2e15d593bbce0d5f502ca438c54136"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:82a38d7d2077128a017094aff334e67e26194f46bd709f9dcdacbf3835d47ef5"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7d20c3267385236b4ce54575cc8e9f43e7673fc761b069c820097092e318e3b"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8ece5d1a99a2adcb38f69af2f07d96fb615415d32820108cd340361f590d128"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2794eef1b04b5ba8948c72cc606aab62ac4b0c538b14806d9c0d88afd0576d6b"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a8d24eb5cb67996fb84633fdc96dbc04f2d8b12bfcb20ab3222d6be271616b67"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0120a879aa2b1ac5118bce959ea2492ba18783f65ea15821680a256dfad04754"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-win32.whl", hash = "sha256:bef49c07fcb411c942da6ee7d7ea37430f830c482bf6e4b72d92fd506dd3a427"},
+ {file = "greenlet-1.1.3.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:62723e7eb85fa52e536e516ee2ac91433c7bb60d51099293671815ff49ed1c21"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d25cdedd72aa2271b984af54294e9527306966ec18963fd032cc851a725ddc1b"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:924df1e7e5db27d19b1359dc7d052a917529c95ba5b8b62f4af611176da7c8ad"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ec615d2912b9ad807afd3be80bf32711c0ff9c2b00aa004a45fd5d5dde7853d9"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0971d37ae0eaf42344e8610d340aa0ad3d06cd2eee381891a10fe771879791f9"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325f272eb997916b4a3fc1fea7313a8adb760934c2140ce13a2117e1b0a8095d"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75afcbb214d429dacdf75e03a1d6d6c5bd1fa9c35e360df8ea5b6270fb2211c"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5c2d21c2b768d8c86ad935e404cc78c30d53dea009609c3ef3a9d49970c864b5"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:467b73ce5dcd89e381292fb4314aede9b12906c18fab903f995b86034d96d5c8"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-win32.whl", hash = "sha256:8149a6865b14c33be7ae760bcdb73548bb01e8e47ae15e013bf7ef9290ca309a"},
+ {file = "greenlet-1.1.3.post0-cp38-cp38-win_amd64.whl", hash = "sha256:104f29dd822be678ef6b16bf0035dcd43206a8a48668a6cae4d2fe9c7a7abdeb"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:c8c9301e3274276d3d20ab6335aa7c5d9e5da2009cccb01127bddb5c951f8870"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8415239c68b2ec9de10a5adf1130ee9cb0ebd3e19573c55ba160ff0ca809e012"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:3c22998bfef3fcc1b15694818fc9b1b87c6cc8398198b96b6d355a7bcb8c934e"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa1845944e62f358d63fcc911ad3b415f585612946b8edc824825929b40e59e"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:890f633dc8cb307761ec566bc0b4e350a93ddd77dc172839be122be12bae3e10"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cf37343e43404699d58808e51f347f57efd3010cc7cee134cdb9141bd1ad9ea"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5edf75e7fcfa9725064ae0d8407c849456553a181ebefedb7606bac19aa1478b"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a954002064ee919b444b19c1185e8cce307a1f20600f47d6f4b6d336972c809"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-win32.whl", hash = "sha256:2ccdc818cc106cc238ff7eba0d71b9c77be868fdca31d6c3b1347a54c9b187b2"},
+ {file = "greenlet-1.1.3.post0-cp39-cp39-win_amd64.whl", hash = "sha256:91a84faf718e6f8b888ca63d0b2d6d185c8e2a198d2a7322d75c303e7097c8b7"},
+ {file = "greenlet-1.1.3.post0.tar.gz", hash = "sha256:f5e09dc5c6e1796969fd4b775ea1417d70e49a5df29aaa8e5d10675d9e11872c"},
+]
+gunicorn = [
+ {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
+ {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
+]
+h3 = [
+ {file = "h3-3.7.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e61d3c6b1b66072f5b74d46dbee7df29daac6ce9738b9a6223a67dc577114515"},
+ {file = "h3-3.7.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:960cd005b8817314d95fbaff3e848a72385df4e3c6c9703ff99b08581c8def69"},
+ {file = "h3-3.7.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:68227df989274b0da54de9101a50741c70c48197ba3beacfb97c88170445c18e"},
+ {file = "h3-3.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bf4d75fe42a260ac23bf4cb9f9de6e6f2aa37279b2719387711f3e0727c4653"},
+ {file = "h3-3.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c644ab3f221c7faaab2d1ccd11bc3b1106f172e9bb1c85a863b0a097f6b71cce"},
+ {file = "h3-3.7.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5366d24c2c01ef3bae68547c15f1965fac6053b2596c0073766bf7544ecaf0"},
+ {file = "h3-3.7.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83c2b0cd8259541f95b0493a620fb781b6a18c7c1e8fac1bda4fb234ae23ab43"},
+ {file = "h3-3.7.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1108a9acb755310dce50a6e3c58ae1a2460ef60901d40e1155d633c7392f858"},
+ {file = "h3-3.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce86c6dce2c923bfb16e26586bc5f0443a8be61d4f43227be8587ccb95588a46"},
+ {file = "h3-3.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad21cfa8d97a62984ce30692a7ddf71a32a0c744cc247c43cbdbac1536aec4de"},
+ {file = "h3-3.7.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be63482de86bbb91db7f3f3b7dd452b9e08a11dacbda2088386831fb0e7de59c"},
+ {file = "h3-3.7.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a96ea1844182bd0511cdcdc89e38e3026d9a3d4139fd0c5e899709edd798ffa"},
+ {file = "h3-3.7.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2faf304020493c5ffede34264bd28ed529b8b7238103e0904c0f3e9ca880bcfd"},
+ {file = "h3-3.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:3b1642085939c597a9c723ae3b187f80527ffc79cad0ded0e55be9c9bac69c6c"},
+ {file = "h3-3.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8d03622433da1a2761574311af378ff1ff841f5956db25927837c6aee9d1c13c"},
+ {file = "h3-3.7.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a09f7a43ed142573c602ef487a058da54ab4d86c173082b29a5057805fe2d3"},
+ {file = "h3-3.7.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469fdf90034b1a67e155cac4f46b077fdc404b6182ab33abcb7081c9bfbf411"},
+ {file = "h3-3.7.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:33b147ecc0e19ab1f27303d0e3ae28e5a457f3347ce18ca9a58b694a8b0cdd0a"},
+ {file = "h3-3.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:c95c0818c163b69989c9e876dd82005e60edfbaabfd45429abebfc26f9a357e8"},
+ {file = "h3-3.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b0ddfdd02920996d7c6672c91e83efb5432c67ff83f89a03f774e84bcfe19f8"},
+ {file = "h3-3.7.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a1284521d86cab414981056390be944dca780fe74c6c9e463a16d1c8d24871"},
+ {file = "h3-3.7.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:585f375ba2a95ceb16b115a378e9118159c912c26703cf1627f57a004818c3b3"},
+ {file = "h3-3.7.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdd68e684f0c6e18604d46ee04dbcfe5c79de62238b2c29f1db0f3a5d8dfa47b"},
+ {file = "h3-3.7.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a59d7d10597a2da9e9729637a625ae8dff2ed4e7c6c0b4952f0a5b2db6ef7152"},
+ {file = "h3-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:38a084d74b234d48aafc01e4329cd9a92966e3f45b8cf21224118643b6eaa1c0"},
+ {file = "h3-3.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a33fae02a54c63acb3c30fe49388715d658d76d42858a6ad4563e7e6859a9e57"},
+ {file = "h3-3.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b0277d82578b3ed3220167ef5c5acd8b4e0ef2fcd6c2fd69dbf29e0c4e03765"},
+ {file = "h3-3.7.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8155b2de1938eb56128fe4fd96e4f6d2022d4c34d8137bc95d73cbf329f8f89e"},
+ {file = "h3-3.7.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1387166f453816f91d624c6ce70876a3c20356cd28a3a759920dee23c78684cf"},
+ {file = "h3-3.7.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:62057c1c3d1c7fe492841e42fa360825d66fafd55ac37dc4e90b2292af21cb47"},
+ {file = "h3-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:25f0c22f4802ab71c45b86d206bd30fa0a6c7fbc3b630398b60c22907e9742e6"},
+ {file = "h3-3.7.4.tar.gz", hash = "sha256:f8edf5a546b31afdcd801b60448ea890ce1ff418fb784335e1329519f13aa85e"},
+]
+hatanaka = [
+ {file = "hatanaka-2.4.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:ef594d63473782fac46df5b0c92a59211a3efea1d47c1a964244a0abffc9f3f6"},
+ {file = "hatanaka-2.4.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fda4aa56f27313de75a806a2f5aa83ed5bb2dc7561bebab856a774d06cf1ee7"},
+ {file = "hatanaka-2.4.0-py3-none-win_amd64.whl", hash = "sha256:5a0624f6812b13abb4c996398a60338566885c1786841c4c04de9b1b91da28d2"},
+ {file = "hatanaka-2.4.0.tar.gz", hash = "sha256:c22970b99169bddaf22e5239672e856a6bc9602c435f8793d26ad49619a70a99"},
+]
+hexdump = [
+ {file = "hexdump-3.3.zip", hash = "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db"},
+]
+html5lib = [
+ {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"},
+ {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"},
+]
+humanfriendly = [
+ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
+ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
+]
+hypothesis = [
+ {file = "hypothesis-6.46.7-py3-none-any.whl", hash = "sha256:2696cdb9005946bf1d2b215cc91d3fc01625e3342eb8743ddd04b667b2f1882b"},
+ {file = "hypothesis-6.46.7.tar.gz", hash = "sha256:967009fa561b3a3f8363a73d71923357271c37dc7fa27b30c2d21a1b6092b240"},
+]
+identify = [
+ {file = "identify-2.5.6-py2.py3-none-any.whl", hash = "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"},
+ {file = "identify-2.5.6.tar.gz", hash = "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245"},
+]
+idna = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+imageio = [
+ {file = "imageio-2.22.2-py3-none-any.whl", hash = "sha256:9bdafe9c5a3d336a187f3f554f3e30bcdbf8a1d7d46f0e4d94e4a535adfb64c7"},
+ {file = "imageio-2.22.2.tar.gz", hash = "sha256:db7010cd10712518819a4187baf61b05988361ea20c23e829918727b27acb977"},
+]
+imagesize = [
+ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+importlib-metadata = [
+ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"},
+ {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"},
+]
+importlib-resources = [
+ {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"},
+ {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"},
+]
+influxdb-client = [
+ {file = "influxdb_client-1.33.0-py3-none-any.whl", hash = "sha256:38a8bedce673ffd2211f60012b5848cb2418977c19f83a43454882b665acbc69"},
+ {file = "influxdb_client-1.33.0.tar.gz", hash = "sha256:45f6a1763804a19b972890daf4c5f0bd2d3ae2f202b86451c579e09dcadd30e6"},
+]
+iniconfig = [
+ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+inputs = [
+ {file = "inputs-0.5-py2.py3-none-any.whl", hash = "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec"},
+ {file = "inputs-0.5.tar.gz", hash = "sha256:a31d5b96a3525f1232f326be9e7ce8ccaf873c6b1fb84d9f3c9bc3d79b23eae4"},
+]
+ipykernel = [
+ {file = "ipykernel-6.16.1-py3-none-any.whl", hash = "sha256:32eb7bdc5af57185e9a42b0dcef66413ef91a0490b378eae46cbdf0d4e0b5912"},
+ {file = "ipykernel-6.16.1.tar.gz", hash = "sha256:3a27a550c1d682e7825f0f7732b0142b79ef1b21cd2e713cacac0c9847535f13"},
+]
+ipython = [
+ {file = "ipython-8.5.0-py3-none-any.whl", hash = "sha256:6f090e29ab8ef8643e521763a4f1f39dc3914db643122b1e9d3328ff2e43ada2"},
+ {file = "ipython-8.5.0.tar.gz", hash = "sha256:097bdf5cd87576fd066179c9f7f208004f7a6864ee1b20f37d346c0bcb099f84"},
+]
+ipython-genutils = [
+ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
+ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
+]
+ipywidgets = [
+ {file = "ipywidgets-8.0.2-py3-none-any.whl", hash = "sha256:1dc3dd4ee19ded045ea7c86eb273033d238d8e43f9e7872c52d092683f263891"},
+ {file = "ipywidgets-8.0.2.tar.gz", hash = "sha256:08cb75c6e0a96836147cbfdc55580ae04d13e05d26ffbc377b4e1c68baa28b1f"},
+]
+isodate = [
+ {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"},
+ {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"},
+]
+isort = [
+ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+ {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
+itsdangerous = [
+ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+ {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+jaraco-classes = [
+ {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"},
+ {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"},
+]
+jedi = [
+ {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
+ {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
+]
+jeepney = [
+ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"},
+ {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"},
+]
+jinja2 = [
+ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
+jmespath = [
+ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"},
+ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
+]
+joblib = [
+ {file = "joblib-1.2.0-py3-none-any.whl", hash = "sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385"},
+ {file = "joblib-1.2.0.tar.gz", hash = "sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018"},
+]
+json-logging-py = [
+ {file = "json-logging-py-0.2.tar.gz", hash = "sha256:118b1fe1f4eacaea6370e5b9710d0f6d0c0a4599aef9d5b9875a6a579974fc9a"},
+]
+json-rpc = [
+ {file = "json-rpc-1.13.0.tar.gz", hash = "sha256:def0dbcf5b7084fc31d677f2f5990d988d06497f2f47f13024274cfb2d5d7589"},
+ {file = "json_rpc-1.13.0-py2.py3-none-any.whl", hash = "sha256:84b45058e5ba95f49c7b6afcf7e03ab86bee89bf2c01f3ad8dd41fe114fc1f84"},
+]
+json5 = [
+ {file = "json5-0.9.10-py2.py3-none-any.whl", hash = "sha256:993189671e7412e9cdd8be8dc61cf402e8e579b35f1d1bb20ae6b09baa78bbce"},
+ {file = "json5-0.9.10.tar.gz", hash = "sha256:ad9f048c5b5a4c3802524474ce40a622fae789860a86f10cc4f7e5f9cf9b46ab"},
+]
+jsonschema = [
+ {file = "jsonschema-4.16.0-py3-none-any.whl", hash = "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9"},
+ {file = "jsonschema-4.16.0.tar.gz", hash = "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23"},
+]
+jupyter = [
+ {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"},
+ {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"},
+ {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"},
+]
+jupyter-client = [
+ {file = "jupyter_client-7.4.3-py3-none-any.whl", hash = "sha256:8845e3f5a339734b1ecc21d2100638aa1c7a145e356a31845f155cda5b624b1c"},
+ {file = "jupyter_client-7.4.3.tar.gz", hash = "sha256:4fa2514cdb54dd9fbdcf7d7e4c5c3c8a973028168a8b4fc097b6aef625be13b0"},
+]
+jupyter-console = [
+ {file = "jupyter_console-6.4.4-py3-none-any.whl", hash = "sha256:756df7f4f60c986e7bc0172e4493d3830a7e6e75c08750bbe59c0a5403ad6dee"},
+ {file = "jupyter_console-6.4.4.tar.gz", hash = "sha256:172f5335e31d600df61613a97b7f0352f2c8250bbd1092ef2d658f77249f89fb"},
+]
+jupyter-core = [
+ {file = "jupyter_core-4.11.2-py3-none-any.whl", hash = "sha256:3815e80ec5272c0c19aad087a0d2775df2852cfca8f5a17069e99c9350cecff8"},
+ {file = "jupyter_core-4.11.2.tar.gz", hash = "sha256:c2909b9bc7dca75560a6c5ae78c34fd305ede31cd864da3c0d0bb2ed89aa9337"},
+]
+jupyter-server = [
+ {file = "jupyter_server-1.21.0-py3-none-any.whl", hash = "sha256:992531008544d77e05a16251cdfbd0bdff1b1efa14760c79b9cc776ac9214cf1"},
+ {file = "jupyter_server-1.21.0.tar.gz", hash = "sha256:d0adca19913a3763359be7f0b8c2ea8bfde356f4b8edd8e3149d7d0fbfaa248b"},
+]
+jupyterlab = [
+ {file = "jupyterlab-3.4.8-py3-none-any.whl", hash = "sha256:4626a0434c76a3a22f11c4efaa1d031d2586367f72cfdbdbff6b08b6ef0060f7"},
+ {file = "jupyterlab-3.4.8.tar.gz", hash = "sha256:1fafb8b657005d91603f3c3adfd6d9e8eaf33fdc601537fef09283332efe67cb"},
+]
+jupyterlab-pygments = [
+ {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"},
+ {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"},
+]
+jupyterlab-server = [
+ {file = "jupyterlab_server-2.16.1-py3-none-any.whl", hash = "sha256:b572cd3e59b0722120f41d47f2363a0072765227184aea418b7cc276db4d75fd"},
+ {file = "jupyterlab_server-2.16.1.tar.gz", hash = "sha256:fe0de558ff3bb447a32e24099aa7e17444fdbc8c08f6dbc0171cb1a0ae382d3f"},
+]
+jupyterlab-vim = [
+ {file = "jupyterlab_vim-0.15.1-py3-none-any.whl", hash = "sha256:e3ea4a0f140bb2956fa7c3b75442121cce264930fb94f06e5b0f75ccae2bde2e"},
+ {file = "jupyterlab_vim-0.15.1.tar.gz", hash = "sha256:c4282beb4a67d21f7126e32cda19f99ed324a1d3fc494560e393fe53f7519a74"},
+]
+jupyterlab-widgets = [
+ {file = "jupyterlab_widgets-3.0.3-py3-none-any.whl", hash = "sha256:6aa1bc0045470d54d76b9c0b7609a8f8f0087573bae25700a370c11f82cb38c8"},
+ {file = "jupyterlab_widgets-3.0.3.tar.gz", hash = "sha256:c767181399b4ca8b647befe2d913b1260f51bf9d8ef9b7a14632d4c1a7b536bd"},
+]
+keyring = [
+ {file = "keyring-23.9.3-py3-none-any.whl", hash = "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0"},
+ {file = "keyring-23.9.3.tar.gz", hash = "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5"},
+]
+kiwisolver = [
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"},
+ {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"},
+]
+knack = [
+ {file = "knack-0.10.0-py3-none-any.whl", hash = "sha256:9e989286622d4a028ba738111f9fac475bae93146d375522141908ce43077cda"},
+ {file = "knack-0.10.0.tar.gz", hash = "sha256:13190fa95d4c21bce04b4bee22d6a4e3fb19a93b6999b1d104bd02c476706c28"},
+]
+lazy-object-proxy = [
+ {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"},
+ {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"},
+ {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"},
+ {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"},
+ {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"},
+ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
+ {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
+]
+libusb1 = [
+ {file = "libusb1-3.0.0-py3-none-any.whl", hash = "sha256:0e652b04cbe85ec8e74f9ee82b49f861fb14b5320ae51399387ad2601ccc0500"},
+ {file = "libusb1-3.0.0-py3-none-win32.whl", hash = "sha256:083f75e5d15cb5e2159e64c308c5317284eae926a820d6dce53a9505d18be3b2"},
+ {file = "libusb1-3.0.0-py3-none-win_amd64.whl", hash = "sha256:6f6bb010632ada35c661d17a65e135077beef0fbb2434d5ffdb3a4a911fd9490"},
+ {file = "libusb1-3.0.0.tar.gz", hash = "sha256:5792a9defee40f15d330a40d9b1800545c32e47ba7fc66b6f28f133c9fcc8538"},
+]
+lockfile = [
+ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"},
+ {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"},
+]
+lru-dict = [
+ {file = "lru-dict-1.1.8.tar.gz", hash = "sha256:878bc8ef4073e5cfb953dfc1cf4585db41e8b814c0106abde34d00ee0d0b3115"},
+ {file = "lru_dict-1.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9d5815c0e85922cd0fb8344ca8b1c7cf020bf9fc45e670d34d51932c91fd7ec"},
+ {file = "lru_dict-1.1.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f877f53249c3e49bbd7612f9083127290bede6c7d6501513567ab1bf9c581381"},
+ {file = "lru_dict-1.1.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fef595c4f573141d54a38bda9221b9ee3cbe0acc73d67304a1a6d5972eb2a02"},
+ {file = "lru_dict-1.1.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:db20597c4e67b4095b376ce2e83930c560f4ce481e8d05737885307ed02ba7c1"},
+ {file = "lru_dict-1.1.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5b09dbe47bc4b4d45ffe56067aff190bc3c0049575da6e52127e114236e0a6a7"},
+ {file = "lru_dict-1.1.8-cp310-cp310-win32.whl", hash = "sha256:3b1692755fef288b67af5cd8a973eb331d1f44cb02cbdc13660040809c2bfec6"},
+ {file = "lru_dict-1.1.8-cp310-cp310-win_amd64.whl", hash = "sha256:8f6561f9cd5a452cb84905c6a87aa944fdfdc0f41cc057d03b71f9b29b2cc4bd"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ca8f89361e0e7aad0bf93ae03a31502e96280faeb7fb92267f4998fb230d36b2"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c50ab9edaa5da5838426816a2b7bcde9d576b4fc50e6a8c062073dbc4969d78"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe16ade5fd0a57e9a335f69b8055aaa6fb278fbfa250458e4f6b8255115578f"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de972c7f4bc7b6002acff2a8de984c55fbd7f2289dba659cfd90f7a0f5d8f5d1"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3d003a864899c29b0379e412709a6e516cbd6a72ee10b09d0b33226343617412"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-win32.whl", hash = "sha256:6e2a7aa9e36626fb48fdc341c7e3685a31a7b50ea4918677ea436271ad0d904d"},
+ {file = "lru_dict-1.1.8-cp36-cp36m-win_amd64.whl", hash = "sha256:d2ed4151445c3f30423c2698f72197d64b27b1cd61d8d56702ffe235584e47c2"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:075b9dd46d7022b675419bc6e3631748ae184bc8af195d20365a98b4f3bb2914"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70364e3cbef536adab8762b4835e18f5ca8e3fddd8bd0ec9258c42bbebd0ee77"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720f5728e537f11a311e8b720793a224e985d20e6b7c3d34a891a391865af1a2"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c2fe692332c2f1d81fd27457db4b35143801475bfc2e57173a2403588dd82a42"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:86d32a4498b74a75340497890a260d37bf1560ad2683969393032977dd36b088"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-win32.whl", hash = "sha256:348167f110494cfafae70c066470a6f4e4d43523933edf16ccdb8947f3b5fae0"},
+ {file = "lru_dict-1.1.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9be6c4039ef328676b868acea619cd100e3de1a35b3be211cf0eaf9775563b65"},
+ {file = "lru_dict-1.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a777d48319d293b1b6a933d606c0e4899690a139b4c81173451913bbcab6f44f"},
+ {file = "lru_dict-1.1.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99f6cfb3e28490357a0805b409caf693e46c61f8dbb789c51355adb693c568d3"},
+ {file = "lru_dict-1.1.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:163079dbda54c3e6422b23da39fb3ecc561035d65e8496ff1950cbdb376018e1"},
+ {file = "lru_dict-1.1.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0972d669e9e207617e06416166718b073a49bf449abbd23940d9545c0847a4d9"},
+ {file = "lru_dict-1.1.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:97c24ffc55de6013075979f440acd174e88819f30387074639fb7d7178ca253e"},
+ {file = "lru_dict-1.1.8-cp38-cp38-win32.whl", hash = "sha256:0f83cd70a6d32f9018d471be609f3af73058f700691657db4a3d3dd78d3f96dd"},
+ {file = "lru_dict-1.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:add762163f4af7f4173fafa4092eb7c7f023cf139ef6d2015cfea867e1440d82"},
+ {file = "lru_dict-1.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ac524e4615f06dc72ffbfd83f26e073c9ec256de5413634fbd024c010a8bc"},
+ {file = "lru_dict-1.1.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7284bdbc5579bbdc3fc8f869ed4c169f403835566ab0f84567cdbfdd05241847"},
+ {file = "lru_dict-1.1.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca497cb25f19f24171f9172805f3ff135b911aeb91960bd4af8e230421ccb51"},
+ {file = "lru_dict-1.1.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1df1da204a9f0b5eb8393a46070f1d984fa8559435ee790d7f8f5602038fc00"},
+ {file = "lru_dict-1.1.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f4d0a6d733a23865019b1c97ed6fb1fdb739be923192abf4dbb644f697a26a69"},
+ {file = "lru_dict-1.1.8-cp39-cp39-win32.whl", hash = "sha256:7be1b66926277993cecdc174c15a20c8ce785c1f8b39aa560714a513eef06473"},
+ {file = "lru_dict-1.1.8-cp39-cp39-win_amd64.whl", hash = "sha256:881104711900af45967c2e5ce3e62291dd57d5b2a224d58b7c9f60bf4ad41b8c"},
+ {file = "lru_dict-1.1.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:beb089c46bd95243d1ac5b2bd13627317b08bf40dd8dc16d4b7ee7ecb3cf65ca"},
+ {file = "lru_dict-1.1.8-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10fe823ff90b655f0b6ba124e2b576ecda8c61b8ead76b456db67831942d22f2"},
+ {file = "lru_dict-1.1.8-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07163c9dcbb2eca377f366b1331f46302fd8b6b72ab4d603087feca00044bb0"},
+ {file = "lru_dict-1.1.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93336911544ebc0e466272043adab9fb9f6e9dcba6024b639c32553a3790e089"},
+ {file = "lru_dict-1.1.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55aeda6b6789b2d030066b4f5f6fc3596560ba2a69028f35f3682a795701b5b1"},
+ {file = "lru_dict-1.1.8-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262a4e622010ceb960a6a5222ed011090e50954d45070fd369c0fa4d2ed7d9a9"},
+ {file = "lru_dict-1.1.8-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6f64005ede008b7a866be8f3f6274dbf74e656e15e4004e9d99ad65efb01809"},
+ {file = "lru_dict-1.1.8-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9d70257246b8207e8ef3d8b18457089f5ff0dfb087bd36eb33bce6584f2e0b3a"},
+ {file = "lru_dict-1.1.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f874e9c2209dada1a080545331aa1277ec060a13f61684a8642788bf44b2325f"},
+ {file = "lru_dict-1.1.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a592363c93d6fc6472d5affe2819e1c7590746aecb464774a4f67e09fbefdfc"},
+ {file = "lru_dict-1.1.8-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f340b61f3cdfee71f66da7dbfd9a5ea2db6974502ccff2065cdb76619840dca"},
+ {file = "lru_dict-1.1.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9447214e4857e16d14158794ef01e4501d8fad07d298d03308d9f90512df02fa"},
+]
+lxml = [
+ {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"},
+ {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"},
+ {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
+ {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"},
+ {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"},
+ {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"},
+ {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"},
+ {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"},
+ {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"},
+ {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"},
+ {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"},
+ {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"},
+ {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"},
+ {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"},
+ {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"},
+ {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"},
+ {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"},
+ {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"},
+ {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"},
+ {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"},
+ {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"},
+ {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"},
+ {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"},
+ {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"},
+ {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"},
+ {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"},
+ {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"},
+ {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"},
+ {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"},
+ {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"},
+ {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"},
+ {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"},
+ {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"},
+ {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"},
+ {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"},
+ {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"},
+ {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"},
+ {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"},
+ {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"},
+ {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"},
+ {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"},
+ {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"},
+ {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"},
+ {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"},
+ {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"},
+ {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"},
+ {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"},
+ {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
+]
+mako = [
+ {file = "Mako-1.2.3-py3-none-any.whl", hash = "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f"},
+ {file = "Mako-1.2.3.tar.gz", hash = "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd"},
+]
+markdown = [
+ {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"},
+ {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"},
+]
+markdown-it-py = [
+ {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"},
+ {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"},
+]
+markupsafe = [
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
+ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
+]
+matplotlib = [
+ {file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:7730e60e751cfcfe7fcb223cf03c0b979e9a064c239783ad37929d340a364cef"},
+ {file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9dd40505ccc526acaf9a5db1b3029e237c64b58f1249983b28a291c2d6a1d0fa"},
+ {file = "matplotlib-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85948b303534b69fd771126764cf883fde2af9b003eb5778cb60f3b46f93d3f6"},
+ {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71eced071825005011cdc64efbae2e2c76b8209c18aa487dedf69796fe4b1e40"},
+ {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220314c2d6b9ca11570d7cd4b841c9f3137546f188336003b9fb8def4dcb804d"},
+ {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc5d726d4d42865f909c5208a7841109d76584950dd0587b01a77cc279d4ab7"},
+ {file = "matplotlib-3.6.1-cp310-cp310-win32.whl", hash = "sha256:183bf3ac6a6023ee590aa4b677f391ceed65ec0d6b930901a8483c267bd12995"},
+ {file = "matplotlib-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:a68b91ac7e6bb26100a540a033f54c95fe06d9c0aa51312c2a52d07d1bde78f4"},
+ {file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:4648f0d79a87bf50ee740058305c91091ee5e1fbb71a7d2f5fe6707bfe328d1c"},
+ {file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9403764017d20ff570f7ce973a8b9637f08a6109118f4e0ce6c7493d8849a0d3"},
+ {file = "matplotlib-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4c8b5a243dd29d50289d694e931bd6cb6ae0b5bd654d12c647543d63862540c"},
+ {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1effccef0cea2d4da9feeed22079adf6786f92c800a7d0d2ef2104318a1c66c"},
+ {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dc25473319afabe49150267e54648ac559c33b0fc2a80c8caecfbbc2948a820"},
+ {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47cb088bbce82ae9fc2edf3c25e56a5c6142ce2553fea2b781679f960a70c207"},
+ {file = "matplotlib-3.6.1-cp311-cp311-win32.whl", hash = "sha256:4d3b0e0a4611bd22065bbf47e9b2f689ac9e575bcb850a9f0ae2bbed75cab956"},
+ {file = "matplotlib-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:e3c116e779fbbf421a9e4d3060db259a9bb486d98f4e3c5a0877c599bd173582"},
+ {file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:565f514dec81a41cbed10eb6011501879695087fc2787fb89423a466508abbbd"},
+ {file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:05e86446562063d6186ff6d700118c0dbd5dccc403a6187351ee526c48878f10"},
+ {file = "matplotlib-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8245e85fd793f58edf29b8a9e3be47e8ecf76ea1a1e8240545f2746181ca5787"},
+ {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1e2c75d5d1ff6b7ef9870360bfa23bea076b8dc0945a60d19453d7619ed9ea8f"},
+ {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9756a8e69f6e1f76d47eb42132175b6814da1fbeae0545304c6d0fc2aae252a"},
+ {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f5788168da2661b42f7468063b725cc73fdbeeb80f2704cb2d8c415e9a57c50"},
+ {file = "matplotlib-3.6.1-cp38-cp38-win32.whl", hash = "sha256:0bab7564aafd5902128d54b68dca04f5755413fb6b502100bb0235a545882c48"},
+ {file = "matplotlib-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3c53486278a0629fd892783271dc994b962fba8dfe207445d039e14f1928ea46"},
+ {file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:27337bcb38d5db7430c14f350924542d75416ec1546d5d9d9f39b362b71db3fb"},
+ {file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fad858519bd6d52dbfeebdbe04d00dd8e932ed436f1c535e61bcc970a96c11e4"},
+ {file = "matplotlib-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a3d903588b519b38ed085d0ae762a1dcd4b70164617292175cfd91b90d6c415"},
+ {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bdbd37d0a41e025879863fe9b17bab15c0421313bc33e77e5e1aa54215c9c5"},
+ {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e632f66218811d4cf8b7a2a649e25ec15406c3c498f72d19e2bcf8377f38445d"},
+ {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ddd58324dc9a77e2e56d7b7aea7dbd0575b6f7cd1333c3ca9d388ac70978344"},
+ {file = "matplotlib-3.6.1-cp39-cp39-win32.whl", hash = "sha256:12ab21d0cad122f5b23688d453a0280676e7c42f634f0dbd093d15d42d142b1f"},
+ {file = "matplotlib-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:563896ba269324872ace436a57775dcc8322678a9496b28a8c25cdafa5ec2b92"},
+ {file = "matplotlib-3.6.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:52935b7d4ccbf0dbc9cf454dbb10ca99c11cbe8da9467596b96e5e21fd4dfc5c"},
+ {file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87027ff7b2edeb14476900261ef04d4beae949e1dfa0a3eb3ad6a6efbf9d0e1d"},
+ {file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4de03085afb3b80fab341afaf8e60dfe06ce439b6dfed55d657cf34a7bc3c40"},
+ {file = "matplotlib-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b53387d4e59432ff221540a4ffb5ee9669c69417805e4faf0148a00d701c61f9"},
+ {file = "matplotlib-3.6.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:02561141c434154f7bae8e5449909d152367cb40aa57bfb2a27f2748b9c5f95f"},
+ {file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0161ebf87518ecfe0980c942d5f0d5df0e080c1746ebaab2027a969967014b7"},
+ {file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2469f57e4c5cc0e85eddc7b30995ea9c404a78c0b1856da75d1a5887156ca350"},
+ {file = "matplotlib-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5f97141e05baf160c3ec125f06ceb2a44c9bb62f42fcb8ee1c05313c73e99432"},
+ {file = "matplotlib-3.6.1.tar.gz", hash = "sha256:e2d1b7225666f7e1bcc94c0bc9c587a82e3e8691da4757e357e5c2515222ee37"},
+]
+matplotlib-inline = [
+ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
+ {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
+]
+mccabe = [
+ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+mdit-py-plugins = [
+ {file = "mdit-py-plugins-0.3.1.tar.gz", hash = "sha256:3fc13298497d6e04fe96efdd41281bfe7622152f9caa1815ea99b5c893de9441"},
+ {file = "mdit_py_plugins-0.3.1-py3-none-any.whl", hash = "sha256:606a7f29cf56dbdfaf914acb21709b8f8ee29d857e8f29dcc33d8cb84c57bfa1"},
+]
+mdurl = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+mistune = [
+ {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"},
+ {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"},
+]
+more-itertools = [
+ {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"},
+ {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"},
+]
+mpld3 = [
+ {file = "mpld3-0.5.8-py3-none-any.whl", hash = "sha256:41938e65de4ba41a1b53d92e7c8609e7172e09b33ef5db42bb6f73701106c8b7"},
+ {file = "mpld3-0.5.8.tar.gz", hash = "sha256:1a167dbef836dd7c66d8aa71c06a32d50bffa18725f304d93cb74fdb3545043b"},
+]
+mpmath = [
+ {file = "mpmath-1.2.1-py3-none-any.whl", hash = "sha256:604bc21bd22d2322a177c73bdb573994ef76e62edd595d17e00aff24b0667e5c"},
+ {file = "mpmath-1.2.1.tar.gz", hash = "sha256:79ffb45cf9f4b101a807595bcb3e72e0396202e0b1d25d689134b48c4216a81a"},
+]
+msal = [
+ {file = "msal-1.20.0b1-py2.py3-none-any.whl", hash = "sha256:07805ade58ce76bdaa4f6a3f7e61477901d5145abd7959a850eb6c49a2fbaea6"},
+ {file = "msal-1.20.0b1.tar.gz", hash = "sha256:8c0d7238b7bc5abe86515568c4fd59e53c5ccca159ef02b369f867b951480c4d"},
+]
+msal-extensions = [
+ {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"},
+ {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"},
+]
+msgpack = [
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"},
+ {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"},
+ {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"},
+ {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"},
+ {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"},
+ {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"},
+ {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"},
+ {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"},
+ {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"},
+ {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"},
+ {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"},
+ {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"},
+ {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"},
+ {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"},
+ {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"},
+ {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"},
+ {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"},
+ {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"},
+ {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"},
+ {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"},
+ {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"},
+ {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"},
+ {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"},
+ {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"},
+ {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"},
+ {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"},
+ {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"},
+]
+msgpack-numpy = [
+ {file = "msgpack-numpy-0.4.8.tar.gz", hash = "sha256:c667d3180513422f9c7545be5eec5d296dcbb357e06f72ed39cc683797556e69"},
+ {file = "msgpack_numpy-0.4.8-py2.py3-none-any.whl", hash = "sha256:773c19d4dfbae1b3c7b791083e2caf66983bb19b40901646f61d8731554ae3da"},
+]
+msgpack-python = [
+ {file = "msgpack-python-0.5.6.tar.gz", hash = "sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"},
+]
+msrest = [
+ {file = "msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32"},
+ {file = "msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9"},
+]
+msrestazure = [
+ {file = "msrestazure-0.6.4-py2.py3-none-any.whl", hash = "sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9"},
+ {file = "msrestazure-0.6.4.tar.gz", hash = "sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189"},
+]
+multidict = [
+ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
+ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
+ {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"},
+ {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"},
+ {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"},
+ {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"},
+ {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"},
+ {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"},
+ {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"},
+ {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"},
+ {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"},
+ {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"},
+ {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"},
+ {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"},
+ {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"},
+ {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"},
+ {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"},
+ {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"},
+ {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"},
+ {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"},
+ {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"},
+ {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"},
+ {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"},
+ {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"},
+ {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"},
+ {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"},
+ {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"},
+ {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"},
+ {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"},
+ {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"},
+ {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"},
+ {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"},
+ {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"},
+ {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"},
+ {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"},
+ {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"},
+ {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"},
+ {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"},
+ {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"},
+ {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"},
+ {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"},
+ {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"},
+ {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"},
+ {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"},
+ {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"},
+ {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"},
+ {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"},
+ {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"},
+ {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"},
+ {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"},
+ {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"},
+ {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"},
+ {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"},
+ {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"},
+ {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"},
+ {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"},
+ {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"},
+ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
+ {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
+]
+munch = [
+ {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"},
+ {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"},
+]
+mypy = [
+ {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
+ {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
+ {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
+ {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
+ {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
+ {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
+ {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
+ {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
+ {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
+ {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
+ {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
+ {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
+ {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
+ {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
+ {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
+ {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
+ {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
+ {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
+ {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
+ {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
+ {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
+ {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
+ {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
+]
+mypy-extensions = [
+ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+myst-parser = [
+ {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"},
+ {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"},
+]
+natsort = [
+ {file = "natsort-8.2.0-py3-none-any.whl", hash = "sha256:04fe18fdd2b9e5957f19f687eb117f102ef8dde6b574764e536e91194bed4f5f"},
+ {file = "natsort-8.2.0.tar.gz", hash = "sha256:57f85b72c688b09e053cdac302dd5b5b53df5f73ae20b4874fcbffd8bf783d11"},
+]
+nbclassic = [
+ {file = "nbclassic-0.4.7-py3-none-any.whl", hash = "sha256:d71d18aa6605eaf59e9b99b34c96360af45847f2a30ee2fefbe2f7bed4bc3df2"},
+ {file = "nbclassic-0.4.7.tar.gz", hash = "sha256:1e0470583b55089c427940ed31b8a866ffef7ccab101494e409efe5ac7ba9897"},
+]
+nbclient = [
+ {file = "nbclient-0.7.0-py3-none-any.whl", hash = "sha256:434c91385cf3e53084185334d675a0d33c615108b391e260915d1aa8e86661b8"},
+ {file = "nbclient-0.7.0.tar.gz", hash = "sha256:a1d844efd6da9bc39d2209bf996dbd8e07bf0f36b796edfabaa8f8a9ab77c3aa"},
+]
+nbconvert = [
+ {file = "nbconvert-7.2.2-py3-none-any.whl", hash = "sha256:fc7a3787c927cbd45a52e088e934fbbaab39afe61767522168a724b7483992be"},
+ {file = "nbconvert-7.2.2.tar.gz", hash = "sha256:24acfaa466d2c9b7eb524800e4a45afbed862c5d058cfb30fc7aa24d448c95eb"},
+]
+nbformat = [
+ {file = "nbformat-5.7.0-py3-none-any.whl", hash = "sha256:1b05ec2c552c2f1adc745f4eddce1eac8ca9ffd59bb9fd859e827eaa031319f9"},
+ {file = "nbformat-5.7.0.tar.gz", hash = "sha256:1d4760c15c1a04269ef5caf375be8b98dd2f696e5eb9e603ec2bf091f9b0d3f3"},
+]
+ncompress = [
+ {file = "ncompress-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0349d7de11edd70a7efea9ce9dc67f0e47b5774832dd063f7ae68a9e3e36ea31"},
+ {file = "ncompress-1.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af0011bae90e44121f4e4edbff3dccdce7e4c5fc5e354db7eb48410d71f496df"},
+ {file = "ncompress-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6f5bf381412e9d3847b76e8b6bd1f84dfadcd3d9c25903c8592facb437909a0"},
+ {file = "ncompress-1.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e0ebd71990ef7909b6627b5341a2fe1977dcce61dd3760a29e19e3f9e4c6a275"},
+ {file = "ncompress-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b9acc46cf36bb998ed215d6e76a94e2bd1e827b9a4cb5362982b7004b5a7620"},
+ {file = "ncompress-1.0.0-cp310-cp310-win32.whl", hash = "sha256:2a104803fbe3ab0a96edb14927fa22c8142be838aabe7e938b4a52a4e82db56e"},
+ {file = "ncompress-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a2ae8a9170fa1f45df7efa292eb8c437b18c225b63d4adca4f50f9da0e8e0c7"},
+ {file = "ncompress-1.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7608fbda43d04d9f476be2dbf4ef3c96e72d83b9557a48b07fbc9ff3ad29cdd2"},
+ {file = "ncompress-1.0.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8322482e72ac2802d1dca1007ec06aa281a4d5cf1cf9f8c75bb51e917382b756"},
+ {file = "ncompress-1.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3590e66313041721ae81e72ece06b7048c9293321bb30900358638673608e264"},
+ {file = "ncompress-1.0.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:736dbae078107742cf6ac7ccc11ae9c5eab77ef2c02aab3ef64802877bb01cab"},
+ {file = "ncompress-1.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5336a8831a7e587829ce54e9e27d1fb2e04ddbc7d2d983693e35a3a03ac3ce79"},
+ {file = "ncompress-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:9cd040ad73a3b0e917e01cdfba507e10e0bb56849daaac3ac3d86382d4d7ad82"},
+ {file = "ncompress-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:8eb4a55cbeaeb238a3b412952077be6b3f37b3416cd0211cc22776391ff2fef7"},
+ {file = "ncompress-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:916671d62167191af58d6b4a17b1c09c647e349dcff1fc0b7d764aa64c3773ee"},
+ {file = "ncompress-1.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15f10fbfa11345ff0af090e3e6ae13a1fe2b52a2bb39d4f2373c2d6aeac75e5d"},
+ {file = "ncompress-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe0a671a2f7dc1ee0438d278ef30ab425a969536100c4352b5cb6bc0b6210818"},
+ {file = "ncompress-1.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d89acf209858e7940223cf35324e1b2effec119bb009a41f039e2ea4db22177"},
+ {file = "ncompress-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:66d991155a1655ccd98e8433c4a7e811d63eb649adb55f47d8f9528a30cc4b7a"},
+ {file = "ncompress-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:34c6496168fd4dbc13f1fc0c0fcbadded1957639956f8cbc6894c39999817e36"},
+ {file = "ncompress-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:94b3f4e851f5b37e1d4cf2d8da911fa10783a59cba3d7f1f2ae5bd2842558077"},
+ {file = "ncompress-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aaa18a509d9fc173b4b47d53c834e43ced1eda63d2aa7d4613dc59b2f802a31a"},
+ {file = "ncompress-1.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6540556d47670a8fb93878a44d0206bbdc87f32e4c5b57d6fe63691efafbb982"},
+ {file = "ncompress-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da7c81313aed4b6c6e8020442ed8d03d04bff72947f9380ea1ce2c63ffb8ad1"},
+ {file = "ncompress-1.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d45ec59a8a3ce00613df0c81e5567854574dbbbf01ecd1a5a0929cd8fb04844d"},
+ {file = "ncompress-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:393cc3c126b9451fb32fe2bc07773264c90e73afbd37da0df472ac23bfd1a2d5"},
+ {file = "ncompress-1.0.0-cp38-cp38-win32.whl", hash = "sha256:78674f246878938387b6f82b10d1aa2192e02544d214541943d12ef1a45e66c6"},
+ {file = "ncompress-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:da216a53db7cd4c0247376f87367dd71df457443567e55310f6d3d23a9aff2f2"},
+ {file = "ncompress-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34754041d9bac2d6908ae0d07ba541e4d6d606cca222ddd53f3a57e15f386b0a"},
+ {file = "ncompress-1.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab9fc62baaa55faf8ed8ac67f2c64a7295fec91d7c1f306ac46aa894ca4edf91"},
+ {file = "ncompress-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070044eab19586a45d1855c3e50e000ce86d6075b122a5ec8cffd480713dffac"},
+ {file = "ncompress-1.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f9ba6ab2aadd6fd90365fdad5219e4dc7bc2459b94f1e900a733dddaf4e9b2e6"},
+ {file = "ncompress-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b031e06b42037b181e3514261e1e85a9eae4af990c12b9348a9f22b8042201ff"},
+ {file = "ncompress-1.0.0-cp39-cp39-win32.whl", hash = "sha256:13fa26ec8000d786a8079bb265508b5df4b445a4f460481a13549b4bd3c83824"},
+ {file = "ncompress-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:d11df815d280985dfa660974df11dbe051a1a18dca2f91f9d30fbd6548237b8f"},
+ {file = "ncompress-1.0.0.tar.gz", hash = "sha256:e7bbf10cca1376f4f17ae2c447e33a9d4067525abb0c71d488c9a5ced50552f1"},
+]
+nest-asyncio = [
+ {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"},
+ {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"},
+]
+networkx = [
+ {file = "networkx-2.3.zip", hash = "sha256:8311ddef63cf5c5c5e7c1d0212dd141d9a1fe3f474915281b73597ed5f1d4e3d"},
+]
+nodeenv = [
+ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
+ {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
+]
+nose = [
+ {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"},
+ {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"},
+ {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"},
+]
+notebook = [
+ {file = "notebook-6.4.12-py3-none-any.whl", hash = "sha256:8c07a3bb7640e371f8a609bdbb2366a1976c6a2589da8ef917f761a61e3ad8b1"},
+ {file = "notebook-6.4.12.tar.gz", hash = "sha256:6268c9ec9048cff7a45405c990c29ac9ca40b0bc3ec29263d218c5e01f2b4e86"},
+]
+notebook-shim = [
+ {file = "notebook_shim-0.2.0-py3-none-any.whl", hash = "sha256:481711abddfb2e5305b83cf0efe18475824eb47d1ba9f87f66a8c574b8b5c9e4"},
+ {file = "notebook_shim-0.2.0.tar.gz", hash = "sha256:fdb81febb05932c6d19e44e10382ce05469cac5e1b6e99b49be6159ddb5e4804"},
+]
+numpy = [
+ {file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"},
+ {file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"},
+ {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71"},
+ {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3"},
+ {file = "numpy-1.23.4-cp310-cp310-win32.whl", hash = "sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd"},
+ {file = "numpy-1.23.4-cp310-cp310-win_amd64.whl", hash = "sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329"},
+ {file = "numpy-1.23.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db"},
+ {file = "numpy-1.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f"},
+ {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0"},
+ {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488"},
+ {file = "numpy-1.23.4-cp311-cp311-win32.whl", hash = "sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79"},
+ {file = "numpy-1.23.4-cp311-cp311-win_amd64.whl", hash = "sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d"},
+ {file = "numpy-1.23.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5"},
+ {file = "numpy-1.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6"},
+ {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f"},
+ {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68"},
+ {file = "numpy-1.23.4-cp38-cp38-win32.whl", hash = "sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba"},
+ {file = "numpy-1.23.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8"},
+ {file = "numpy-1.23.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894"},
+ {file = "numpy-1.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7"},
+ {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735"},
+ {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0"},
+ {file = "numpy-1.23.4-cp39-cp39-win32.whl", hash = "sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef"},
+ {file = "numpy-1.23.4-cp39-cp39-win_amd64.whl", hash = "sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e"},
+ {file = "numpy-1.23.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911"},
+ {file = "numpy-1.23.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810"},
+ {file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"},
+ {file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"},
+]
+nvidia-ml-py3 = [
+ {file = "nvidia-ml-py3-7.352.0.tar.gz", hash = "sha256:390f02919ee9d73fe63a98c73101061a6b37fa694a793abf56673320f1f51277"},
+]
+oauthlib = [
+ {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
+ {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
+]
+onnx = [
+ {file = "onnx-1.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bdbd2578424c70836f4d0f9dda16c21868ddb07cc8192f9e8a176908b43d694b"},
+ {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213e73610173f6b2e99f99a4b0636f80b379c417312079d603806e48ada4ca8b"},
+ {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fd2f4e23078df197bb76a59b9cd8f5a43a6ad2edc035edb3ecfb9042093e05a"},
+ {file = "onnx-1.12.0-cp310-cp310-win32.whl", hash = "sha256:23781594bb8b7ee985de1005b3c601648d5b0568a81e01365c48f91d1f5648e4"},
+ {file = "onnx-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:81a3555fd67be2518bf86096299b48fb9154652596219890abfe90bd43a9ec13"},
+ {file = "onnx-1.12.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:5578b93dc6c918cec4dee7fb7d9dd3b09d338301ee64ca8b4f28bc217ed42dca"},
+ {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c11162ffc487167da140f1112f49c4f82d815824f06e58bc3095407699f05863"},
+ {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341c7016e23273e9ffa9b6e301eee95b8c37d0f04df7cedbdb169d2c39524c96"},
+ {file = "onnx-1.12.0-cp37-cp37m-win32.whl", hash = "sha256:3c6e6bcffc3f5c1e148df3837dc667fa4c51999788c1b76b0b8fbba607e02da8"},
+ {file = "onnx-1.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8a7aa61aea339bd28f310f4af4f52ce6c4b876386228760b16308efd58f95059"},
+ {file = "onnx-1.12.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:56ceb7e094c43882b723cfaa107d85ad673cfdf91faeb28d7dcadacca4f43a07"},
+ {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3629e8258db15d4e2c9b7f1be91a3186719dd94661c218c6f5fde3cc7de3d4d"},
+ {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d9a7db54e75529160337232282a4816cc50667dc7dc34be178fd6f6b79d4705"},
+ {file = "onnx-1.12.0-cp38-cp38-win32.whl", hash = "sha256:fea5156a03398fe0e23248042d8651c1eaac5f6637d4dd683b4c1f1320b9f7b4"},
+ {file = "onnx-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:f66d2996e65f490a57b3ae952e4e9189b53cc9fe3f75e601d50d4db2dc1b1cd9"},
+ {file = "onnx-1.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c39a7a0352c856f1df30dccf527eb6cb4909052e5eaf6fa2772a637324c526aa"},
+ {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab13feb4d94342aae6d357d480f2e47d41b9f4e584367542b21ca6defda9e0a"},
+ {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7a9b3ea02c30efc1d2662337e280266aca491a8e86be0d8a657f874b7cccd1e"},
+ {file = "onnx-1.12.0-cp39-cp39-win32.whl", hash = "sha256:f8800f28c746ab06e51ef8449fd1215621f4ddba91be3ffc264658937d38a2af"},
+ {file = "onnx-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833"},
+ {file = "onnx-1.12.0.tar.gz", hash = "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9"},
+]
+onnx2torch = [
+ {file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"},
+]
+onnxoptimizer = [
+ {file = "onnxoptimizer-0.3.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e73a5e2e3ca4db9bff54f7131768749c861677b97ee811a136fcf1a52783cf6e"},
+ {file = "onnxoptimizer-0.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bf2bfe0dc43f0776867688e1759122dec049ff4f45f7221931b687fe7e139e"},
+ {file = "onnxoptimizer-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a9a815bba418abfb23f319838370cfd9450305a2da7d970a2261046889a70730"},
+ {file = "onnxoptimizer-0.3.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:60f1d3600f03466a451c05e3d12ce97565bae016e46f70396ba22208cfeae6f6"},
+ {file = "onnxoptimizer-0.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:056a765593d197b2b643bfb35520a66eacebfc682583d9ac0389a56c2a259e6f"},
+ {file = "onnxoptimizer-0.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:15da6a036df388c3f08c3fc638b4d313ee6a1b96aaaa1c602fd1b424dd7bbc23"},
+ {file = "onnxoptimizer-0.3.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5547203ee3392e3dabcea68a3a4d316ee0269ad3cf8a3504d1f68d467f60a06a"},
+ {file = "onnxoptimizer-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c773b03313e253a60c7d8fe56ea5bba38fb3442ab84a825468a35a739e8fb20b"},
+ {file = "onnxoptimizer-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:aac4d5c4c5f4c471346dfb28356355b0c54c074a1f2fe1fe122b197f68c08a92"},
+ {file = "onnxoptimizer-0.3.1.tar.gz", hash = "sha256:0aa2e873a49f3762822e4400e1e8886236156f9d1dbf20319e2c18f7ebfb6a1d"},
+]
+onnxruntime-gpu = [
+ {file = "onnxruntime_gpu-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b0393c5122ed90fa2eb76192a486261d86e9526ccb78b2a98923c22791d2d1"},
+ {file = "onnxruntime_gpu-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:ecfe97335027e569d4f46725ba89316041e562b8c499690e25e11cfee4601cd1"},
+ {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2be6f7f5a1ce0bc8471ce42e10eab92cfb19d0748b857edcb5320b5e98311b7"},
+ {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d73204323aefebe4eecab9fcf76e22b1a00394e3d838c2962a28a27301186b73"},
+ {file = "onnxruntime_gpu-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37872527d03d3df10756408ca44014bd6ac354a044ab1c4286cd42dc138e518"},
+ {file = "onnxruntime_gpu-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:296bd9733986cb7517d15bef5535c555d3f863963a71e6575e92d2a854aee61d"},
+ {file = "onnxruntime_gpu-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e46d0724ce54c5908c5760037b78de741fbd48962b370c29ebc20e608b30eda"},
+ {file = "onnxruntime_gpu-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd919373be35b9ba54210688265df38ad5e19a530449385c40dab51da407345d"},
+]
+opencv-python-headless = []
+osmium = [
+ {file = "osmium-3.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7bf648744dadd3396040ed57332780272356fb4b87dbd17521ba9b8b91c6e665"},
+ {file = "osmium-3.4.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a8ee676ed0a0dbd359197ffb42518889af700da29629ac13834c1149a4b83ce6"},
+ {file = "osmium-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:080345e4be12474da9b1d4f45fbf8214ab316826029f821f8043f1b36e749cc3"},
+ {file = "osmium-3.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:58b61b72911b7bb87f617ae12872d7c11d360f08fb50927eb769a91be3434d7c"},
+ {file = "osmium-3.4.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bfee94a25e419923bcb885bf624024a1610f0e1c26ceb1a7c65426420d549c0e"},
+ {file = "osmium-3.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:2ac803d2d1b0097768a8bf0c174349d686d5dc2ec832ec84cadb20937a415d10"},
+ {file = "osmium-3.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:daa478c951ade8b49348096f5e330bbe39cd2b5057127666a9102094cf7f31fb"},
+ {file = "osmium-3.4.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5453ed8c8c69ab3ed2c17ae1fd3567b9b54036916f0ffe77235fe363e7029b01"},
+ {file = "osmium-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f9776510dbaac32ded5d3bbe31bacdeeefefc79ce8ea54c743e9eb2ce4ace007"},
+ {file = "osmium-3.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d49e56225f4e31867e2bbdef000ae3fb9b18ca6697e44c3315d64b46d53fc77e"},
+ {file = "osmium-3.4.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:76e8461ae68c5307f44eb407424b7dcfc7d0d8bb5b52283088d8f549c0d715ba"},
+ {file = "osmium-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f4710b8350d93edc194a466ab4cb42efcc58961be46901cced359f74bed27e5"},
+ {file = "osmium-3.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a777eaa408a49f8c2f0f0358a09307c9fa69d4809ae3632f3e40c61bdbf11d17"},
+ {file = "osmium-3.4.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50a9c94c4856d81c11e68ff327b81d759311974d7fb67618cec3b15e32b7f4c1"},
+ {file = "osmium-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e45b7c54ac756e9cb40e2ba68691df635804eb6aa2023088af66936a9c8e3782"},
+ {file = "osmium-3.4.1.tar.gz", hash = "sha256:575dad72ab169cf585b9aeefb4f5f99ac250bf7da1986992afcbf169dc70c381"},
+]
+packaging = [
+ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+pandas = [
+ {file = "pandas-1.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4"},
+ {file = "pandas-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391"},
+ {file = "pandas-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594"},
+ {file = "pandas-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d"},
+ {file = "pandas-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96"},
+ {file = "pandas-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee"},
+ {file = "pandas-1.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036"},
+ {file = "pandas-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3"},
+ {file = "pandas-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb"},
+ {file = "pandas-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624"},
+ {file = "pandas-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4"},
+ {file = "pandas-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77"},
+ {file = "pandas-1.5.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209"},
+ {file = "pandas-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0"},
+ {file = "pandas-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d"},
+ {file = "pandas-1.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26"},
+ {file = "pandas-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075"},
+ {file = "pandas-1.5.1-cp38-cp38-win32.whl", hash = "sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7"},
+ {file = "pandas-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d"},
+ {file = "pandas-1.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113"},
+ {file = "pandas-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454"},
+ {file = "pandas-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96"},
+ {file = "pandas-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060"},
+ {file = "pandas-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048"},
+ {file = "pandas-1.5.1-cp39-cp39-win32.whl", hash = "sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658"},
+ {file = "pandas-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68"},
+ {file = "pandas-1.5.1.tar.gz", hash = "sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee"},
+]
+pandocfilters = [
+ {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"},
+ {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"},
+]
+parameterized = [
+ {file = "parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9"},
+ {file = "parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c"},
+]
+paramiko = [
+ {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"},
+ {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"},
+]
+parso = [
+ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
+ {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
+]
+pexpect = [
+ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+ {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
+pillow = [
+ {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"},
+ {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"},
+ {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"},
+ {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"},
+ {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"},
+ {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"},
+ {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"},
+ {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"},
+ {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"},
+ {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"},
+ {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc"},
+ {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437"},
+ {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"},
+ {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"},
+ {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"},
+ {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"},
+ {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"},
+ {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"},
+ {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"},
+ {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"},
+ {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"},
+ {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"},
+ {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"},
+ {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"},
+ {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"},
+ {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"},
+ {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"},
+ {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"},
+ {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"},
+ {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"},
+ {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"},
+ {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"},
+ {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"},
+ {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"},
+ {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"},
+ {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"},
+ {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"},
+ {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"},
+ {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"},
+ {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"},
+ {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"},
+ {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"},
+ {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"},
+ {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"},
+ {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"},
+ {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"},
+ {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"},
+ {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"},
+ {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"},
+ {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"},
+ {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"},
+ {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"},
+ {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"},
+ {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"},
+ {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"},
+ {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"},
+ {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"},
+ {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"},
+]
+pillow-avif-plugin = [
+ {file = "pillow-avif-plugin-1.2.2.tar.gz", hash = "sha256:38033ef060b96b2615f5c9e8fa87d4928a4254f0f43081abd89c9017f82c589a"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:fc542b03ea87f0e832904fe1ff61cb586bafcd5f80e352ae7ca5cac008e9d651"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-macosx_11_0_arm64.whl", hash = "sha256:c77ed103dc587894e7d707cd4ffc0f5b1c641d09b1fb84245475f02c1db039c9"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:5c0b979acd9a58a5d5f662eb167d65c8e103af0198fcaa200661abf9733dbf9b"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6dade8e87909766a655a88df0a3c0078c9020315e2fefab8cf3496d9940fd0f8"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:54b1a27610cbf754e332b0f3e7be188cceedcfcdcb4cff92d272aff04082eb1d"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:887665e08bee4ded0512fe991bbbe7403c90aa9646be8d27295271dcdc10581e"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:feba13f19bd9381e079e72f088a66a6509caefb52304888751d637c23b1faa00"},
+ {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7238447e395ccee68052c06c0fb824696f5cd1f3050f50466f1889d8356a28f7"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:703f4e15dcfc9335f69ebd09cecb576f3db0c3586af6bd43d0971a591183b0de"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c4665a8d3aa5fb60ed2976eb52ae886f6a276c7ddf346c118b16dc6f1ceec7c"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:593006af5569df6f3aefba184a90a5112798859ec8c745bcb495ab8b84d194bc"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2fa7245f7544f4ae03ab4efc035a398883b404488806094e661b3438d921b88"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017e5e52cb4320414e8ce3e2089eae2cb87c22c73ff6012b17ae326fc5753b20"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a57136d4866de5dc80cfb24d66655955fbdd87acf1d11d88c8dc2ab41023e46"},
+ {file = "pillow_avif_plugin-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:f339511d0fccb69e3a5e3af39f8fe6700b0a07279015006ea56f8f49e7fecff4"},
+ {file = "pillow_avif_plugin-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05e821ecd90bb0b8d2dc7610625372cc47de9cb893d09662528bad572f669d1c"},
+ {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33886a5f9796fe9a8a3bc25ccfdeba7db119adb50b7004f1928a14b07d0213a"},
+ {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b7ed186c2f740dd26e556f6a966c59a170b70263e429a2c81920fe444da8a7"},
+ {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11aef6b79078b8dad25c928e5871c146ab94424472851d5bf539ba62abde9ac"},
+ {file = "pillow_avif_plugin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10696c536d68a14cefea3b98edb8d5a7ae29e8e07458f1d59c5d1cd780a8bf2a"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1a7291d6a5fb7336e72685a31d193e0b3a6bee9986c9ac4d8bd4b68dbe6d4f7f"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:14b9c5dbf237e7dc12f69819ea181a457b3bd4f59f8cd71d028d3635fd3bcab4"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:799cbfbeee831332d280c80df9ce16b5c3b1224c318264e97e89df8da32e870e"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8f64153b2bb8007d00a3ccb32bb374dda185bf1b4a145c2e971fc67b68d015c"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b7937a20042fdd4272cdb3f2942d0290a045e6016335eeb79685ee24e8aa652"},
+ {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e5aa90fee46584d5958832a47e19fb7c18de059cacdac33494a55b9a064a56dc"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:1124f4a5e2312663cdb96879dd01a200475bd86626bf4905fca33c6d438e479b"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:a1d2a17a6b133793ad733b33712cdc0bba13acc6fe83d259ef3c735e172550a4"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:eb2f34c093b93dbc05b28fc34715b411ee0a5f30788137b27eb89864375134c8"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bef5d0f2b198aca6a13ebd77006ecb25e08d2c09deb25dc89ec72d2460da8e67"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8ec81be95e14de6e8052cceae593d4ff43ba22938186b44c68964715a85c1f9"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:39cbf7b21392778f1bd3988c97429ff7b86a9a972f033c9c3674544114e612ab"},
+ {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c9099647c81ec26b2f92dde414876f000f74588f15f5e60228dbf0494891d817"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:050599ad735e6a3d6c565bc563ced74872acd3e8bd3e7ac9f014dc2e82249611"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3adf80a6ee5e895b75bcd3743f81b3ed96cd0c418326f499312b1bde87b41f1"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:251f1fc60caedd2c641c9f28c4f5a74610aa6c1d1b54e681ce63d508a8e30c53"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3216e9f28514947fcdecd41782ccb5bb3a73360d758b1a2ff7fbf3831e0f5bf7"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a229d44f8446d6e2515e965d6d066cfd9308d50c6ada22ecb2f13a9b0c492e86"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:529eb2d9e3827acf874c6ddedc28729bfb709c115cc972c77464cc7bacdf2b38"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d3ee3b5b66d58c33bc723074e2db7e1256d341ba90edfd2c9ea19c7964836da"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a34a928ed23508a694b34a14252e178b82db577285d43a1eaf143a6a8a6ddd63"},
+ {file = "pillow_avif_plugin-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:d40bf89d78da035dea9d5167335f73e71b696509e14513d875ac2605b91ae269"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:ad8958a5ea5e3f6ae2a4ded374fb7b84b3af2176e295ca6c3d1d0b6e9866933e"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab19285161028059dfc6362b7aba6a02ff5371d22492d8a71eade7d7e5ca6f59"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8f70d2787bf9bed4588fedd9e1f149eb7c2a9990df3ee123eb18d86882ca0b8a"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220fc8e8b9e1e3e98cd7f6dcc116cff8ca453b249dba62264ab0dc2a92fc4e1b"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0514b09d518a699525061160093da58eb3a04649daf98d8e9ee52d739de5be6e"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a6314e0144a2b08e39b6842fa5b981383d7932f6e8363b6bbb291a9efc23deb1"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3bf72f8df4b08b63b183c6368e3bd0cd098bfacc494803383589655607bc1f76"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0d54543ed9fbb6c18b61c93a2296e2a9bfc85ef559cb6a50b92fe80cc4711db5"},
+ {file = "pillow_avif_plugin-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:86c62edb830a83a97c2a2f9a262968f12ce32adbf1069fd5fae47a0fae706e16"},
+]
+pipenv = [
+ {file = "pipenv-2022.10.12-py2.py3-none-any.whl", hash = "sha256:f43972a42411107ade86b6f17dd698dfcd843bd84eb7264163ebb363f6b0ede4"},
+ {file = "pipenv-2022.10.12.tar.gz", hash = "sha256:a4d88f6667cbcd9ea432d626a8b373cd3101886b9fb964ea7e7f9650a83fc307"},
+]
+pkginfo = [
+ {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"},
+ {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"},
+]
+pkgutil-resolve-name = [
+ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"},
+ {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
+]
+platformdirs = [
+ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
+ {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+]
+plotly = [
+ {file = "plotly-5.10.0-py2.py3-none-any.whl", hash = "sha256:989b13825cc974390aa0169479485d9257d37848a47bc63957251f8e1a7046ba"},
+ {file = "plotly-5.10.0.tar.gz", hash = "sha256:4d36d9859b7a153b273562deeed8c292587a472eb1fd57cd4158ec89d9defadb"},
+]
+pluggy = [
+ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+poetry = [
+ {file = "poetry-1.2.2-py3-none-any.whl", hash = "sha256:93ea3c4a622485c2a7b7249f1e34e4ac84f8229ded76153b67506313201b154f"},
+ {file = "poetry-1.2.2.tar.gz", hash = "sha256:6d9ed0b1b826a0a79190f2078d7d78483fa24bf2494f3b170e354eaa5e7b5ea1"},
+]
+poetry-core = [
+ {file = "poetry-core-1.3.2.tar.gz", hash = "sha256:0ab006a40cb38d6a38b97264f6835da2f08a96912f2728ce668e9ac6a34f686f"},
+ {file = "poetry_core-1.3.2-py3-none-any.whl", hash = "sha256:ea0f5a90b339cde132b4e43cff78a1b440cd928db864bb67cfc97fdfcefe7218"},
+]
+poetry-plugin-export = [
+ {file = "poetry-plugin-export-1.1.2.tar.gz", hash = "sha256:5e92525dd63f38ce74a51ed68ea91d753523f21ce5f9ef8d3b793e2a4b2222ef"},
+ {file = "poetry_plugin_export-1.1.2-py3-none-any.whl", hash = "sha256:946e3313b3d00c18fb9a50522e9d5e6a7e111beaba8d6ae33297662fc2070ac1"},
+]
+portalocker = [
+ {file = "portalocker-2.6.0-py2.py3-none-any.whl", hash = "sha256:102ed1f2badd8dec9af3d732ef70e94b215b85ba45a8d7ff3c0003f19b442f4e"},
+ {file = "portalocker-2.6.0.tar.gz", hash = "sha256:964f6830fb42a74b5d32bce99ed37d8308c1d7d44ddf18f3dd89f4680de97b39"},
+]
+pprofile = [
+ {file = "pprofile-2.1.0.tar.gz", hash = "sha256:b2bb56603dadf40c0bc0f61621f22c20e41638425f729945d9b7f8e4ae8cdd4a"},
+]
+pre-commit = [
+ {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
+ {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
+]
+pretrainedmodels = [
+ {file = "pretrainedmodels-0.7.4.tar.gz", hash = "sha256:7e77ead4619a3e11ab3c41982c8ad5b86edffe37c87fd2a37ec3c2cc6470b98a"},
+]
+prometheus-client = [
+ {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"},
+ {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"},
+]
+prompt-toolkit = [
+ {file = "prompt_toolkit-3.0.31-py3-none-any.whl", hash = "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d"},
+ {file = "prompt_toolkit-3.0.31.tar.gz", hash = "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"},
+]
+protobuf = [
+ {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"},
+ {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"},
+ {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"},
+ {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"},
+ {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"},
+ {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"},
+ {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"},
+ {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"},
+ {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"},
+ {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"},
+ {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"},
+ {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"},
+ {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"},
+ {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"},
+ {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"},
+ {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"},
+ {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"},
+ {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"},
+ {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"},
+ {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"},
+ {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"},
+ {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"},
+ {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"},
+ {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"},
+]
+psutil = [
+ {file = "psutil-5.9.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71"},
+ {file = "psutil-5.9.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b"},
+ {file = "psutil-5.9.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab"},
+ {file = "psutil-5.9.3-cp27-cp27m-win32.whl", hash = "sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6"},
+ {file = "psutil-5.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31"},
+ {file = "psutil-5.9.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727"},
+ {file = "psutil-5.9.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d"},
+ {file = "psutil-5.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7"},
+ {file = "psutil-5.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb"},
+ {file = "psutil-5.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543"},
+ {file = "psutil-5.9.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7"},
+ {file = "psutil-5.9.3-cp310-cp310-win32.whl", hash = "sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e"},
+ {file = "psutil-5.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650"},
+ {file = "psutil-5.9.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088"},
+ {file = "psutil-5.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a"},
+ {file = "psutil-5.9.3-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9"},
+ {file = "psutil-5.9.3-cp36-cp36m-win32.whl", hash = "sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89"},
+ {file = "psutil-5.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02"},
+ {file = "psutil-5.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef"},
+ {file = "psutil-5.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a"},
+ {file = "psutil-5.9.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698"},
+ {file = "psutil-5.9.3-cp37-cp37m-win32.whl", hash = "sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837"},
+ {file = "psutil-5.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c"},
+ {file = "psutil-5.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1"},
+ {file = "psutil-5.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2"},
+ {file = "psutil-5.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1"},
+ {file = "psutil-5.9.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f"},
+ {file = "psutil-5.9.3-cp38-cp38-win32.whl", hash = "sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619"},
+ {file = "psutil-5.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df"},
+ {file = "psutil-5.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc"},
+ {file = "psutil-5.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe"},
+ {file = "psutil-5.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f"},
+ {file = "psutil-5.9.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc"},
+ {file = "psutil-5.9.3-cp39-cp39-win32.whl", hash = "sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395"},
+ {file = "psutil-5.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931"},
+ {file = "psutil-5.9.3.tar.gz", hash = "sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6"},
+]
+ptyprocess = [
+ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+]
+pure-eval = [
+ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
+ {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
+]
+py = [
+ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
+pycapnp = [
+ {file = "pycapnp-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:bdd013eae51d190a2426d00cc72d0aaed148a5be778ca86ee1adae3ab7a0613f"},
+ {file = "pycapnp-1.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9a1d6306a0e3e0090574aeb08d432bd67f9eb04ab564e89ef34cd1fe320b20f"},
+ {file = "pycapnp-1.1.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:47c8bc28521312660c95cfc8a552654949407f8b17bc7ed6955ad7dae34d21a4"},
+ {file = "pycapnp-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:60adf2674f89f629551171116b8f400b17e9a41a2ef15736767acec405d4ca50"},
+ {file = "pycapnp-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a788a374ccb93354943c89f5b1caf785faf7bb90191cd6265e042aa004f8b206"},
+ {file = "pycapnp-1.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a1746017079107232faf26af8ef4284ab0b20ce5cbe688d44e7553a67e5e5cb"},
+ {file = "pycapnp-1.1.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0c770145a4eccfe97f53ab500283aa9bf969d4a37bffc75737a964db4f2af833"},
+ {file = "pycapnp-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:1774a4fe9db5f094ba40cf00898fa4b437773e7f9c538b779275b9f422a92ebc"},
+ {file = "pycapnp-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2b28d5d951602c0b832bbe63f85ebdd7685b33118b1c11c2c65a243ec9f35a66"},
+ {file = "pycapnp-1.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:61b009faba34855c9d29db107e188898c83099347e22ebcbc1d955774403247b"},
+ {file = "pycapnp-1.1.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a7aa9af0185e5977a59228db5042dffb048b2d4bf4f665d2105b4781cf2fcbc"},
+ {file = "pycapnp-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:13badfb644e2eb7f1219aab259d18b262d1512021e4112fa1ad5e74d17bc30cf"},
+ {file = "pycapnp-1.1.0.tar.gz", hash = "sha256:786a2e39b79e592a41e8a1eaeea6e41e2015ecb9f5b7f7c20dfc5768ba1ae077"},
+]
+pycodestyle = [
+ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
+ {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
+]
+pycparser = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+pycryptodome = [
+ {file = "pycryptodome-3.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:2ae53125de5b0d2c95194d957db9bb2681da8c24d0fb0fe3b056de2bcaf5d837"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"},
+ {file = "pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"},
+ {file = "pycryptodome-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:eb6fce570869e70cc8ebe68eaa1c26bed56d40ad0f93431ee61d400525433c54"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:50ca7e587b8e541eb6c192acf92449d95377d1f88908c0a32ac5ac2703ebe28b"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"},
+ {file = "pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"},
+ {file = "pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"},
+ {file = "pycryptodome-3.15.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9"},
+ {file = "pycryptodome-3.15.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e"},
+ {file = "pycryptodome-3.15.0-pp27-pypy_73-win32.whl", hash = "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f"},
+ {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2"},
+ {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5"},
+ {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b"},
+ {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f"},
+ {file = "pycryptodome-3.15.0.tar.gz", hash = "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8"},
+]
+pycuda = [
+ {file = "pycuda-2022.1.tar.gz", hash = "sha256:acd9030d93e76e60b122e33ad16bcf01bb1344f4c304dedff1cd2bffb0f313a3"},
+]
+pycurl = [
+ {file = "pycurl-7.45.1.tar.gz", hash = "sha256:a863ad18ff478f5545924057887cdae422e1b2746e41674615f687498ea5b88a"},
+]
+pyflakes = [
+ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
+ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
+]
+pygame = [
+ {file = "pygame-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f149e182d0eeef15d8a9b4c9dad1b87dc6eba3a99bd3c44a777a3a2b053a3dca"},
+ {file = "pygame-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc4444d61d48c5546df5137cdf81554887ddb6e2ef1be7f51eb77ea3b6bdd56f"},
+ {file = "pygame-2.1.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a0ccf8e3dce7ca67d523a6020b7e3dbf4b26797a9a8db5cc4c7b5ef20fb64701"},
+ {file = "pygame-2.1.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7889dce887ec83c9a0bef8d9eb3669d8863fdaf37c45bacec707d8ad90b24a38"},
+ {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2f40d5a75fd9cdda473c58b0d8b294da6e0179f00bb3b1fc2f7f29cac09bea"},
+ {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4b4cd440d50a9f8551b8989e856aab175593af07eb825cad22fd2f8f6f2ffce"},
+ {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:754c2906f2ef47173a14493e1de116b2a56a2c8e1764f1202ba844d080248a5b"},
+ {file = "pygame-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c99b95e62cdda29c2e60235d7763447c168a6a877403e6f9ca5b2e2bb297c2ce"},
+ {file = "pygame-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:9649419254d3282dae41f23837de4108b17bc62187c3acd8af2ae3801b765cbd"},
+ {file = "pygame-2.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dcc285ee1f1d0e2672cc52f880fd3f564b1505de710e817f692fbf64a72ca657"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e1bb25986db77a48f632469c6bc61baf7508ce945aa6161c02180d4ee5ac5b8d"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a8e18677e0064b7a422f6653a622652d932826a27e50f279d55a8b122a1a83"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd528dbb91eca16f7522c975d0f9e94b95f6b5024c82c3247dc0383d242d33c6"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc9586e17875c0cdf8764597955f9daa979098fd4f80be07ed68276ac225480"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ce7f3d8af14d7e04eb7eb41c5e5313c43508c252bb2b9eb53e51fc87ada9fd"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e09044e9e1aa8512d6a9c7ce5f94b881824bcfc401105f3c24f546dfc3bb4aa5"},
+ {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:40e4d8d65985bb467d9c5a1305fb53fd6820c61d764979600becab973339676f"},
+ {file = "pygame-2.1.2-cp36-cp36m-win32.whl", hash = "sha256:50d9a21edd551669862c27c9272747401b20b1939abaacb842c08ea1cdd1c04d"},
+ {file = "pygame-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e18c9466131378421d00fc40b637425229238d506a073d9c537b230b355a25d6"},
+ {file = "pygame-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:07ca9f683075aea9bd977af9f09a720ebf747343d3ea8103e4f1735283b02330"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3c8d6637ff75351e581327efefa9d04eeb0f257b533392b6cc6b15ceca4f7c5e"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ebbefb8b576572c8fc97a3321d37dc2b4afea6b6e3877a67f7158d8c2c4cefe"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d6452419e01a0f848aed0597f69fd10a4c2a7750c15d1b0607f86090a39dcf3"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e627300a66a90651fb39e41601d447b1fdbbfffca3f08ef0278d6cc0436b2160"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56a811d8821f7b9a594e3d0e0dd8bd39b25e3eea8963d5963263b90fd2ea5c2"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24b4f7f30fa2b3d092b60be6fcc725fb91d569fc87a9bcc91614ee8b0c005726"},
+ {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8e87716114e97322fb177e223d889a4be369a0f73212f4f8507fe0cd43253b23"},
+ {file = "pygame-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:20676da24e3e3e6b9fc4eecc7ba09d77ef46c3a83a028763ba1d222476c2e3fb"},
+ {file = "pygame-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:93c4cbfc942dd00410eaa9e84252129f9f9993f37f683006d7b49ab245342254"},
+ {file = "pygame-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2405414d8c572668e04739875661e030a0c588e197fa95463fe301c3d0a0510b"},
+ {file = "pygame-2.1.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e8632f6b2ddb90f6f3950744bd65d5ef15af615e3034057fa30ff836f48a7179"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ca5ef1315fa67c241a657ab077be44f230c05740c95f0b46409457dceefdc7e5"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1219a963941bd53aa754e8449364c142004fe706c33a9c22ff2a76521a82d078"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bb0674aa789848ddc264bfc60c54965bf3bb659c141de4f600e379acc9b944c"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24254c4244f0d9bdc904f5d3f38e86757ca4c6aa0e44a6d55ef5e016bc7274d6"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97a74ba186deee68318a52637012ef6abf5be6282c659e1d1ba6ad08cf35ec85"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e97d38308c441942577fea7fcd1326308bc56d6be6c024218e94d075d322e0f"},
+ {file = "pygame-2.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea36f4f93524554a35cac2359df63b50af6556ed866830aa1f07f0d8580280ea"},
+ {file = "pygame-2.1.2-cp38-cp38-win32.whl", hash = "sha256:4aa3ae32320cc704d63e185864e44f6265c2a6e52c9384afe152cc3d51b3a2ef"},
+ {file = "pygame-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:9d7b021b8dde5d528363e474bc18bd6f79a9666eef89fb4859bcb8f0a536c9de"},
+ {file = "pygame-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:660c80c0b2e80f1f801715583b759fb4c7bc0c11eb3b534e89c9fc4bfbc38acd"},
+ {file = "pygame-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dad6bf3fdd3752d7519422f3732be779b98fe7c87d32c3efe2fdffdcbeebb6ca"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:119dee20c372c85dc47b717119534d15a60c64ceab8b0eb09278866d10486afe"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fc2e5db54491e8f27785fc5204c96f540d3557dcf5b0a9a857b6594d6b32561b"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d3c50ee9847b743db6cd7b1bb17a94c2c2abc16679d70f5e745cabdf19e655"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ecda8dd4583982bb65f9c682f244a5e94524dcf628379766227e9ed97201a49"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e06ae8e1c830f1b9c36a2bc6bb11de840232e95b78e2c349c6ed803a303be19"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ea87da5fe4b6164c3854f3b0c9146811dbad0dd7fa74297683dfacc485ae1c"},
+ {file = "pygame-2.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0427c103f741234336e5606d2fad86f5403c1a3d1dc55c309fbff3c984f0c9ae"},
+ {file = "pygame-2.1.2-cp39-cp39-win32.whl", hash = "sha256:5e88b0d4338b94960686f59396f23f7f684fed4859fcc3b9f40286d72c1c61af"},
+ {file = "pygame-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:5d0c14152d0ca8ef5fbcc5ed9981462bdf59a9ae85a291e62d8a8d0b7e5cbe43"},
+ {file = "pygame-2.1.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:636f51f56615d67459b11918206bb4da30cd7d7042027bf997c218ccd6c77902"},
+ {file = "pygame-2.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ff961c3280d6ee5f4163f4772f963d7a4dbe42e36c6dd54b79ad436c1f046e5d"},
+ {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc30e834f65b893d1b4c230070183bf98e6b70c41c1511687e8436a33d5ce49d"},
+ {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c7600bf307de1ca1dca0cc7840e34604d5b0b0a5a5dad345c3fa62b054b886d"},
+ {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fdb93b4282962c9a2ebf1af994ee698be823dd913218ed97a5f2fb372b10b66"},
+ {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fddec8829e96424800c806582d73a5173b7d48946cccf7d035839ca09850db8"},
+ {file = "pygame-2.1.2.tar.gz", hash = "sha256:d6d0eca28f886f0477cd0721ac688189155a587f2bb8eae740e52ca56c3ad23c"},
+]
+pygments = [
+ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
+ {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
+]
+pyjwt = [
+ {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"},
+ {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"},
+]
+pylev = [
+ {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"},
+ {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"},
+]
+pylint = [
+ {file = "pylint-2.15.4-py3-none-any.whl", hash = "sha256:629cf1dbdfb6609d7e7a45815a8bb59300e34aa35783b5ac563acaca2c4022e9"},
+ {file = "pylint-2.15.4.tar.gz", hash = "sha256:5441e9294335d354b7bad57c1044e5bd7cce25c433475d76b440e53452fa5cb8"},
+]
+pymsalruntime = [
+ {file = "pymsalruntime-0.11.2-cp310-cp310-win32.whl", hash = "sha256:a45e35c9fa58c196029bb9f5b8bedd313b2a8ac971d576c57c31cb06139de247"},
+ {file = "pymsalruntime-0.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:81bd2502779b1c032d878b3e6bdb64882b8c7f94560cffed62e2555470d1fe45"},
+ {file = "pymsalruntime-0.11.2-cp36-cp36m-win32.whl", hash = "sha256:e6f6316128de8f62caeadf2a755e3919cc4532964d77a550974b156f27ae5f33"},
+ {file = "pymsalruntime-0.11.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ee139e7222e89f5bda3d6e8f9426b6bc0e8b4ef7c6fc2e6b9bd8b6c11579c3fb"},
+ {file = "pymsalruntime-0.11.2-cp37-cp37m-win32.whl", hash = "sha256:6eedca38877cc8718ac273cf796aebed9a25d64258d717599f601bc01f9b7922"},
+ {file = "pymsalruntime-0.11.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b0f289e4d3b01bbddf422cd82899295cf5ee4c41eda45752dd3a0733acd0ba0f"},
+ {file = "pymsalruntime-0.11.2-cp38-cp38-win32.whl", hash = "sha256:5760a1c1b6fa8c10f15a325815e0315a703106d3984cbc939d17f48d84ab21bb"},
+ {file = "pymsalruntime-0.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:e0654d04f54d1cee218a868da77f4a5a2b5369e2cc9bb35f539144256f08b00a"},
+ {file = "pymsalruntime-0.11.2-cp39-cp39-win32.whl", hash = "sha256:06501f5a1fcf83ba2edbae34c94df08f50c31290301ac20f1828c4cecc2ae1bc"},
+ {file = "pymsalruntime-0.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c10f8761baa042cba3a502be50fb19d0fe46850de82cb730b836ac3aff7a9b0e"},
+ {file = "pymsalruntime-0.11.2.tar.gz", hash = "sha256:636d01c7f6f165b48e288c2af0bd21447649846af00050956ba28a5d4079e98b"},
+]
+pymysql = [
+ {file = "PyMySQL-0.9.3-py2.py3-none-any.whl", hash = "sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a"},
+ {file = "PyMySQL-0.9.3.tar.gz", hash = "sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"},
+]
+pynacl = [
+ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
+ {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+]
+pyopencl = [
+ {file = "pyopencl-2022.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c270c7090f6bd8a1cc006aca38256936a0b82f70165e8aff873763218f29bf85"},
+ {file = "pyopencl-2022.2.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a823c2003d2d3754836f7e65f19e553f8ca022f493d0111ea3bacd886853596"},
+ {file = "pyopencl-2022.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1d1605eed80f9305251578b41068e1021f140e6d503b22933a4860c43e7cde"},
+ {file = "pyopencl-2022.2.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:85bf733b73158a66484763ff7bca7b2d5d0187933987a2753cad3f0deeff989f"},
+ {file = "pyopencl-2022.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f4288700717e3f0b0ce7c6663729ea6a1362d86964261fcaa84ca2efcf06b0"},
+ {file = "pyopencl-2022.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:dc72723fd7d5041521f9dccece70ab1ab06f2ad74b1738712a49a3b56cea3628"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82b3f13843c034cee54906b2c750a65910c9f28ba58b61abf80b9bc9c1595a1f"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a0893caf4b522559a233c4d66e7c8a96a01d8f2c038ff7fd5b3d795a1eb2ed"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3a5d59d87d3e271f7d41c80d558cf45f4226003ec3c341d8374d3ef849614b4"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cef4b300b64b58d55364d6d9eb37123907ee1f32f2dc7f0b7d1fcabbaf6fe260"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:47f751cfa75d3038ba0aedf587dd42dc447b8e49b88339048b2147cce176e286"},
+ {file = "pyopencl-2022.2.4-cp36-cp36m-win_amd64.whl", hash = "sha256:3b82458c982e6eb61691712139fc8cfd69f28ef127db2b0de06e87058e919f96"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28a0df521643644a1d5b521032b7c6617f7d53b09851a21ce9b10b8ed869dfd5"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3611fe2f7c219dcf3d3a119873bfe7a837a467aac00c6e53682dda71a060d29f"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5cfc4459e5c84ecba49b928d2e9a439f0421342e16884a6749aa227efea900"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:37001a8b70bde143597f22e79dbac90ae8c997e2136b0ec4391a2824f78eb548"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:349e1ff88b2c0ddb343666e1857b86c65fa34545354012382fe84b5d564ffde1"},
+ {file = "pyopencl-2022.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:da4bc9167f04eb47774e30c5cc6b411b970d8b09ea61873b86058dd2d64445d1"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8fae8ba0a020c5db230bba53c39fd8f950983a992fbb1318a19ba06af83bd416"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8685df5b6ec23da07751afd589a168825f6fa727634bf957652091ed7c937151"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84f94a99822d81372b16dcdc56ee9f7e3d2268acaf9cca21924e65b4ddefae20"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:532d7972d8cc27278761634e5367ff84cacee81f32490ffce9285ea122804768"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d31c4cd0af50aa602b2131a7b5c964e9105291d611f2d4418ed4628094c9336f"},
+ {file = "pyopencl-2022.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:ca9d67ca0b072989530c71c4f781acc1efd1adb6a459e742a6d38d2b8ab1d8e8"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6d2a3d6dfbae423dbf6dce45ca8a1b32cca2ee842aea49d246f8bbf8ab90812"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6e7fbacef495944e1ebb1456ac7a4c38562077d50dfd804dca6aef204f6a79a"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3735b5d2add089ea02b43ace69b50fb8fb55b11b93f7bc4be5b4249fb3370b64"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d9e88c92569a5bbcbec802dc193be06a19891979fb02f9ca4739239e2cfd5e04"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0b456e153c1ae1f92379680710daa228c9aa0302918e24f12355533bd4b5e1f4"},
+ {file = "pyopencl-2022.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:876912cfebfe9aa92a478d0bf737178e7b686d27103d7279cd1dc754b8e6e85d"},
+ {file = "pyopencl-2022.2.4-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f259595ac26d250f39ffbfcb47cfa81181aff6f24d70e718f67114a452eb43"},
+ {file = "pyopencl-2022.2.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0caf451084194da8262d6197251cccdac4c2945d0cb8807fe4bd1d7b0b6ecd5c"},
+ {file = "pyopencl-2022.2.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d64d2354fef846fa7e252078c5a4b74a3c302454de6167d6802a9e1d374e60"},
+ {file = "pyopencl-2022.2.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912bc9c48c443f5a88e8827ee68e9cb03f6a1d78ac6f64bb7a4d65f849fa7069"},
+ {file = "pyopencl-2022.2.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf8ebabee550849e8d4a21e13487d34b17c89749b38169bba249020f21363cf"},
+ {file = "pyopencl-2022.2.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384a1089df1256df00fde4fc87d1f7f500fc640f4ff47adf55a592502a16396"},
+ {file = "pyopencl-2022.2.4.tar.gz", hash = "sha256:b57c9ef8bd8e6db07e89106f3091ba236b24f95a38fd40dfb17d2ed7ff6be4ad"},
+]
+pyopenssl = [
+ {file = "pyOpenSSL-22.0.0-py2.py3-none-any.whl", hash = "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"},
+ {file = "pyOpenSSL-22.0.0.tar.gz", hash = "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf"},
+]
+pyparsing = [
+ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+pyprof2calltree = [
+ {file = "pyprof2calltree-1.4.5.tar.gz", hash = "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f"},
+]
+pyproj = [
+ {file = "pyproj-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07"},
+ {file = "pyproj-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285"},
+ {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b"},
+ {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be"},
+ {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6"},
+ {file = "pyproj-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5"},
+ {file = "pyproj-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0"},
+ {file = "pyproj-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134"},
+ {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c"},
+ {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5"},
+ {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c"},
+ {file = "pyproj-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:25a36e297f3e0524694d40259e3e895edc1a47492a0e30608268ffc1328e3f5d"},
+ {file = "pyproj-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:77d5f519f3cdb94b026ecca626f78db4f041afe201cf082079c8c0092a30b087"},
+ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccb4b70ad25218027f77e0c8934d10f9b7cdf91d5e64080147743d58fddbc3c0"},
+ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e161114bc92701647a83c4bbce79489984f12d980cabb365516e953d1450885"},
+ {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c"},
+ {file = "pyproj-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d"},
+ {file = "pyproj-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b"},
+ {file = "pyproj-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7"},
+ {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b"},
+ {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a"},
+ {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6"},
+ {file = "pyproj-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:65a0bcdbad95b3c00b419e5d75b1f7e450ec17349b5ea16bf7438ac1d50a12a2"},
+ {file = "pyproj-3.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:14ad113b5753c6057f9b2f3c85a6497cef7fa237c4328f2943c0223e98c1dde6"},
+ {file = "pyproj-3.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4688b4cd62cbd86b5e855f9e27d90fbb53f2b4c2ea1cd394a46919e1a4151b89"},
+ {file = "pyproj-3.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47ad53452ae1dc8b0bf1df920a210bb5616989085aa646592f8681f1d741a754"},
+ {file = "pyproj-3.4.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:48787962232109bad8b72e27949037a9b03591228a6955f25dbe451233e8648a"},
+ {file = "pyproj-3.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cb8592259ea54e7557523b079d3f2304081680bdb48bfbf0fd879ee6156129c"},
+ {file = "pyproj-3.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82200b4569d68b421c079d2973475b58d5959306fe758b43366e79fe96facfe5"},
+ {file = "pyproj-3.4.0.tar.gz", hash = "sha256:a708445927ace9857f52c3ba67d2915da7b41a8fdcd9b8f99a4c9ed60a75eb33"},
+]
+pyreadline3 = [
+ {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"},
+ {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"},
+]
+pyrsistent = [
+ {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
+ {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
+ {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
+ {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
+ {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
+ {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
+ {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
+ {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
+ {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
+ {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
+ {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
+ {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
+ {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
+ {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
+ {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
+ {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
+ {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
+ {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
+ {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
+ {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
+ {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
+]
+pyserial = [
+ {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"},
+ {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"},
+]
+pysocks = [
+ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
+ {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
+ {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
+]
+pytest = [
+ {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
+ {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
+]
+pytest-forked = [
+ {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"},
+ {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"},
+]
+pytest-xdist = [
+ {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"},
+ {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+python-engineio = [
+ {file = "python-engineio-4.3.4.tar.gz", hash = "sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae"},
+ {file = "python_engineio-4.3.4-py3-none-any.whl", hash = "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c"},
+]
+python-logstash = [
+ {file = "python-logstash-0.4.8.tar.gz", hash = "sha256:d04e1ce11ecc107e4a4f3b807fc57d96811e964a554081b3bbb44732f74ef5f9"},
+]
+python-socketio = [
+ {file = "python-socketio-5.7.2.tar.gz", hash = "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097"},
+ {file = "python_socketio-5.7.2-py3-none-any.whl", hash = "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3"},
+]
+pytools = [
+ {file = "pytools-2022.1.12.tar.gz", hash = "sha256:4d62875e9a2ab2a24e393a9a8b799492f1a721bffa840af3807bfd42871dd1f4"},
+]
+pytz = [
+ {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"},
+ {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"},
+]
+pywavelets = [
+ {file = "PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c"},
+ {file = "PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4"},
+ {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c"},
+ {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202"},
+ {file = "PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd"},
+ {file = "PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1"},
+ {file = "PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"},
+ {file = "PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c"},
+ {file = "PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4"},
+ {file = "PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93"},
+]
+pywin32 = [
+ {file = "pywin32-304-cp310-cp310-win32.whl", hash = "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3"},
+ {file = "pywin32-304-cp310-cp310-win_amd64.whl", hash = "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48"},
+ {file = "pywin32-304-cp310-cp310-win_arm64.whl", hash = "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f"},
+ {file = "pywin32-304-cp311-cp311-win32.whl", hash = "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269"},
+ {file = "pywin32-304-cp311-cp311-win_amd64.whl", hash = "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4"},
+ {file = "pywin32-304-cp311-cp311-win_arm64.whl", hash = "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43"},
+ {file = "pywin32-304-cp36-cp36m-win32.whl", hash = "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1"},
+ {file = "pywin32-304-cp36-cp36m-win_amd64.whl", hash = "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988"},
+ {file = "pywin32-304-cp37-cp37m-win32.whl", hash = "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5"},
+ {file = "pywin32-304-cp37-cp37m-win_amd64.whl", hash = "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df"},
+ {file = "pywin32-304-cp38-cp38-win32.whl", hash = "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96"},
+ {file = "pywin32-304-cp38-cp38-win_amd64.whl", hash = "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e"},
+ {file = "pywin32-304-cp39-cp39-win32.whl", hash = "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc"},
+ {file = "pywin32-304-cp39-cp39-win_amd64.whl", hash = "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9"},
+]
+pywin32-ctypes = [
+ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
+ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
+]
+pywinpty = [
+ {file = "pywinpty-2.0.8-cp310-none-win_amd64.whl", hash = "sha256:9cbf89834abc8d4d4c5f295f11e15dd93889a8069db876f2bc10cc64aa4060ac"},
+ {file = "pywinpty-2.0.8-cp37-none-win_amd64.whl", hash = "sha256:a2f9a95f3b74262ef73f1be5257c295c8caab1f79f081aa3400ca61c724f9bc6"},
+ {file = "pywinpty-2.0.8-cp38-none-win_amd64.whl", hash = "sha256:23389d56258d6a1fbc4b41257bd65e5bdabaf6fde7f30a13806e557ea9ee6865"},
+ {file = "pywinpty-2.0.8-cp39-none-win_amd64.whl", hash = "sha256:ea7c1da94eed5ef93e75026c67c60d4dca33ea9a1c212fa89221079a7b463c68"},
+ {file = "pywinpty-2.0.8.tar.gz", hash = "sha256:a89b9021c63ef78b1e7d8e14f0fac4748c88a0c2e4f529c84f37f6e72b914280"},
+]
+pyyaml = [
+ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+ {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+ {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+ {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
+ {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
+ {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
+ {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+ {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+ {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+ {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+ {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+ {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+ {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+ {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+ {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+ {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+pyzmq = [
+ {file = "pyzmq-23.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:a3fd44b5046d247e7f0f1660bcafe7b5fb0db55d0934c05dd57dda9e1f823ce7"},
+ {file = "pyzmq-23.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2141e6798d5981be04c08996d27962086a1aa3ea536fe9cf7e89817fd4523f86"},
+ {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a39ddb0431a68954bd318b923230fa5b649c9c62b0e8340388820c5f1b15bd2"},
+ {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06747014a5ad1b28cebf5bc1ddcdaccfb44e9b441d35e6feb1286c8a72e54be"},
+ {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0113d70b095339e99bb522fe7294f5ae6a7f3b2b8f52f659469a74b5cc7661"},
+ {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:71b32a1e827bdcbf73750e60370d3b07685816ff3d8695f450f0f8c3226503f8"},
+ {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:55568a020ad2cae9ae36da6058e7ca332a56df968f601cbdb7cf6efb2a77579a"},
+ {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c02a0cd39dc01659b3d6cb70bb3a41aebd9885fd78239acdd8d9c91351c4568"},
+ {file = "pyzmq-23.2.1-cp310-cp310-win32.whl", hash = "sha256:e1fe30bcd5aea5948c42685fad910cd285eacb2518ea4dc6c170d6b535bee95d"},
+ {file = "pyzmq-23.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:650389bbfca73955b262b2230423d89992f38ec48033307ae80e700eaa2fbb63"},
+ {file = "pyzmq-23.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:e753eee6d3b93c5354e8ba0a1d62956ee49355f0a36e00570823ef64e66183f5"},
+ {file = "pyzmq-23.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f07016e3cf088dbfc6e7c5a7b3f540db5c23b0190d539e4fd3e2b5e6beffa4b5"},
+ {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4805af9614b0b41b7e57d17673459facf85604dac502a5a9244f6e8c9a4de658"},
+ {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39dd252b683816935702825e5bf775df16090619ced9bb4ba68c2d0b6f0c9b18"},
+ {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:84678153432241bcdca2210cf4ff83560b200556867aea913ffbb960f5d5f340"},
+ {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:90d88f9d9a2ae6cfb1dc4ea2d1710cdf6456bc1b9a06dd1bb485c5d298f2517e"},
+ {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:794871988c34727c7f79bdfe2546e6854ae1fa2e1feb382784f23a9c6c63ecb3"},
+ {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c56b1a62a1fb87565343c57b6743fd5da6e138b8c6562361d7d9b5ce4acf399a"},
+ {file = "pyzmq-23.2.1-cp311-cp311-win32.whl", hash = "sha256:c3ebf1668664d20c8f7d468955f18379b7d1f7bc8946b13243d050fa3888c7ff"},
+ {file = "pyzmq-23.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ec9803aca9491fd6f0d853d2a6147f19f8deaaa23b1b713d05c5d09e56ea7142"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:385609812eafd9970c3752c51f2f6c4f224807e3e441bcfd8c8273877d00c8a8"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b861db65f6b8906c8d6db51dde2448f266f0c66bf28db2c37aea50f58a849859"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b1e79bba24f6df1712e3188d5c32c480d8eda03e8ecff44dc8ecb0805fa62f3"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8dc66f109a245653b19df0f44a5af7a3f14cb8ad6c780ead506158a057bd36ce"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b815991c7d024bf461f358ad871f2be1135576274caed5749c4828859e40354e"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:29b74774a0bfd3c4d98ac853f0bdca55bd9ec89d5b0def5486407cca54472ef8"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4bb798bef181648827019001f6be43e1c48b34b477763b37a8d27d8c06d197b8"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-win32.whl", hash = "sha256:565bd5ab81f6964fc4067ccf2e00877ad0fa917308975694bbb54378389215f8"},
+ {file = "pyzmq-23.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:1f368a82b29f80071781b20663c0fc0c8f6b13273f9f5abe1526af939534f90f"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c9cfaf530e6a7ff65f0afe275e99f983f68b54dfb23ea401f0bc297a632766b6"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c558b50402fca1acc94329c5d8f12aa429738904a5cfb32b9ed3c61235221bb"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20bafc4095eab00f41a510579363a3f5e1f5c69d7ee10f1d88895c4df0259183"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f619fd38fc2641abfb53cca719c165182500600b82c695cc548a0f05f764be05"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:044447ae4b2016a6b8697571fd633f799f860b19b76c4a2fd9b1140d52ee6745"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:49d30ba7074f469e8167917abf9eb854c6503ae10153034a6d4df33618f1db5f"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:48400b96788cdaca647021bf19a9cd668384f46e4d9c55cf045bdd17f65299c8"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-win32.whl", hash = "sha256:8a68f57b7a3f7b6b52ada79876be1efb97c8c0952423436e84d70cc139f16f0d"},
+ {file = "pyzmq-23.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9e5bf6e7239fc9687239de7a283aa8b801ab85371116045b33ae20132a1325d6"},
+ {file = "pyzmq-23.2.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:0ff6294e001129a9f22dcbfba186165c7e6f573c46de2704d76f873c94c65416"},
+ {file = "pyzmq-23.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffc6b1623d0f9affb351db4ca61f432dca3628a5ee015f9bf2bfbe9c6836881c"},
+ {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4d6f110c56f7d5b4d64dde3a382ae61b6d48174e30742859d8e971b18b6c9e5c"},
+ {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9269fbfe3a4eb2009199120861c4571ef1655fdf6951c3e7f233567c94e8c602"},
+ {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12e62ff0d5223ec09b597ab6d73858b9f64a51221399f3cb08aa495e1dff7935"},
+ {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6fd5d0d50cbcf4bc376861529a907bed026a4cbe8c22a500ff8243231ef02433"},
+ {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9d0ab2936085c85a1fc6f9fd8f89d5235ae99b051e90ec5baa5e73ad44346e1f"},
+ {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:022cf5ea7bcaa8a06a03c2706e0ae66904b6138b2155577cd34c64bc7cc637ab"},
+ {file = "pyzmq-23.2.1-cp38-cp38-win32.whl", hash = "sha256:28dbdb90b2f6b131f8f10e6081012e4e25234213433420e67e0c1162de537113"},
+ {file = "pyzmq-23.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:10d1910ec381b851aeb024a042a13db178cb1edf125e76a4e9d2548ad103aadb"},
+ {file = "pyzmq-23.2.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:99a5a77a10863493a1ee8dece02578c6b32025fb3afff91b40476bc489e81648"},
+ {file = "pyzmq-23.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aecd6ceaccc4b594e0092d6513ef3f1c0fa678dd89f86bb8ff1a47014b8fca35"},
+ {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:415ff62ac525d9add1e3550430a09b9928d2d24a20cc4ce809e67caac41219ab"},
+ {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67975a9e1237b9ccc78f457bef17691bbdd2055a9d26e81ee914ba376846d0ce"},
+ {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e106b64bad744fe469dc3dd864f2764d66399178c1bf39d45294cc7980f14f"},
+ {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c842109d31a9281d678f668629241c405928afbebd913c48a5a8e7aee61f63d"},
+ {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fefdf9b685fda4141b95ebec975946076a5e0723ff70b037032b2085c5317684"},
+ {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79a87831b47a9f6161ad23fa5e89d5469dc585abc49f90b9b07fea8905ae1234"},
+ {file = "pyzmq-23.2.1-cp39-cp39-win32.whl", hash = "sha256:342ca3077f47ec2ee41b9825142b614e03e026347167cbc72a59b618c4f6106c"},
+ {file = "pyzmq-23.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:5e05492be125dce279721d6b54fd1b956546ecc4bcdfcf8e7b4c413bc0874c10"},
+ {file = "pyzmq-23.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07ed8aaf7ffe150af873269690cc654ffeca7491f62aae0f3821baa181f8d5fe"},
+ {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad28ddb40db8e450d7d4bf8a1d765d3f87b63b10e7e9a825a3c130c6371a8c03"},
+ {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2f67b63f53c6994d601404fd1a329e6d940ac3dd1d92946a93b2b9c70df67b9f"},
+ {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c890309296f53f9aa32ffcfc51d805705e1982bffd27c9692a8f1e1b8de279f4"},
+ {file = "pyzmq-23.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:624fd38071a817644acdae075b92a23ea0bdd126a58148288e8284d23ec361ce"},
+ {file = "pyzmq-23.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a114992a193577cb62233abf8cb2832970f9975805a64740e325d2f895e7f85a"},
+ {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c780acddd2934c6831ff832ecbf78a45a7b62d4eb216480f863854a8b7d54fa7"},
+ {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d904f6595acfaaf99a1a61881fea068500c40374d263e5e073aa4005e5f9c28a"},
+ {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929d548b74c0f82f7f95b54e4a43f9e4ce2523cfb8a54d3f7141e45652304b2a"},
+ {file = "pyzmq-23.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f392cbea531b7142d1958c0d4a0c9c8d760dc451e5848d8dd3387804d3e3e62c"},
+ {file = "pyzmq-23.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0f09d85c45f58aa8e715b42f8b26beba68b3b63a8f7049113478aca26efbc30"},
+ {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e708fbfdf4ee3107422b69ca65da1b9f056b431fc0888096a8c1d6cd908e8f"},
+ {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35e635343ff367f697d00fa1484262bb68e36bc74c9b80737eac5a1e04c4e1b1"},
+ {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb9e38b2a590282704269585de7eb33bf43dc294cad092e1b172e23d4c217e5"},
+ {file = "pyzmq-23.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:407f909c4e8fde62fbdad9ebd448319792258cc0550c2815567a4d9d8d9e6d18"},
+ {file = "pyzmq-23.2.1.tar.gz", hash = "sha256:2b381aa867ece7d0a82f30a0c7f3d4387b7cf2e0697e33efaa5bed6c5784abcd"},
+]
+qtconsole = [
+ {file = "qtconsole-5.3.2-py3-none-any.whl", hash = "sha256:c29d24464f57cdbaa17d6f6060be6e6d5e29126e7feb57eebc1747433382b3d1"},
+ {file = "qtconsole-5.3.2.tar.gz", hash = "sha256:8eadf012e83ab018295803c247c6ab7eacd3d5ab1e1d88a0f37fdcfdab9295a3"},
+]
+qtpy = [
+ {file = "QtPy-2.2.1-py3-none-any.whl", hash = "sha256:268cf5328f41353be1b127e04a81bc74ec9a9b54c9ac75dd8fe0ff48d8ad6ead"},
+ {file = "QtPy-2.2.1.tar.gz", hash = "sha256:7d5231133b772e40b4ee514b6673aca558331e4b88ca038b26c9e16c5c95524f"},
+]
+qudida = [
+ {file = "qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6"},
+ {file = "qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8"},
+]
+reactivex = [
+ {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"},
+ {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"},
+]
+redis = [
+ {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"},
+ {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"},
+]
+requests = [
+ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+ {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+]
+requests-oauthlib = [
+ {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
+ {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
+]
+requests-toolbelt = [
+ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
+ {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
+]
+reverse-geocoder = [
+ {file = "reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c"},
+]
+s2sphere = [
+ {file = "s2sphere-0.2.5-py2.py3-none-any.whl", hash = "sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"},
+ {file = "s2sphere-0.2.5.tar.gz", hash = "sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf"},
+]
+scikit-image = [
+ {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"},
+ {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"},
+ {file = "scikit_image-0.19.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc"},
+ {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19"},
+ {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7"},
+ {file = "scikit_image-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732"},
+ {file = "scikit_image-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe"},
+ {file = "scikit_image-0.19.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef"},
+ {file = "scikit_image-0.19.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099"},
+ {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790"},
+ {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245"},
+ {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b"},
+ {file = "scikit_image-0.19.3-cp38-cp38-win32.whl", hash = "sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34"},
+ {file = "scikit_image-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d"},
+ {file = "scikit_image-0.19.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced"},
+ {file = "scikit_image-0.19.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d"},
+ {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6"},
+ {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83"},
+ {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"},
+ {file = "scikit_image-0.19.3-cp39-cp39-win32.whl", hash = "sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e"},
+ {file = "scikit_image-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919"},
+]
+scikit-learn = [
+ {file = "scikit-learn-1.1.2.tar.gz", hash = "sha256:7c22d1305b16f08d57751a4ea36071e2215efb4c09cb79183faa4e8e82a3dbf8"},
+ {file = "scikit_learn-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6c840f662b5d3377c4ccb8be1fc21bb52cb5d8b8790f8d6bf021739f84e543cf"},
+ {file = "scikit_learn-1.1.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2b8db962360c93554cab7bb3c096c4a24695da394dd4b3c3f13409f409b425bc"},
+ {file = "scikit_learn-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e7d1fc817867a350133f937aaebcafbc06192517cbdf0cf7e5774ad4d1adb9f"},
+ {file = "scikit_learn-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec3ea40d467966821843210c02117d82b097b54276fdcfb50f4dfb5c60dbe39"},
+ {file = "scikit_learn-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbef6ea1c012ff9f3e6f6e9ca006b8772d8383e177b898091e68fbd9b3f840f9"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a90ca42fe8242fd6ff56cda2fecc5fca586a88a24ab602d275d2d0dcc0b928fb"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a682ec0f82b6f30fb07486daed1c8001b6683cc66b51877644dfc532bece6a18"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c33e16e9a165af6012f5be530ccfbb672e2bc5f9b840238a05eb7f6694304e3f"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94c0146bad51daef919c402a3da8c1c6162619653e1c00c92baa168fda292f2"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-win32.whl", hash = "sha256:2f46c6e3ff1054a5ec701646dcfd61d43b8ecac4d416014daed8843cf4c33d4d"},
+ {file = "scikit_learn-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1e706deca9b2ad87ae27dafd5ac4e8eff01b6db492ed5c12cef4735ec5f21ea"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:567417dbbe6a6278399c3e6daf1654414a5a1a4d818d28f251fa7fc28730a1bf"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d6f232779023c3b060b80b5c82e5823723bc424dcac1d1a148aa2492c54d245d"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:589d46f28460469f444b898223b13d99db9463e1038dc581ba698111f612264b"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76800652fb6d6bf527bce36ecc2cc25738b28fe1a17bd294a218fff8e8bd6d50"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-win32.whl", hash = "sha256:1c8fecb7c9984d9ec2ea48898229f98aad681a0873e0935f2b7f724fbce4a047"},
+ {file = "scikit_learn-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:407e9a1cb9e6ba458a539986a9bd25546a757088095b3aab91d465b79a760d37"},
+]
+scipy = [
+ {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"},
+ {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"},
+ {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"},
+ {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"},
+ {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"},
+ {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"},
+ {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"},
+ {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"},
+ {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"},
+ {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"},
+ {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"},
+ {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"},
+ {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"},
+ {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"},
+ {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"},
+ {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"},
+ {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"},
+ {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"},
+ {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"},
+ {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"},
+ {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"},
+]
+scons = [
+ {file = "SCons-4.4.0-py3-none-any.whl", hash = "sha256:cbbd73b83cf85f1aaaf986370359de1bbfd3af97a765cb3554734f6dcec734e1"},
+ {file = "SCons-4.4.0.tar.gz", hash = "sha256:7703c4e9d2200b4854a31800c1dbd4587e1fa86e75f58795c740bcfa7eca7eaa"},
+]
+secretstorage = [
+ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
+ {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
+]
+segmentation-models-pytorch = [
+ {file = "segmentation_models_pytorch-0.2.1-py3-none-any.whl", hash = "sha256:98822571470867fb0f416c112c32f7f1d21702dd32195ec8f7736932c2de0486"},
+ {file = "segmentation_models_pytorch-0.2.1.tar.gz", hash = "sha256:86744552b04c6bedf7e10f7928791894d8d9b399b9ed58ed1a3236d2bf69ead6"},
+]
+send2trash = [
+ {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"},
+ {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"},
+]
+sentry-sdk = [
+ {file = "sentry-sdk-1.10.0.tar.gz", hash = "sha256:1b965bcdbfe52321bb1307c7c93c74035afdbfceb5f585f01a963327c5befc4e"},
+ {file = "sentry_sdk-1.10.0-py2.py3-none-any.whl", hash = "sha256:8c648e96e0e2ec5e17ca75a28c442e2f523453fa7cf761ec093f4a656153490e"},
+]
+setproctitle = [
+ {file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe"},
+ {file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18"},
+ {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005"},
+ {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7"},
+ {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d"},
+ {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246"},
+ {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494"},
+ {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806"},
+ {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94"},
+ {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1"},
+ {file = "setproctitle-1.3.2-cp310-cp310-win32.whl", hash = "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099"},
+ {file = "setproctitle-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865"},
+ {file = "setproctitle-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f"},
+ {file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5"},
+ {file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f"},
+ {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963"},
+ {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65"},
+ {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973"},
+ {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31"},
+ {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42"},
+ {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484"},
+ {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827"},
+ {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202"},
+ {file = "setproctitle-1.3.2-cp38-cp38-win32.whl", hash = "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1"},
+ {file = "setproctitle-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c"},
+ {file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76"},
+ {file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4"},
+ {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b"},
+ {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761"},
+ {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f"},
+ {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4"},
+ {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe"},
+ {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8"},
+ {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796"},
+ {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f"},
+ {file = "setproctitle-1.3.2-cp39-cp39-win32.whl", hash = "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c"},
+ {file = "setproctitle-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9"},
+ {file = "setproctitle-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575"},
+ {file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e"},
+ {file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b"},
+ {file = "setproctitle-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084"},
+ {file = "setproctitle-1.3.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6"},
+ {file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb"},
+ {file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb"},
+ {file = "setproctitle-1.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98"},
+ {file = "setproctitle-1.3.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122"},
+ {file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172"},
+ {file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13"},
+ {file = "setproctitle-1.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665"},
+ {file = "setproctitle-1.3.2.tar.gz", hash = "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd"},
+]
+setuptools = [
+ {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
+ {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
+]
+setuptools-scm = [
+ {file = "setuptools_scm-7.0.5-py3-none-any.whl", hash = "sha256:7930f720905e03ccd1e1d821db521bff7ec2ac9cf0ceb6552dd73d24a45d3b02"},
+ {file = "setuptools_scm-7.0.5.tar.gz", hash = "sha256:031e13af771d6f892b941adb6ea04545bbf91ebc5ce68c78aaf3fff6e1fb4844"},
+]
+shellingham = [
+ {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"},
+ {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"},
+]
+simplejson = [
+ {file = "simplejson-3.17.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a89acae02b2975b1f8e4974cb8cdf9bf9f6c91162fb8dec50c259ce700f2770a"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:82ff356ff91be0ab2293fc6d8d262451eb6ac4fd999244c4b5f863e049ba219c"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0de783e9c2b87bdd75b57efa2b6260c24b94605b5c9843517577d40ee0c3cc8a"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:d24a9e61df7a7787b338a58abfba975414937b609eb6b18973e25f573bc0eeeb"},
+ {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e8603e691580487f11306ecb066c76f1f4a8b54fb3bdb23fa40643a059509366"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9b01e7b00654115965a206e3015f0166674ec1e575198a62a977355597c0bef5"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:37bc0cf0e5599f36072077e56e248f3336917ded1d33d2688624d8ed3cefd7d2"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cf6e7d5fe2aeb54898df18db1baf479863eae581cce05410f61f6b4188c8ada1"},
+ {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bdfc54b4468ed4cd7415928cbe782f4d782722a81aeb0f81e2ddca9932632211"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd16302d39c4d6f4afde80edd0c97d4db643327d355a312762ccd9bd2ca515ed"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:deac4bdafa19bbb89edfb73b19f7f69a52d0b5bd3bb0c4ad404c1bbfd7b4b7fd"},
+ {file = "simplejson-3.17.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8bbdb166e2fb816e43ab034c865147edafe28e1b19c72433147789ac83e2dda"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7854326920d41c3b5d468154318fe6ba4390cb2410480976787c640707e0180"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:04e31fa6ac8e326480703fb6ded1488bfa6f1d3f760d32e29dbf66d0838982ce"},
+ {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f63600ec06982cdf480899026f4fda622776f5fabed9a869fdb32d72bc17e99a"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e03c3b8cc7883a54c3f34a6a135c4a17bc9088a33f36796acdb47162791b02f6"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2d30d6c1652140181dc6861f564449ad71a45e4f165a6868c27d36745b65d40"},
+ {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1aa6e4cae8e3b8d5321be4f51c5ce77188faf7baa9fe1e78611f93a8eed2882"},
+ {file = "simplejson-3.17.6-cp310-cp310-win32.whl", hash = "sha256:97202f939c3ff341fc3fa84d15db86156b1edc669424ba20b0a1fcd4a796a045"},
+ {file = "simplejson-3.17.6-cp310-cp310-win_amd64.whl", hash = "sha256:80d3bc9944be1d73e5b1726c3bbfd2628d3d7fe2880711b1eb90b617b9b8ac70"},
+ {file = "simplejson-3.17.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9fa621b3c0c05d965882c920347b6593751b7ab20d8fa81e426f1735ca1a9fc7"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2fb11922f58df8528adfca123f6a84748ad17d066007e7ac977720063556bd"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:724c1fe135aa437d5126138d977004d165a3b5e2ee98fc4eb3e7c0ef645e7e27"},
+ {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ff4ac6ff3aa8f814ac0f50bf218a2e1a434a17aafad4f0400a57a8cc62ef17f"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:67093a526e42981fdd954868062e56c9b67fdd7e712616cc3265ad0c210ecb51"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b4af7ad7e4ac515bc6e602e7b79e2204e25dbd10ab3aa2beef3c5a9cad2c7"},
+ {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1c9b1ed7ed282b36571638297525f8ef80f34b3e2d600a56f962c6044f24200d"},
+ {file = "simplejson-3.17.6-cp36-cp36m-win32.whl", hash = "sha256:632ecbbd2228575e6860c9e49ea3cc5423764d5aa70b92acc4e74096fb434044"},
+ {file = "simplejson-3.17.6-cp36-cp36m-win_amd64.whl", hash = "sha256:4c09868ddb86bf79b1feb4e3e7e4a35cd6e61ddb3452b54e20cf296313622566"},
+ {file = "simplejson-3.17.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b6bd8144f15a491c662f06814bd8eaa54b17f26095bb775411f39bacaf66837"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5decdc78849617917c206b01e9fc1d694fd58caa961be816cb37d3150d613d9a"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:521877c7bd060470806eb6335926e27453d740ac1958eaf0d8c00911bc5e1802"},
+ {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:65b998193bd7b0c7ecdfffbc825d808eac66279313cb67d8892bb259c9d91494"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac786f6cb7aa10d44e9641c7a7d16d7f6e095b138795cd43503769d4154e0dc2"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3ff5b3464e1ce86a8de8c88e61d4836927d5595c2162cab22e96ff551b916e81"},
+ {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:69bd56b1d257a91e763256d63606937ae4eb890b18a789b66951c00062afec33"},
+ {file = "simplejson-3.17.6-cp37-cp37m-win32.whl", hash = "sha256:b81076552d34c27e5149a40187a8f7e2abb2d3185576a317aaf14aeeedad862a"},
+ {file = "simplejson-3.17.6-cp37-cp37m-win_amd64.whl", hash = "sha256:07ecaafc1b1501f275bf5acdee34a4ad33c7c24ede287183ea77a02dc071e0c0"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:068670af975247acbb9fc3d5393293368cda17026db467bf7a51548ee8f17ee1"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4d1c135af0c72cb28dd259cf7ba218338f4dc027061262e46fe058b4e6a4c6a3"},
+ {file = "simplejson-3.17.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23fe704da910ff45e72543cbba152821685a889cf00fc58d5c8ee96a9bad5f94"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f444762fed1bc1fd75187ef14a20ed900c1fbb245d45be9e834b822a0223bc81"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:681eb4d37c9a9a6eb9b3245a5e89d7f7b2b9895590bb08a20aa598c1eb0a1d9d"},
+ {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e8607d8f6b4f9d46fee11447e334d6ab50e993dd4dbfb22f674616ce20907ab"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b10556817f09d46d420edd982dd0653940b90151d0576f09143a8e773459f6fe"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e1ec8a9ee0987d4524ffd6299e778c16cc35fef6d1a2764e609f90962f0b293a"},
+ {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b4126cac7d69ac06ff22efd3e0b3328a4a70624fcd6bca4fc1b4e6d9e2e12bf"},
+ {file = "simplejson-3.17.6-cp38-cp38-win32.whl", hash = "sha256:35a49ebef25f1ebdef54262e54ae80904d8692367a9f208cdfbc38dbf649e00a"},
+ {file = "simplejson-3.17.6-cp38-cp38-win_amd64.whl", hash = "sha256:743cd768affaa508a21499f4858c5b824ffa2e1394ed94eb85caf47ac0732198"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb62d517a516128bacf08cb6a86ecd39fb06d08e7c4980251f5d5601d29989ba"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:12133863178a8080a3dccbf5cb2edfab0001bc41e5d6d2446af2a1131105adfe"},
+ {file = "simplejson-3.17.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5540fba2d437edaf4aa4fbb80f43f42a8334206ad1ad3b27aef577fd989f20d9"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d74ee72b5071818a1a5dab47338e87f08a738cb938a3b0653b9e4d959ddd1fd9"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:28221620f4dcabdeac310846629b976e599a13f59abb21616356a85231ebd6ad"},
+ {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b09bc62e5193e31d7f9876220fb429ec13a6a181a24d897b9edfbbdbcd678851"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7255a37ff50593c9b2f1afa8fafd6ef5763213c1ed5a9e2c6f5b9cc925ab979f"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:401d40969cee3df7bda211e57b903a534561b77a7ade0dd622a8d1a31eaa8ba7"},
+ {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a649d0f66029c7eb67042b15374bd93a26aae202591d9afd71e111dd0006b198"},
+ {file = "simplejson-3.17.6-cp39-cp39-win32.whl", hash = "sha256:522fad7be85de57430d6d287c4b635813932946ebf41b913fe7e880d154ade2e"},
+ {file = "simplejson-3.17.6-cp39-cp39-win_amd64.whl", hash = "sha256:3fe87570168b2ae018391e2b43fbf66e8593a86feccb4b0500d134c998983ccc"},
+ {file = "simplejson-3.17.6.tar.gz", hash = "sha256:cf98038d2abf63a1ada5730e91e84c642ba6c225b0198c3684151b1f80c5f8a6"},
+]
+six = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+smbus2 = [
+ {file = "smbus2-0.4.2-py2.py3-none-any.whl", hash = "sha256:50f3c78e436b42a9583948be06961a8104cf020ebad5edfaaf2657528bef0818"},
+ {file = "smbus2-0.4.2.tar.gz", hash = "sha256:634541ed794068a822fe7499f1577468b9d4641b68dd9bfb6d0eb7270f4d2a32"},
+]
+sniffio = [
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+snowballstemmer = [
+ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+]
+sortedcontainers = [
+ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
+sounddevice = [
+ {file = "sounddevice-0.4.5-py3-none-any.whl", hash = "sha256:5cea4afd9412e731f50ae09a54d68b10628a604cfd56b42a976c54d424c6c39d"},
+ {file = "sounddevice-0.4.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:0875173595a8bd5a66b5a03a3d958e7b89c3b956b8befbe4491a24a3ce7784c0"},
+ {file = "sounddevice-0.4.5-py3-none-win32.whl", hash = "sha256:442adf53850916374a58f902200aaf9412227378181264e60c966da64be47d41"},
+ {file = "sounddevice-0.4.5-py3-none-win_amd64.whl", hash = "sha256:d3216c5d3d678c3301058e9aac7000879e255140c524c9ef98730091b67ea676"},
+ {file = "sounddevice-0.4.5.tar.gz", hash = "sha256:2fe0d41299e4f3037dad2acede4eff0666b34a1fa3da5335e47120373964bef5"},
+]
+soupsieve = [
+ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
+ {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
+]
+sphinx = [
+ {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"},
+ {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"},
+]
+sphinx-rtd-theme = [
+ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"},
+ {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"},
+]
+sphinx-sitemap = [
+ {file = "sphinx-sitemap-2.2.0.tar.gz", hash = "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737"},
+]
+sphinxcontrib-applehelp = [
+ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
+ {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
+]
+sphinxcontrib-devhelp = [
+ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
+ {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
+]
+sphinxcontrib-htmlhelp = [
+ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
+ {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
+]
+sphinxcontrib-jsmath = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+sphinxcontrib-qthelp = [
+ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
+ {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
+]
+sphinxcontrib-serializinghtml = [
+ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
+ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
+]
+spidev = [
+ {file = "spidev-3.6-cp39-cp39-linux_armv7l.whl", hash = "sha256:280abc00a1ef7780ef62c3f294f52a2527b6c47d8c269fea98664970bcaf6da5"},
+ {file = "spidev-3.6.tar.gz", hash = "sha256:14dbc37594a4aaef85403ab617985d3c3ef464d62bc9b769ef552db53701115b"},
+]
+sqlalchemy = [
+ {file = "SQLAlchemy-1.4.42-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:28e881266a172a4d3c5929182fde6bb6fba22ac93f137d5380cc78a11a9dd124"},
+ {file = "SQLAlchemy-1.4.42-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ca9389a00f639383c93ed00333ed763812f80b5ae9e772ea32f627043f8c9c88"},
+ {file = "SQLAlchemy-1.4.42-cp27-cp27m-win32.whl", hash = "sha256:1d0c23ecf7b3bc81e29459c34a3f4c68ca538de01254e24718a7926810dc39a6"},
+ {file = "SQLAlchemy-1.4.42-cp27-cp27m-win_amd64.whl", hash = "sha256:6c9d004eb78c71dd4d3ce625b80c96a827d2e67af9c0d32b1c1e75992a7916cc"},
+ {file = "SQLAlchemy-1.4.42-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9e3a65ce9ed250b2f096f7b559fe3ee92e6605fab3099b661f0397a9ac7c8d95"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:2e56dfed0cc3e57b2f5c35719d64f4682ef26836b81067ee6cfad062290fd9e2"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b42c59ffd2d625b28cdb2ae4cde8488543d428cba17ff672a543062f7caee525"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22459fc1718785d8a86171bbe7f01b5c9d7297301ac150f508d06e62a2b4e8d2"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df76e9c60879fdc785a34a82bf1e8691716ffac32e7790d31a98d7dec6e81545"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-win32.whl", hash = "sha256:e7e740453f0149437c101ea4fdc7eea2689938c5760d7dcc436c863a12f1f565"},
+ {file = "SQLAlchemy-1.4.42-cp310-cp310-win_amd64.whl", hash = "sha256:effc89e606165ca55f04f3f24b86d3e1c605e534bf1a96e4e077ce1b027d0b71"},
+ {file = "SQLAlchemy-1.4.42-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:97ff50cd85bb907c2a14afb50157d0d5486a4b4639976b4a3346f34b6d1b5272"},
+ {file = "SQLAlchemy-1.4.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12c6949bae10f1012ab5c0ea52ab8db99adcb8c7b717938252137cdf694c775"},
+ {file = "SQLAlchemy-1.4.42-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11b2ec26c5d2eefbc3e6dca4ec3d3d95028be62320b96d687b6e740424f83b7d"},
+ {file = "SQLAlchemy-1.4.42-cp311-cp311-win32.whl", hash = "sha256:6045b3089195bc008aee5c273ec3ba9a93f6a55bc1b288841bd4cfac729b6516"},
+ {file = "SQLAlchemy-1.4.42-cp311-cp311-win_amd64.whl", hash = "sha256:0501f74dd2745ec38f44c3a3900fb38b9db1ce21586b691482a19134062bf049"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6e39e97102f8e26c6c8550cb368c724028c575ec8bc71afbbf8faaffe2b2092a"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15d878929c30e41fb3d757a5853b680a561974a0168cd33a750be4ab93181628"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fa5b7eb2051e857bf83bade0641628efe5a88de189390725d3e6033a1fff4257"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1c5f8182b4f89628d782a183d44db51b5af84abd6ce17ebb9804355c88a7b5"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-win32.whl", hash = "sha256:a7dd5b7b34a8ba8d181402d824b87c5cee8963cb2e23aa03dbfe8b1f1e417cde"},
+ {file = "SQLAlchemy-1.4.42-cp36-cp36m-win_amd64.whl", hash = "sha256:5ede1495174e69e273fad68ad45b6d25c135c1ce67723e40f6cf536cb515e20b"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:9256563506e040daddccaa948d055e006e971771768df3bb01feeb4386c242b0"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4948b6c5f4e56693bbeff52f574279e4ff972ea3353f45967a14c30fb7ae2beb"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1811a0b19a08af7750c0b69e38dec3d46e47c4ec1d74b6184d69f12e1c99a5e0"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b01d9cd2f9096f688c71a3d0f33f3cd0af8549014e66a7a7dee6fc214a7277d"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-win32.whl", hash = "sha256:bd448b262544b47a2766c34c0364de830f7fb0772d9959c1c42ad61d91ab6565"},
+ {file = "SQLAlchemy-1.4.42-cp37-cp37m-win_amd64.whl", hash = "sha256:04f2598c70ea4a29b12d429a80fad3a5202d56dce19dd4916cc46a965a5ca2e9"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ab7c158f98de6cb4f1faab2d12973b330c2878d0c6b689a8ca424c02d66e1b3"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee377eb5c878f7cefd633ab23c09e99d97c449dd999df639600f49b74725b80"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:934472bb7d8666727746a75670a1f8d91a9cae8c464bba79da30a0f6faccd9e1"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb94a3d1ba77ff2ef11912192c066f01e68416f554c194d769391638c8ad09a"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-win32.whl", hash = "sha256:f0f574465b78f29f533976c06b913e54ab4980b9931b69aa9d306afff13a9471"},
+ {file = "SQLAlchemy-1.4.42-cp38-cp38-win_amd64.whl", hash = "sha256:a85723c00a636eed863adb11f1e8aaa36ad1c10089537823b4540948a8429798"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5ce6929417d5dce5ad1d3f147db81735a4a0573b8fb36e3f95500a06eaddd93e"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723e3b9374c1ce1b53564c863d1a6b2f1dc4e97b1c178d9b643b191d8b1be738"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:876eb185911c8b95342b50a8c4435e1c625944b698a5b4a978ad2ffe74502908"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd49af453e590884d9cdad3586415922a8e9bb669d874ee1dc55d2bc425aacd"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-win32.whl", hash = "sha256:e4ef8cb3c5b326f839bfeb6af5f406ba02ad69a78c7aac0fbeeba994ad9bb48a"},
+ {file = "SQLAlchemy-1.4.42-cp39-cp39-win_amd64.whl", hash = "sha256:5f966b64c852592469a7eb759615bbd351571340b8b344f1d3fa2478b5a4c934"},
+ {file = "SQLAlchemy-1.4.42.tar.gz", hash = "sha256:177e41914c476ed1e1b77fd05966ea88c094053e17a85303c4ce007f88eff363"},
+]
+stack-data = [
+ {file = "stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2"},
+ {file = "stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b"},
+]
+subprocess32 = [
+ {file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"},
+ {file = "subprocess32-3.5.4-cp27-cp27mu-manylinux2014_x86_64.whl", hash = "sha256:e45d985aef903c5b7444d34350b05da91a9e0ea015415ab45a21212786c649d0"},
+ {file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"},
+]
+sympy = [
+ {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"},
+ {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"},
+]
+tabulate = [
+ {file = "tabulate-0.8.10-py3-none-any.whl", hash = "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc"},
+ {file = "tabulate-0.8.10.tar.gz", hash = "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519"},
+]
+tenacity = [
+ {file = "tenacity-8.1.0-py3-none-any.whl", hash = "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac"},
+ {file = "tenacity-8.1.0.tar.gz", hash = "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445"},
+]
+terminado = [
+ {file = "terminado-0.16.0-py3-none-any.whl", hash = "sha256:3e995072a7178a104c41134548ce9b03e4e7f0a538e9c29df4f1fbc81c7cfc75"},
+ {file = "terminado-0.16.0.tar.gz", hash = "sha256:fac14374eb5498bdc157ed32e510b1f60d5c3c7981a9f5ba018bb9a64cec0c25"},
+]
+threadpoolctl = [
+ {file = "threadpoolctl-3.1.0-py3-none-any.whl", hash = "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b"},
+ {file = "threadpoolctl-3.1.0.tar.gz", hash = "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"},
+]
+tifffile = [
+ {file = "tifffile-2022.10.10-py3-none-any.whl", hash = "sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503"},
+ {file = "tifffile-2022.10.10.tar.gz", hash = "sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e"},
+]
+timezonefinder = [
+ {file = "timezonefinder-6.1.3-cp38-cp38-manylinux_2_31_x86_64.whl", hash = "sha256:96c96db94e75e072187843152e6c5dc0718500a9a91986032365abe09162d0e7"},
+ {file = "timezonefinder-6.1.3.tar.gz", hash = "sha256:f2ee561b1e7692b933fcd914df38800e93db7caf278e7328de7328829b04f275"},
+]
+timm = [
+ {file = "timm-0.4.12-py3-none-any.whl", hash = "sha256:dba6b1702b7d24bf9f0f1c2fc394b4ee28f93cde5404f1dc732d63ccd00533b6"},
+ {file = "timm-0.4.12.tar.gz", hash = "sha256:b14be70dbd4528b5ca8657cf5bc2672c7918c3d9ebfbffe80f4785b54e884b1e"},
+]
+tinycss2 = [
+ {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
+ {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"},
+]
+toml = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+tomli = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+tomlkit = [
+ {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"},
+ {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"},
+]
+torch = []
+torchsummary = [
+ {file = "torchsummary-1.5.1-py3-none-any.whl", hash = "sha256:10f41d1743fb918f83293f13183f532ab1bb8f6639a1b89e5f8592ec1919a976"},
+ {file = "torchsummary-1.5.1.tar.gz", hash = "sha256:981bf689e22e0cf7f95c746002f20a24ad26aa6b9d861134a14bc6ce92230590"},
+]
+torchvision = []
+tornado = [
+ {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"},
+ {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"},
+ {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"},
+ {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"},
+ {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"},
+ {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"},
+ {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"},
+ {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"},
+ {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"},
+ {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"},
+ {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"},
+]
+tqdm = [
+ {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"},
+ {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"},
+]
+traitlets = [
+ {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"},
+ {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"},
+]
+triton = [
+ {file = "triton-1.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8441e6f44517aef8f6345f621c003926cbe970892802411a949ccda516cbd5ba"},
+ {file = "triton-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840776bc1f4757fb2d6af974694c5e5313220ceec238ee6118b9728bc2aa9ade"},
+ {file = "triton-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d42cdaa7d56de463d762c18cc876bfd0828a2b6a706263393fe7e10d1c83ca"},
+ {file = "triton-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc19c0e902bbf7d29de4d444455608065a2c56e3524f4bc94e724511ca518f3"},
+ {file = "triton-1.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02f798cd2dd922228082ce1a4e9d81badb9a6217a9aac6d783e95bf7055974d"},
+]
+types-atomicwrites = [
+ {file = "types-atomicwrites-1.4.5.1.tar.gz", hash = "sha256:9e9f0923ebf93524b28bcece5a23ac8c3820f39b060df29f671936d2e4bc04bc"},
+ {file = "types_atomicwrites-1.4.5.1-py3-none-any.whl", hash = "sha256:2f1febbdc78b55453b189fa5b136dce34bab7d1d82319163d470e404aab55c83"},
+]
+types-certifi = [
+ {file = "types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f"},
+ {file = "types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a"},
+]
+types-pycurl = [
+ {file = "types-pycurl-7.45.1.tar.gz", hash = "sha256:82e00aa2981595bfa55e5a3bac42221eb3435b0026dffbe1177f6ac9f2d51200"},
+ {file = "types_pycurl-7.45.1-py3-none-any.whl", hash = "sha256:9eab3414da4a1b1e9a628bd288fc5172b8c182e1d9fb6d8d082441b0fd64baed"},
+]
+types-pyyaml = [
+ {file = "types-PyYAML-6.0.12.tar.gz", hash = "sha256:f6f350418125872f3f0409d96a62a5a5ceb45231af5cc07ee0034ec48a3c82fa"},
+ {file = "types_PyYAML-6.0.12-py3-none-any.whl", hash = "sha256:29228db9f82df4f1b7febee06bbfb601677882e98a3da98132e31c6874163e15"},
+]
+types-requests = [
+ {file = "types-requests-2.28.11.2.tar.gz", hash = "sha256:fdcd7bd148139fb8eef72cf4a41ac7273872cad9e6ada14b11ff5dfdeee60ed3"},
+ {file = "types_requests-2.28.11.2-py3-none-any.whl", hash = "sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef"},
+]
+types-urllib3 = [
+ {file = "types-urllib3-1.26.25.1.tar.gz", hash = "sha256:a948584944b2412c9a74b9cf64f6c48caf8652cb88b38361316f6d15d8a184cd"},
+ {file = "types_urllib3-1.26.25.1-py3-none-any.whl", hash = "sha256:f6422596cc9ee5fdf68f9d547f541096a20c2dcfd587e37c804c9ea720bf5cb2"},
+]
+typing-extensions = [
+ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
+ {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+]
+utm = [
+ {file = "utm-0.7.0.tar.gz", hash = "sha256:3c9a3650e98bb6eecec535418d0dfd4db8f88c8ceaca112a0ff0787e116566e2"},
+]
+virtualenv = [
+ {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"},
+ {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
+]
+virtualenv-clone = [
+ {file = "virtualenv-clone-0.5.7.tar.gz", hash = "sha256:418ee935c36152f8f153c79824bb93eaf6f0f7984bae31d3f48f350b9183501a"},
+ {file = "virtualenv_clone-0.5.7-py3-none-any.whl", hash = "sha256:44d5263bceed0bac3e1424d64f798095233b64def1c5689afa43dc3223caf5b0"},
+]
+wcwidth = [
+ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+ {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
+]
+webencodings = [
+ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
+websocket-client = [
+ {file = "websocket-client-1.4.1.tar.gz", hash = "sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef"},
+ {file = "websocket_client-1.4.1-py3-none-any.whl", hash = "sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090"},
+]
+werkzeug = [
+ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
+ {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
+]
+widgetsnbextension = [
+ {file = "widgetsnbextension-4.0.3-py3-none-any.whl", hash = "sha256:7f3b0de8fda692d31ef03743b598620e31c2668b835edbd3962d080ccecf31eb"},
+ {file = "widgetsnbextension-4.0.3.tar.gz", hash = "sha256:34824864c062b0b3030ad78210db5ae6a3960dfb61d5b27562d6631774de0286"},
+]
+wrapt = [
+ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
+ {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
+ {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
+ {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
+ {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
+ {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
+ {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
+ {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
+ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
+ {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
+]
+xattr = [
+ {file = "xattr-0.9.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:58a9fb4fd19b467e88f4b75b5243706caa57e312d3aee757b53b57c7fd0f4ba9"},
+ {file = "xattr-0.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e71efca59705c7abde5b7f76323ebe00ed2977f10cba4204b9421dada036b5ca"},
+ {file = "xattr-0.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:1aad96b6603961c3d1ca1aaa8369b1a8d684a7b37357b2428087c286bf0e561c"},
+ {file = "xattr-0.9.9-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:46cb74f98d31d9d70f975ec3e6554360a9bdcbb4b9fb50a69fabe54f9f928c97"},
+ {file = "xattr-0.9.9-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:80c2db56058a687d7439be041f916cbeb2943fbe2623e53d5da721a4552d8991"},
+ {file = "xattr-0.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c360d1cc42e885b64d84f64de3c501dd7bce576248327ef583b4625ee63aa023"},
+ {file = "xattr-0.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:debd87afe6bdf88c3689bde52eecf2b166388b13ef7388259d23223374db417d"},
+ {file = "xattr-0.9.9-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:4280c9f33a8678828f1bbc3d3dc8b823b5e4a113ee5ecb0fb98bff60cc2b9ad1"},
+ {file = "xattr-0.9.9-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e0916ec1656d2071cd3139d1f52426825985d8ed076f981ef7f0bc13dfa8e96c"},
+ {file = "xattr-0.9.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a517916fbf2f58a3222bb2048fe1eeff4e23e07a4ce6228a27de004c80bf53ab"},
+ {file = "xattr-0.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e886c882b3b28c7a684c3e3daf46347da5428a46b88bc6d62c4867d574b90c54"},
+ {file = "xattr-0.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:373e3d1fd9258438fc38d1438142d3659f36743f374a20457346ef26741ed441"},
+ {file = "xattr-0.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7beeb54ca140273b2f6320bb98b701ec30628af2ebe4eb30f7051419eb4ef3"},
+ {file = "xattr-0.9.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3ca29cdaae9c47c625d84bb6c9046f7275cccde0ea805caa23ca58d3671f3f"},
+ {file = "xattr-0.9.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c381d890931cd18b137ce3fb5c5f08b672c3c61e2e47b1a7442ee46e827abfe"},
+ {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:59c5783ccf57cf2700ce57d51a92134900ed26f6ab20d209f383fb898903fea6"},
+ {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:966b885b69d95362e2a12d39f84889cf857090e57263b5ac33409498aa00c160"},
+ {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efaaf0cb1ea8e9febb7baad301ae8cc9ad7a96fdfc5c6399d165e7a19e3e61ce"},
+ {file = "xattr-0.9.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f19fa75ed1e9db86354efab29869cb2be6976d456bd7c89e67b118d5384a1d98"},
+ {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ca28ad06828244b315214ee35388f57e81e90aac2ceac3f32e42ae394e31b9c"},
+ {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:532c7f1656dd2fe937116b9e210229f716d7fc7ac142f9cdace7da92266d32e8"},
+ {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c28033c17e98c67e0def9d6ebd415ad3c006a7bc3fee6bad79c5e52d0dff49"},
+ {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:473cabb30e544ea08c8c01c1ef18053147cdc8552d443ac97815e46fbb13c7d4"},
+ {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c4a308522b444d090fbd66a385c9519b6b977818226921b0d2fc403667c93564"},
+ {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:82493434488aca72d88b5129dac8f212e7b8bdca7ceffe7bb977c850f2452e4e"},
+ {file = "xattr-0.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e41d289706c7e8940f4d08e865da6a8ae988123e40a44f9a97ddc09e67795d7d"},
+ {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef08698e360cf43688dca3db3421b156b29948a714d5d089348073f463c11646"},
+ {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eb10ac16ca8d534c0395425d52121e0c1981f808e1b3f577f6a5ec33d3853e4"},
+ {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5605fec07b0e964bd980cc70ec335b9eb1b7ac7c6f314c7c2d8f54b09104fe4c"},
+ {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:974e7d577ddb15e4552fb0ec10a4cfe09bdf6267365aa2b8394bb04637785aad"},
+ {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ad6777de922c638bfa87a0d7faebc5722ddef04a1210b2a8909289b58b769af0"},
+ {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3887e70873ebf0efbde32f9929ec1c7e45ec0013561743e2cc0406a91e51113b"},
+ {file = "xattr-0.9.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:83caa8e93a45a0f25f91b92d9b45f490c87bff74f02555df6312efeba0dacc31"},
+ {file = "xattr-0.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e33ec0a1d913d946d1ab7509f37ee37306c45af735347f13b963df34ffe6e029"},
+ {file = "xattr-0.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:263c58dca83372260c5c195e0b59959e38e1f107f0b7350de82e3db38479036c"},
+ {file = "xattr-0.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:125dfb9905428162349d3b8b825d9a18280893f0cb0db2a2467d5ef253fa6ce2"},
+ {file = "xattr-0.9.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e243524e0dde16d7a2e1b52512ad2c6964df2143dd1c79b820dcb4c6c0822c20"},
+ {file = "xattr-0.9.9-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ec07d24a14406bdc6a123041c63a88e1c4a3f820e4a7d30f7609d57311b499"},
+ {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85c1df5f1d209345ea96de137419e886a27bb55076b3ae01faacf35aafcf3a61"},
+ {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ca74d3eff92d6dc16e271fbad9cbab547fb9a0c983189c4031c3ff3d150dd871"},
+ {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d17505e49ac70c0e71939c5aac96417a863583fb30a2d6304d5ac881230548f"},
+ {file = "xattr-0.9.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ae47a6398d3c04623fa386a4aa2f66e5cd3cdb1a7e69d1bfaeb8c73983bf271"},
+ {file = "xattr-0.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:809e2537d0aff9fca97dacf3245cbbaf711bbced5d1b0235a8d1906b04e26114"},
+ {file = "xattr-0.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de3af84364f06d67b3662ccf7c1a73e1d389d8d274394e952651e7bf1bbd2718"},
+ {file = "xattr-0.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b62cdad232d2d2dedd39b543701db8e3883444ec0d57ce3fab8f75e5f8b0301"},
+ {file = "xattr-0.9.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b11d2eda397d47f7075743409683c233519ca52aa1dac109b413a4d8c15b740"},
+ {file = "xattr-0.9.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661c0a939aefdf071887121f534bb10588d69c7b2dfca5c486af2fc81a0786e8"},
+ {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5db7c2db320a8d5264d437d71f1eb7270a7e4a6545296e7766161d17752590b7"},
+ {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:83203e60cbaca9536d297e5039b285a600ff84e6e9e8536fe2d521825eeeb437"},
+ {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42bfb4e4da06477e739770ac6942edbdc71e9fc3b497b67db5fba712fa8109c2"},
+ {file = "xattr-0.9.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:67047d04d1c56ad4f0f5886085e91b0077238ab3faaec6492c3c21920c6566eb"},
+ {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:885782bc82ded1a3f684d54a1af259ae9fcc347fa54b5a05b8aad82b8a42044c"},
+ {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bc84ccec618b5aa089e7cee8b07fcc92d4069aac4053da604c8143a0d6b1381"},
+ {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baeff3e5dda8ea7e9424cfaee51829f46afe3836c30d02f343f9049c685681ca"},
+ {file = "xattr-0.9.9.tar.gz", hash = "sha256:09cb7e1efb3aa1b4991d6be4eb25b73dc518b4fe894f0915f5b0dcede972f346"},
+]
+yarl = [
+ {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"},
+ {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"},
+ {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"},
+ {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"},
+ {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"},
+ {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"},
+ {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"},
+ {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"},
+ {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"},
+ {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"},
+ {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"},
+ {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"},
+ {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"},
+ {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"},
+ {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"},
+ {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"},
+ {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"},
+ {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"},
+ {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"},
+ {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"},
+ {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"},
+ {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"},
+ {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"},
+ {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"},
+ {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"},
+ {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"},
+ {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"},
+ {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"},
+ {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"},
+ {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"},
+ {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"},
+ {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"},
+ {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"},
+ {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"},
+ {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"},
+ {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"},
+ {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"},
+ {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"},
+ {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"},
+ {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"},
+ {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"},
+ {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"},
+ {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"},
+ {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"},
+ {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"},
+ {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"},
+ {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"},
+ {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"},
+ {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"},
+ {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"},
+ {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"},
+ {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"},
+ {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"},
+ {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"},
+ {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"},
+ {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"},
+ {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"},
+ {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"},
+ {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"},
+]
+zerorpc = []
+zipp = [
+ {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"},
+ {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"},
+]
+zope-event = [
+ {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"},
+ {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"},
+]
+zope-interface = [
+ {file = "zope.interface-5.5.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:2cb3003941f5f4fa577479ac6d5db2b940acb600096dd9ea9bf07007f5cab46f"},
+ {file = "zope.interface-5.5.0-cp27-cp27m-win32.whl", hash = "sha256:8c791f4c203ccdbcda588ea4c8a6e4353e10435ea48ddd3d8734a26fe9714cba"},
+ {file = "zope.interface-5.5.0-cp27-cp27m-win_amd64.whl", hash = "sha256:3eedf3d04179774d750e8bb4463e6da350956a50ed44d7b86098e452d7ec385e"},
+ {file = "zope.interface-5.5.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:58a66c2020a347973168a4a9d64317bac52f9fdfd3e6b80b252be30da881a64e"},
+ {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7912ae76e1df6a1fb841b619110b1be4c86dfb36699d7fd2f177105cdea885"},
+ {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:423c074e404f13e6fa07f4454f47fdbb38d358be22945bc812b94289d9142374"},
+ {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7bdcec93f152e0e1942102537eed7b166d6661ae57835b20a52a2a3d6a3e1bf3"},
+ {file = "zope.interface-5.5.0-cp310-cp310-win32.whl", hash = "sha256:03f5ae315db0d0de668125d983e2a819a554f3fdb2d53b7e934e3eb3c3c7375d"},
+ {file = "zope.interface-5.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b9f153208d74ccfa25449a0c6cb756ab792ce0dc99d9d771d935f039b38740c"},
+ {file = "zope.interface-5.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeac590cce44e68ee8ad0b8ecf4d7bf15801f102d564ca1b0eb1f12f584ee656"},
+ {file = "zope.interface-5.5.0-cp35-cp35m-win32.whl", hash = "sha256:7d9ec1e6694af39b687045712a8ad14ddcb568670d5eb1b66b48b98b9312afba"},
+ {file = "zope.interface-5.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d18fb0f6c8169d26044128a2e7d3c39377a8a151c564e87b875d379dbafd3930"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0eb2b3e84f48dd9cfc8621c80fba905d7e228615c67f76c7df7c716065669bb6"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6593e150d13cfcce69b0aec5df7bc248cb91e4258a7374c129bb6d56b4e5ca"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9dc4493aa3d87591e3d2bf1453e25b98038c839ca8e499df3d7106631b66fe83"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c6023ae7defd052cf76986ce77922177b0c2f3913bea31b5b28fbdf6cb7099e"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-win32.whl", hash = "sha256:a69c28d85bb7cf557751a5214cb3f657b2b035c8c96d71080c1253b75b79b69b"},
+ {file = "zope.interface-5.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:85dd6dd9aaae7a176948d8bb62e20e2968588fd787c29c5d0d964ab475168d3d"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:970661ece2029915b8f7f70892e88404340fbdefd64728380cad41c8dce14ff4"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e3495bb0cdcea212154e558082c256f11b18031f05193ae2fb85d048848db14"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3f68404edb1a4fb6aa8a94675521ca26c83ebbdbb90e894f749ae0dc4ca98418"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:740f3c1b44380658777669bcc42f650f5348e53797f2cee0d93dc9b0f9d7cc69"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-win32.whl", hash = "sha256:006f8dd81fae28027fc28ada214855166712bf4f0bfbc5a8788f9b70982b9437"},
+ {file = "zope.interface-5.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:43490ad65d4c64e45a30e51a2beb7a6b63e1ff395302ad22392224eb618476d6"},
+ {file = "zope.interface-5.5.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f70726b60009433111fe9928f5d89cbb18962411d33c45fb19eb81b9bbd26fcd"},
+ {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa614d049667bed1c737435c609c0956c5dc0dbafdc1145ee7935e4658582cb"},
+ {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:58a975f89e4584d0223ab813c5ba4787064c68feef4b30d600f5e01de90ae9ce"},
+ {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37ec9ade9902f412cc7e7a32d71f79dec3035bad9bd0170226252eed88763c48"},
+ {file = "zope.interface-5.5.0-cp38-cp38-win32.whl", hash = "sha256:be11fce0e6af6c0e8d93c10ef17b25aa7c4acb7ec644bff2596c0d639c49e20f"},
+ {file = "zope.interface-5.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:cbbf83914b9a883ab324f728de869f4e406e0cbcd92df7e0a88decf6f9ab7d5a"},
+ {file = "zope.interface-5.5.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:26c1456520fdcafecc5765bec4783eeafd2e893eabc636908f50ee31fe5c738c"},
+ {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47ff078734a1030c48103422a99e71a7662d20258c00306546441adf689416f7"},
+ {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:687cab7f9ae18d2c146f315d0ca81e5ffe89a139b88277afa70d52f632515854"},
+ {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d80f6236b57a95eb19d5e47eb68d0296119e1eff6deaa2971ab8abe3af918420"},
+ {file = "zope.interface-5.5.0-cp39-cp39-win32.whl", hash = "sha256:9cdc4e898d3b1547d018829fd4a9f403e52e51bba24be0fbfa37f3174e1ef797"},
+ {file = "zope.interface-5.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:6566b3d2657e7609cd8751bcb1eab1202b1692a7af223035a5887d64bb3a2f3b"},
+ {file = "zope.interface-5.5.0.tar.gz", hash = "sha256:700ebf9662cf8df70e2f0cb4988e078c53f65ee3eefd5c9d80cf988c4175c8e3"},
+]
diff --git a/pyextra/acados_template/acados_layout.json b/pyextra/acados_template/acados_layout.json
index 16fd452337..c9f0b90c73 100644
--- a/pyextra/acados_template/acados_layout.json
+++ b/pyextra/acados_template/acados_layout.json
@@ -5,6 +5,9 @@
"acados_include_path": [
"str"
],
+ "cython_include_dirs": [
+ "str"
+ ],
"model": {
"name" : [
"str"
@@ -23,7 +26,15 @@
],
"dyn_disc_fun" : [
"str"
- ]
+ ],
+ "gnsf" : {
+ "nontrivial_f_LO": [
+ "int"
+ ],
+ "purely_linear": [
+ "int"
+ ]
+ }
},
"parameter_values": [
"ndarray",
@@ -657,6 +668,9 @@
"hessian_approx": [
"str"
],
+ "hpipm_mode": [
+ "str"
+ ],
"regularize_method": [
"str"
],
@@ -693,6 +707,18 @@
"alpha_reduction": [
"float"
],
+ "line_search_use_sufficient_descent": [
+ "int"
+ ],
+ "globalization_use_SOC": [
+ "int"
+ ],
+ "full_step_dual": [
+ "int"
+ ],
+ "eps_sufficient_descent": [
+ "float"
+ ],
"sim_method_num_stages": [
"ndarray",
[
diff --git a/pyextra/acados_template/acados_model.py b/pyextra/acados_template/acados_model.py
index 4871a2c0d5..e292cc7477 100644
--- a/pyextra/acados_template/acados_model.py
+++ b/pyextra/acados_template/acados_model.py
@@ -78,6 +78,13 @@ class AcadosModel():
self.dyn_disc_fun_jac = None #: name of function discrete dyanamics + jacobian; Default: :code:`None`
self.dyn_disc_fun = None #: name of function discrete dyanamics; Default: :code:`None`
+ # for GNSF models
+ self.gnsf = {'nontrivial_f_LO': 1, 'purely_linear': 0}
+ """
+ dictionary containing information on GNSF structure needed when rendering templates.
+ Contains integers `nontrivial_f_LO`, `purely_linear`.
+ """
+
## for OCP
# constraints
self.con_h_expr = None #: CasADi expression for the constraint :math:`h`; Default: :code:`None`
diff --git a/pyextra/acados_template/acados_ocp.py b/pyextra/acados_template/acados_ocp.py
index 198ab033dc..80970239eb 100644
--- a/pyextra/acados_template/acados_ocp.py
+++ b/pyextra/acados_template/acados_ocp.py
@@ -270,28 +270,28 @@ class AcadosOcpDims:
@nx.setter
def nx(self, nx):
- if type(nx) == int and nx > 0:
+ if isinstance(nx, int) and nx > 0:
self.__nx = nx
else:
raise Exception('Invalid nx value, expected positive integer. Exiting.')
@nz.setter
def nz(self, nz):
- if type(nz) == int and nz > -1:
+ if isinstance(nz, int) and nz > -1:
self.__nz = nz
else:
raise Exception('Invalid nz value, expected nonnegative integer. Exiting.')
@nu.setter
def nu(self, nu):
- if type(nu) == int and nu > -1:
+ if isinstance(nu, int) and nu > -1:
self.__nu = nu
else:
raise Exception('Invalid nu value, expected nonnegative integer. Exiting.')
@np.setter
def np(self, np):
- if type(np) == int and np > -1:
+ if isinstance(np, int) and np > -1:
self.__np = np
else:
raise Exception('Invalid np value, expected nonnegative integer. Exiting.')
@@ -312,49 +312,49 @@ class AcadosOcpDims:
@ny_e.setter
def ny_e(self, ny_e):
- if type(ny_e) == int and ny_e > -1:
+ if isinstance(ny_e, int) and ny_e > -1:
self.__ny_e = ny_e
else:
raise Exception('Invalid ny_e value, expected nonnegative integer. Exiting.')
@nr.setter
def nr(self, nr):
- if type(nr) == int and nr > -1:
+ if isinstance(nr, int) and nr > -1:
self.__nr = nr
else:
raise Exception('Invalid nr value, expected nonnegative integer. Exiting.')
@nr_e.setter
def nr_e(self, nr_e):
- if type(nr_e) == int and nr_e > -1:
+ if isinstance(nr_e, int) and nr_e > -1:
self.__nr_e = nr_e
else:
raise Exception('Invalid nr_e value, expected nonnegative integer. Exiting.')
@nh.setter
def nh(self, nh):
- if type(nh) == int and nh > -1:
+ if isinstance(nh, int) and nh > -1:
self.__nh = nh
else:
raise Exception('Invalid nh value, expected nonnegative integer. Exiting.')
@nh_e.setter
def nh_e(self, nh_e):
- if type(nh_e) == int and nh_e > -1:
+ if isinstance(nh_e, int) and nh_e > -1:
self.__nh_e = nh_e
else:
raise Exception('Invalid nh_e value, expected nonnegative integer. Exiting.')
@nphi.setter
def nphi(self, nphi):
- if type(nphi) == int and nphi > -1:
+ if isinstance(nphi, int) and nphi > -1:
self.__nphi = nphi
else:
raise Exception('Invalid nphi value, expected nonnegative integer. Exiting.')
@nphi_e.setter
def nphi_e(self, nphi_e):
- if type(nphi_e) == int and nphi_e > -1:
+ if isinstance(nphi_e, int) and nphi_e > -1:
self.__nphi_e = nphi_e
else:
raise Exception('Invalid nphi_e value, expected nonnegative integer. Exiting.')
@@ -375,42 +375,42 @@ class AcadosOcpDims:
@nbx_0.setter
def nbx_0(self, nbx_0):
- if type(nbx_0) == int and nbx_0 > -1:
+ if isinstance(nbx_0, int) and nbx_0 > -1:
self.__nbx_0 = nbx_0
else:
raise Exception('Invalid nbx_0 value, expected nonnegative integer. Exiting.')
@nbx_e.setter
def nbx_e(self, nbx_e):
- if type(nbx_e) == int and nbx_e > -1:
+ if isinstance(nbx_e, int) and nbx_e > -1:
self.__nbx_e = nbx_e
else:
raise Exception('Invalid nbx_e value, expected nonnegative integer. Exiting.')
@nbu.setter
def nbu(self, nbu):
- if type(nbu) == int and nbu > -1:
+ if isinstance(nbu, int) and nbu > -1:
self.__nbu = nbu
else:
raise Exception('Invalid nbu value, expected nonnegative integer. Exiting.')
@nsbx.setter
def nsbx(self, nsbx):
- if type(nsbx) == int and nsbx > -1:
+ if isinstance(nsbx, int) and nsbx > -1:
self.__nsbx = nsbx
else:
raise Exception('Invalid nsbx value, expected nonnegative integer. Exiting.')
@nsbx_e.setter
def nsbx_e(self, nsbx_e):
- if type(nsbx_e) == int and nsbx_e > -1:
+ if isinstance(nsbx_e, int) and nsbx_e > -1:
self.__nsbx_e = nsbx_e
else:
raise Exception('Invalid nsbx_e value, expected nonnegative integer. Exiting.')
@nsbu.setter
def nsbu(self, nsbu):
- if type(nsbu) == int and nsbu > -1:
+ if isinstance(nsbu, int) and nsbu > -1:
self.__nsbu = nsbu
else:
raise Exception('Invalid nsbu value, expected nonnegative integer. Exiting.')
@@ -1592,14 +1592,14 @@ class AcadosOcpConstraints:
# initial x
@lbx_0.setter
def lbx_0(self, lbx_0):
- if type(lbx_0) == np.ndarray:
+ if isinstance(lbx_0, np.ndarray):
self.__lbx_0 = lbx_0
else:
raise Exception('Invalid lbx_0 value. Exiting.')
@ubx_0.setter
def ubx_0(self, ubx_0):
- if type(ubx_0) == np.ndarray:
+ if isinstance(ubx_0, np.ndarray):
self.__ubx_0 = ubx_0
else:
raise Exception('Invalid ubx_0 value. Exiting.')
@@ -1613,7 +1613,7 @@ class AcadosOcpConstraints:
@Jbx_0.setter
def Jbx_0(self, Jbx_0):
- if type(Jbx_0) == np.ndarray:
+ if isinstance(Jbx_0, np.ndarray):
self.__idxbx_0 = J_to_idx(Jbx_0)
else:
raise Exception('Invalid Jbx_0 value. Exiting.')
@@ -1639,28 +1639,28 @@ class AcadosOcpConstraints:
# bounds on x
@lbx.setter
def lbx(self, lbx):
- if type(lbx) == np.ndarray:
+ if isinstance(lbx, np.ndarray):
self.__lbx = lbx
else:
raise Exception('Invalid lbx value. Exiting.')
@ubx.setter
def ubx(self, ubx):
- if type(ubx) == np.ndarray:
+ if isinstance(ubx, np.ndarray):
self.__ubx = ubx
else:
raise Exception('Invalid ubx value. Exiting.')
@idxbx.setter
def idxbx(self, idxbx):
- if type(idxbx) == np.ndarray:
+ if isinstance(idxbx, np.ndarray):
self.__idxbx = idxbx
else:
raise Exception('Invalid idxbx value. Exiting.')
@Jbx.setter
def Jbx(self, Jbx):
- if type(Jbx) == np.ndarray:
+ if isinstance(Jbx, np.ndarray):
self.__idxbx = J_to_idx(Jbx)
else:
raise Exception('Invalid Jbx value. Exiting.')
@@ -1668,28 +1668,28 @@ class AcadosOcpConstraints:
# bounds on u
@lbu.setter
def lbu(self, lbu):
- if type(lbu) == np.ndarray:
+ if isinstance(lbu, np.ndarray):
self.__lbu = lbu
else:
raise Exception('Invalid lbu value. Exiting.')
@ubu.setter
def ubu(self, ubu):
- if type(ubu) == np.ndarray:
+ if isinstance(ubu, np.ndarray):
self.__ubu = ubu
else:
raise Exception('Invalid ubu value. Exiting.')
@idxbu.setter
def idxbu(self, idxbu):
- if type(idxbu) == np.ndarray:
+ if isinstance(idxbu, np.ndarray):
self.__idxbu = idxbu
else:
raise Exception('Invalid idxbu value. Exiting.')
@Jbu.setter
def Jbu(self, Jbu):
- if type(Jbu) == np.ndarray:
+ if isinstance(Jbu, np.ndarray):
self.__idxbu = J_to_idx(Jbu)
else:
raise Exception('Invalid Jbu value. Exiting.')
@@ -1697,28 +1697,28 @@ class AcadosOcpConstraints:
# bounds on x at shooting node N
@lbx_e.setter
def lbx_e(self, lbx_e):
- if type(lbx_e) == np.ndarray:
+ if isinstance(lbx_e, np.ndarray):
self.__lbx_e = lbx_e
else:
raise Exception('Invalid lbx_e value. Exiting.')
@ubx_e.setter
def ubx_e(self, ubx_e):
- if type(ubx_e) == np.ndarray:
+ if isinstance(ubx_e, np.ndarray):
self.__ubx_e = ubx_e
else:
raise Exception('Invalid ubx_e value. Exiting.')
@idxbx_e.setter
def idxbx_e(self, idxbx_e):
- if type(idxbx_e) == np.ndarray:
+ if isinstance(idxbx_e, np.ndarray):
self.__idxbx_e = idxbx_e
else:
raise Exception('Invalid idxbx_e value. Exiting.')
@Jbx_e.setter
def Jbx_e(self, Jbx_e):
- if type(Jbx_e) == np.ndarray:
+ if isinstance(Jbx_e, np.ndarray):
self.__idxbx_e = J_to_idx(Jbx_e)
else:
raise Exception('Invalid Jbx_e value. Exiting.')
@@ -1742,14 +1742,14 @@ class AcadosOcpConstraints:
@lg.setter
def lg(self, lg):
- if type(lg) == np.ndarray:
+ if isinstance(lg, np.ndarray):
self.__lg = lg
else:
raise Exception('Invalid lg value. Exiting.')
@ug.setter
def ug(self, ug):
- if type(ug) == np.ndarray:
+ if isinstance(ug, np.ndarray):
self.__ug = ug
else:
raise Exception('Invalid ug value. Exiting.')
@@ -1765,14 +1765,14 @@ class AcadosOcpConstraints:
@lg_e.setter
def lg_e(self, lg_e):
- if type(lg_e) == np.ndarray:
+ if isinstance(lg_e, np.ndarray):
self.__lg_e = lg_e
else:
raise Exception('Invalid lg_e value. Exiting.')
@ug_e.setter
def ug_e(self, ug_e):
- if type(ug_e) == np.ndarray:
+ if isinstance(ug_e, np.ndarray):
self.__ug_e = ug_e
else:
raise Exception('Invalid ug_e value. Exiting.')
@@ -1780,14 +1780,14 @@ class AcadosOcpConstraints:
# nonlinear constraints
@lh.setter
def lh(self, lh):
- if type(lh) == np.ndarray:
+ if isinstance(lh, np.ndarray):
self.__lh = lh
else:
raise Exception('Invalid lh value. Exiting.')
@uh.setter
def uh(self, uh):
- if type(uh) == np.ndarray:
+ if isinstance(uh, np.ndarray):
self.__uh = uh
else:
raise Exception('Invalid uh value. Exiting.')
@@ -1795,14 +1795,14 @@ class AcadosOcpConstraints:
# convex-over-nonlinear constraints
@lphi.setter
def lphi(self, lphi):
- if type(lphi) == np.ndarray:
+ if isinstance(lphi, np.ndarray):
self.__lphi = lphi
else:
raise Exception('Invalid lphi value. Exiting.')
@uphi.setter
def uphi(self, uphi):
- if type(uphi) == np.ndarray:
+ if isinstance(uphi, np.ndarray):
self.__uphi = uphi
else:
raise Exception('Invalid uphi value. Exiting.')
@@ -1810,14 +1810,14 @@ class AcadosOcpConstraints:
# nonlinear constraints at shooting node N
@lh_e.setter
def lh_e(self, lh_e):
- if type(lh_e) == np.ndarray:
+ if isinstance(lh_e, np.ndarray):
self.__lh_e = lh_e
else:
raise Exception('Invalid lh_e value. Exiting.')
@uh_e.setter
def uh_e(self, uh_e):
- if type(uh_e) == np.ndarray:
+ if isinstance(uh_e, np.ndarray):
self.__uh_e = uh_e
else:
raise Exception('Invalid uh_e value. Exiting.')
@@ -1825,14 +1825,14 @@ class AcadosOcpConstraints:
# convex-over-nonlinear constraints at shooting node N
@lphi_e.setter
def lphi_e(self, lphi_e):
- if type(lphi_e) == np.ndarray:
+ if isinstance(lphi_e, np.ndarray):
self.__lphi_e = lphi_e
else:
raise Exception('Invalid lphi_e value. Exiting.')
@uphi_e.setter
def uphi_e(self, uphi_e):
- if type(uphi_e) == np.ndarray:
+ if isinstance(uphi_e, np.ndarray):
self.__uphi_e = uphi_e
else:
raise Exception('Invalid uphi_e value. Exiting.')
@@ -1841,21 +1841,21 @@ class AcadosOcpConstraints:
# soft bounds on x
@lsbx.setter
def lsbx(self, lsbx):
- if type(lsbx) == np.ndarray:
+ if isinstance(lsbx, np.ndarray):
self.__lsbx = lsbx
else:
raise Exception('Invalid lsbx value. Exiting.')
@usbx.setter
def usbx(self, usbx):
- if type(usbx) == np.ndarray:
+ if isinstance(usbx, np.ndarray):
self.__usbx = usbx
else:
raise Exception('Invalid usbx value. Exiting.')
@idxsbx.setter
def idxsbx(self, idxsbx):
- if type(idxsbx) == np.ndarray:
+ if isinstance(idxsbx, np.ndarray):
self.__idxsbx = idxsbx
else:
raise Exception('Invalid idxsbx value. Exiting.')
@@ -1870,28 +1870,28 @@ class AcadosOcpConstraints:
# soft bounds on u
@lsbu.setter
def lsbu(self, lsbu):
- if type(lsbu) == np.ndarray:
+ if isinstance(lsbu, np.ndarray):
self.__lsbu = lsbu
else:
raise Exception('Invalid lsbu value. Exiting.')
@usbu.setter
def usbu(self, usbu):
- if type(usbu) == np.ndarray:
+ if isinstance(usbu, np.ndarray):
self.__usbu = usbu
else:
raise Exception('Invalid usbu value. Exiting.')
@idxsbu.setter
def idxsbu(self, idxsbu):
- if type(idxsbu) == np.ndarray:
+ if isinstance(idxsbu, np.ndarray):
self.__idxsbu = idxsbu
else:
raise Exception('Invalid idxsbu value. Exiting.')
@Jsbu.setter
def Jsbu(self, Jsbu):
- if type(Jsbu) == np.ndarray:
+ if isinstance(Jsbu, np.ndarray):
self.__idxsbu = J_to_idx_slack(Jsbu)
else:
raise Exception('Invalid Jsbu value. Exiting.')
@@ -1899,28 +1899,28 @@ class AcadosOcpConstraints:
# soft bounds on x at shooting node N
@lsbx_e.setter
def lsbx_e(self, lsbx_e):
- if type(lsbx_e) == np.ndarray:
+ if isinstance(lsbx_e, np.ndarray):
self.__lsbx_e = lsbx_e
else:
raise Exception('Invalid lsbx_e value. Exiting.')
@usbx_e.setter
def usbx_e(self, usbx_e):
- if type(usbx_e) == np.ndarray:
+ if isinstance(usbx_e, np.ndarray):
self.__usbx_e = usbx_e
else:
raise Exception('Invalid usbx_e value. Exiting.')
@idxsbx_e.setter
def idxsbx_e(self, idxsbx_e):
- if type(idxsbx_e) == np.ndarray:
+ if isinstance(idxsbx_e, np.ndarray):
self.__idxsbx_e = idxsbx_e
else:
raise Exception('Invalid idxsbx_e value. Exiting.')
@Jsbx_e.setter
def Jsbx_e(self, Jsbx_e):
- if type(Jsbx_e) == np.ndarray:
+ if isinstance(Jsbx_e, np.ndarray):
self.__idxsbx_e = J_to_idx_slack(Jsbx_e)
else:
raise Exception('Invalid Jsbx_e value. Exiting.')
@@ -1959,21 +1959,21 @@ class AcadosOcpConstraints:
# soft bounds on nonlinear constraints
@lsh.setter
def lsh(self, lsh):
- if type(lsh) == np.ndarray:
+ if isinstance(lsh, np.ndarray):
self.__lsh = lsh
else:
raise Exception('Invalid lsh value. Exiting.')
@ush.setter
def ush(self, ush):
- if type(ush) == np.ndarray:
+ if isinstance(ush, np.ndarray):
self.__ush = ush
else:
raise Exception('Invalid ush value. Exiting.')
@idxsh.setter
def idxsh(self, idxsh):
- if type(idxsh) == np.ndarray:
+ if isinstance(idxsh, np.ndarray):
self.__idxsh = idxsh
else:
raise Exception('Invalid idxsh value. Exiting.')
@@ -1989,21 +1989,21 @@ class AcadosOcpConstraints:
# soft bounds on convex-over-nonlinear constraints
@lsphi.setter
def lsphi(self, lsphi):
- if type(lsphi) == np.ndarray:
+ if isinstance(lsphi, np.ndarray):
self.__lsphi = lsphi
else:
raise Exception('Invalid lsphi value. Exiting.')
@usphi.setter
def usphi(self, usphi):
- if type(usphi) == np.ndarray:
+ if isinstance(usphi, np.ndarray):
self.__usphi = usphi
else:
raise Exception('Invalid usphi value. Exiting.')
@idxsphi.setter
def idxsphi(self, idxsphi):
- if type(idxsphi) == np.ndarray:
+ if isinstance(idxsphi, np.ndarray):
self.__idxsphi = idxsphi
else:
raise Exception('Invalid idxsphi value. Exiting.')
@@ -2151,6 +2151,11 @@ class AcadosOcpOptions:
self.__ext_cost_num_hess = 0
self.__alpha_min = 0.05
self.__alpha_reduction = 0.7
+ self.__line_search_use_sufficient_descent = 0
+ self.__globalization_use_SOC = 0
+ self.__full_step_dual = 0
+ self.__eps_sufficient_descent = 1e-4
+ self.__hpipm_mode = 'BALANCE'
@property
@@ -2161,6 +2166,21 @@ class AcadosOcpOptions:
"""
return self.__qp_solver
+ @property
+ def hpipm_mode(self):
+ """
+ Mode of HPIPM to be used,
+
+ String in ('BALANCE', 'SPEED_ABS', 'SPEED', 'ROBUST').
+
+ Default: 'BALANCE'.
+
+ see https://cdn.syscop.de/publications/Frison2020a.pdf
+ and the HPIPM code:
+ https://github.com/giaf/hpipm/blob/master/ocp_qp/x_ocp_qp_ipm.c#L69
+ """
+ return self.__hpipm_mode
+
@property
def hessian_approx(self):
"""Hessian approximation.
@@ -2367,6 +2387,43 @@ class AcadosOcpOptions:
"""Step size reduction factor for globalization MERIT_BACKTRACKING, default: 0.7."""
return self.__alpha_reduction
+ @property
+ def line_search_use_sufficient_descent(self):
+ """
+ Determines if sufficient descent (Armijo) condition is used in line search.
+ Type: int; 0 or 1;
+ default: 0.
+ """
+ return self.__line_search_use_sufficient_descent
+
+ @property
+ def eps_sufficient_descent(self):
+ """
+ Factor for sufficient descent (Armijo) conditon, see line_search_use_sufficient_descent.
+ Type: float,
+ default: 1e-4.
+ """
+ return self.__eps_sufficient_descent
+
+ @property
+ def globalization_use_SOC(self):
+ """
+ Determines if second order correction (SOC) is done when using MERIT_BACKTRACKING.
+ SOC is done if preliminary line search does not return full step.
+ Type: int; 0 or 1;
+ default: 0.
+ """
+ return self.__globalization_use_SOC
+
+ @property
+ def full_step_dual(self):
+ """
+ Determines if dual variables are updated with full steps (alpha=1.0) when primal variables are updated with smaller step.
+ Type: int; 0 or 1;
+ default: 0.
+ """
+ return self.__full_step_dual
+
@property
def nlp_solver_tol_ineq(self):
"""NLP solver inequality tolerance"""
@@ -2500,6 +2557,15 @@ class AcadosOcpOptions:
raise Exception('Invalid collocation_type value. Possible values are:\n\n' \
+ ',\n'.join(collocation_types) + '.\n\nYou have: ' + collocation_type + '.\n\nExiting.')
+ @hpipm_mode.setter
+ def hpipm_mode(self, hpipm_mode):
+ hpipm_modes = ('BALANCE', 'SPEED_ABS', 'SPEED', 'ROBUST')
+ if hpipm_mode in hpipm_modes:
+ self.__hpipm_mode = hpipm_mode
+ else:
+ raise Exception('Invalid hpipm_mode value. Possible values are:\n\n' \
+ + ',\n'.join(hpipm_modes) + '.\n\nYou have: ' + hpipm_mode + '.\n\nExiting.')
+
@hessian_approx.setter
def hessian_approx(self, hessian_approx):
hessian_approxs = ('GAUSS_NEWTON', 'EXACT')
@@ -2524,12 +2590,23 @@ class AcadosOcpOptions:
@time_steps.setter
def time_steps(self, time_steps):
- self.__time_steps = time_steps
+ if isinstance(time_steps, np.ndarray):
+ if len(time_steps.shape) == 1:
+ self.__time_steps = time_steps
+ else:
+ raise Exception('Invalid time_steps, expected np.ndarray of shape (N,).')
+ else:
+ raise Exception('Invalid time_steps, expected np.ndarray.')
@shooting_nodes.setter
def shooting_nodes(self, shooting_nodes):
- self.__shooting_nodes = shooting_nodes
-
+ if isinstance(shooting_nodes, np.ndarray):
+ if len(shooting_nodes.shape) == 1:
+ self.__shooting_nodes = shooting_nodes
+ else:
+ raise Exception('Invalid shooting_nodes, expected np.ndarray of shape (N+1,).')
+ else:
+ raise Exception('Invalid shooting_nodes, expected np.ndarray.')
@Tsim.setter
def Tsim(self, Tsim):
@@ -2537,7 +2614,12 @@ class AcadosOcpOptions:
@globalization.setter
def globalization(self, globalization):
- self.__globalization = globalization
+ globalization_types = ('MERIT_BACKTRACKING', 'FIXED_STEP')
+ if globalization in globalization_types:
+ self.__globalization = globalization
+ else:
+ raise Exception('Invalid globalization value. Possible values are:\n\n' \
+ + ',\n'.join(globalization_types) + '.\n\nYou have: ' + globalization + '.\n\nExiting.')
@alpha_min.setter
def alpha_min(self, alpha_min):
@@ -2547,10 +2629,38 @@ class AcadosOcpOptions:
def alpha_reduction(self, alpha_reduction):
self.__alpha_reduction = alpha_reduction
+ @line_search_use_sufficient_descent.setter
+ def line_search_use_sufficient_descent(self, line_search_use_sufficient_descent):
+ if line_search_use_sufficient_descent in [0, 1]:
+ self.__line_search_use_sufficient_descent = line_search_use_sufficient_descent
+ else:
+ raise Exception(f'Invalid value for line_search_use_sufficient_descent. Possible values are 0, 1, got {line_search_use_sufficient_descent}')
+
+ @globalization_use_SOC.setter
+ def globalization_use_SOC(self, globalization_use_SOC):
+ if globalization_use_SOC in [0, 1]:
+ self.__globalization_use_SOC = globalization_use_SOC
+ else:
+ raise Exception(f'Invalid value for globalization_use_SOC. Possible values are 0, 1, got {globalization_use_SOC}')
+
+ @full_step_dual.setter
+ def full_step_dual(self, full_step_dual):
+ if full_step_dual in [0, 1]:
+ self.__full_step_dual = full_step_dual
+ else:
+ raise Exception(f'Invalid value for full_step_dual. Possible values are 0, 1, got {full_step_dual}')
+
+ @eps_sufficient_descent.setter
+ def eps_sufficient_descent(self, eps_sufficient_descent):
+ if isinstance(eps_sufficient_descent, float) and eps_sufficient_descent > 0:
+ self.__eps_sufficient_descent = eps_sufficient_descent
+ else:
+ raise Exception('Invalid eps_sufficient_descent value. eps_sufficient_descent must be a positive float. Exiting')
+
@sim_method_num_stages.setter
def sim_method_num_stages(self, sim_method_num_stages):
- # if type(sim_method_num_stages) == int:
+ # if isinstance(sim_method_num_stages, int):
# self.__sim_method_num_stages = sim_method_num_stages
# else:
# raise Exception('Invalid sim_method_num_stages value. sim_method_num_stages must be an integer. Exiting.')
@@ -2560,7 +2670,7 @@ class AcadosOcpOptions:
@sim_method_num_steps.setter
def sim_method_num_steps(self, sim_method_num_steps):
- # if type(sim_method_num_steps) == int:
+ # if isinstance(sim_method_num_steps, int):
# self.__sim_method_num_steps = sim_method_num_steps
# else:
# raise Exception('Invalid sim_method_num_steps value. sim_method_num_steps must be an integer. Exiting.')
@@ -2570,7 +2680,7 @@ class AcadosOcpOptions:
@sim_method_newton_iter.setter
def sim_method_newton_iter(self, sim_method_newton_iter):
- if type(sim_method_newton_iter) == int:
+ if isinstance(sim_method_newton_iter, int):
self.__sim_method_newton_iter = sim_method_newton_iter
else:
raise Exception('Invalid sim_method_newton_iter value. sim_method_newton_iter must be an integer. Exiting.')
@@ -2593,7 +2703,7 @@ class AcadosOcpOptions:
@nlp_solver_step_length.setter
def nlp_solver_step_length(self, nlp_solver_step_length):
- if type(nlp_solver_step_length) == float and nlp_solver_step_length > 0:
+ if isinstance(nlp_solver_step_length, float) and nlp_solver_step_length > 0:
self.__nlp_solver_step_length = nlp_solver_step_length
else:
raise Exception('Invalid nlp_solver_step_length value. nlp_solver_step_length must be a positive float. Exiting')
@@ -2614,7 +2724,7 @@ class AcadosOcpOptions:
@qp_solver_cond_N.setter
def qp_solver_cond_N(self, qp_solver_cond_N):
- if isinstance(qp_solver_cond_N, int) and qp_solver_cond_N > 0:
+ if isinstance(qp_solver_cond_N, int) and qp_solver_cond_N >= 0:
self.__qp_solver_cond_N = qp_solver_cond_N
else:
raise Exception('Invalid qp_solver_cond_N value. qp_solver_cond_N must be a positive int. Exiting')
@@ -2705,21 +2815,21 @@ class AcadosOcpOptions:
@nlp_solver_max_iter.setter
def nlp_solver_max_iter(self, nlp_solver_max_iter):
- if type(nlp_solver_max_iter) == int and nlp_solver_max_iter > 0:
+ if isinstance(nlp_solver_max_iter, int) and nlp_solver_max_iter > 0:
self.__nlp_solver_max_iter = nlp_solver_max_iter
else:
raise Exception('Invalid nlp_solver_max_iter value. nlp_solver_max_iter must be a positive int. Exiting')
@print_level.setter
def print_level(self, print_level):
- if type(print_level) == int and print_level >= 0:
+ if isinstance(print_level, int) and print_level >= 0:
self.__print_level = print_level
else:
raise Exception('Invalid print_level value. print_level takes one of the values >=0. Exiting')
@model_external_shared_lib_dir.setter
def model_external_shared_lib_dir(self, model_external_shared_lib_dir):
- if type(model_external_shared_lib_dir) == str :
+ if isinstance(model_external_shared_lib_dir, str) :
self.__model_external_shared_lib_dir = model_external_shared_lib_dir
else:
raise Exception('Invalid model_external_shared_lib_dir value. Str expected.' \
@@ -2727,7 +2837,7 @@ class AcadosOcpOptions:
@model_external_shared_lib_name.setter
def model_external_shared_lib_name(self, model_external_shared_lib_name):
- if type(model_external_shared_lib_name) == str :
+ if isinstance(model_external_shared_lib_name, str) :
if model_external_shared_lib_name[-3:] == '.so' :
raise Exception('Invalid model_external_shared_lib_name value. Remove the .so extension.' \
+ '.\n\nYou have: ' + type(model_external_shared_lib_name) + '.\n\nExiting.')
@@ -2805,10 +2915,13 @@ class AcadosOcp:
self.solver_options = AcadosOcpOptions()
"""Solver Options, type :py:class:`acados_template.acados_ocp.AcadosOcpOptions`"""
- self.acados_include_path = f'{acados_path}/include'
- """Path to acados include directory, type: string"""
- self.acados_lib_path = f'{acados_path}/lib'
- """Path to where acados library is located, type: string"""
+ self.acados_include_path = os.path.join(acados_path, 'include').replace(os.sep, '/') # the replace part is important on Windows for CMake
+ """Path to acados include directory (set automatically), type: `string`"""
+ self.acados_lib_path = os.path.join(acados_path, 'lib').replace(os.sep, '/') # the replace part is important on Windows for CMake
+ """Path to where acados library is located, type: `string`"""
+
+ import numpy
+ self.cython_include_dirs = numpy.get_include()
self.__parameter_values = np.array([])
self.__problem_class = 'OCP'
diff --git a/pyextra/acados_template/acados_ocp_solver.py b/pyextra/acados_template/acados_ocp_solver.py
index b34b8fa84a..beeda2cb0c 100644
--- a/pyextra/acados_template/acados_ocp_solver.py
+++ b/pyextra/acados_template/acados_ocp_solver.py
@@ -37,7 +37,7 @@ import os
import json
import numpy as np
from datetime import datetime
-import ctypes
+import importlib
from ctypes import POINTER, cast, CDLL, c_void_p, c_char_p, c_double, c_int, c_int64, byref
from copy import deepcopy
@@ -51,9 +51,10 @@ from .generate_c_code_nls_cost import generate_c_code_nls_cost
from .generate_c_code_external_cost import generate_c_code_external_cost
from .acados_ocp import AcadosOcp
from .acados_model import acados_model_strip_casadi_symbolics
-from .utils import is_column, is_empty, casadi_length, render_template, acados_class2dict,\
+from .utils import is_column, is_empty, casadi_length, render_template,\
format_class_dict, ocp_check_against_layout, np_array_to_list, make_model_consistent,\
- set_up_imported_gnsf_model, get_acados_path, get_ocp_nlp_layout, get_python_interface_path
+ set_up_imported_gnsf_model, get_ocp_nlp_layout, get_python_interface_path
+from .builders import CMakeBuilder
def make_ocp_dims_consistent(acados_ocp):
@@ -90,7 +91,7 @@ def make_ocp_dims_consistent(acados_ocp):
raise Exception('inconsistent dimension np, regarding model.p and parameter_values.' + \
f'\nGot np = {dims.np}, acados_ocp.parameter_values.shape = {acados_ocp.parameter_values.shape[0]}\n')
- # cost
+ ## cost
# initial stage - if not set, copy fields from path constraints
if cost.cost_type_0 is None:
cost.cost_type_0 = cost.cost_type
@@ -132,6 +133,15 @@ def make_ocp_dims_consistent(acados_ocp):
f'\nGot W_0[{cost.W.shape}], yref_0[{cost.yref_0.shape}]\n')
dims.ny_0 = ny_0
+ elif cost.cost_type_0 == 'EXTERNAL':
+ if opts.hessian_approx == 'GAUSS_NEWTON' and opts.ext_cost_num_hess == 0 and model.cost_expr_ext_cost_custom_hess_0 is None:
+ print("\nWARNING: Gauss-Newton Hessian approximation with EXTERNAL cost type not possible!\n"
+ "got cost_type_0: EXTERNAL, hessian_approx: 'GAUSS_NEWTON.'\n"
+ "GAUSS_NEWTON hessian is only supported for cost_types [NON]LINEAR_LS.\n"
+ "If you continue, acados will proceed computing the exact hessian for the cost term.\n"
+ "Note: There is also the option to use the external cost module with a numerical hessian approximation (see `ext_cost_num_hess`).\n"
+ "OR the option to provide a symbolic custom hessian approximation (see `cost_expr_ext_cost_custom_hess`).\n")
+
# path
if cost.cost_type == 'LINEAR_LS':
ny = cost.W.shape[0]
@@ -161,6 +171,15 @@ def make_ocp_dims_consistent(acados_ocp):
f'\nGot W[{cost.W.shape}], yref[{cost.yref.shape}]\n')
dims.ny = ny
+ elif cost.cost_type == 'EXTERNAL':
+ if opts.hessian_approx == 'GAUSS_NEWTON' and opts.ext_cost_num_hess == 0 and model.cost_expr_ext_cost_custom_hess is None:
+ print("\nWARNING: Gauss-Newton Hessian approximation with EXTERNAL cost type not possible!\n"
+ "got cost_type: EXTERNAL, hessian_approx: 'GAUSS_NEWTON.'\n"
+ "GAUSS_NEWTON hessian is only supported for cost_types [NON]LINEAR_LS.\n"
+ "If you continue, acados will proceed computing the exact hessian for the cost term.\n"
+ "Note: There is also the option to use the external cost module with a numerical hessian approximation (see `ext_cost_num_hess`).\n"
+ "OR the option to provide a symbolic custom hessian approximation (see `cost_expr_ext_cost_custom_hess`).\n")
+
# terminal
if cost.cost_type_e == 'LINEAR_LS':
ny_e = cost.W_e.shape[0]
@@ -183,6 +202,14 @@ def make_ocp_dims_consistent(acados_ocp):
raise Exception('inconsistent dimension: regarding W_e, yref_e.')
dims.ny_e = ny_e
+ elif cost.cost_type_e == 'EXTERNAL':
+ if opts.hessian_approx == 'GAUSS_NEWTON' and opts.ext_cost_num_hess == 0 and model.cost_expr_ext_cost_custom_hess_e is None:
+ print("\nWARNING: Gauss-Newton Hessian approximation with EXTERNAL cost type not possible!\n"
+ "got cost_type_e: EXTERNAL, hessian_approx: 'GAUSS_NEWTON.'\n"
+ "GAUSS_NEWTON hessian is only supported for cost_types [NON]LINEAR_LS.\n"
+ "If you continue, acados will proceed computing the exact hessian for the cost term.\n"
+ "Note: There is also the option to use the external cost module with a numerical hessian approximation (see `ext_cost_num_hess`).\n"
+ "OR the option to provide a symbolic custom hessian approximation (see `cost_expr_ext_cost_custom_hess`).\n")
## constraints
# initial
@@ -434,18 +461,14 @@ def make_ocp_dims_consistent(acados_ocp):
if np.shape(opts.shooting_nodes)[0] != dims.N+1:
raise Exception('inconsistent dimension N, regarding shooting_nodes.')
- # time_steps = opts.shooting_nodes[1:] - opts.shooting_nodes[0:-1]
- # # identify constant time-steps: due to numerical reasons the content of time_steps might vary a bit
- # delta_time_steps = time_steps[1:] - time_steps[0:-1]
- # avg_time_steps = np.average(time_steps)
- # # criterion for constant time-step detection: the min/max difference in values normalized by the average
- # check_const_time_step = np.max(delta_time_steps)-np.min(delta_time_steps) / avg_time_steps
- # # if the criterion is small, we have a constant time-step
- # if check_const_time_step < 1e-9:
- # time_steps[:] = avg_time_steps # if we have a constant time-step: apply the average time-step
- time_steps = np.zeros((dims.N,))
- for i in range(dims.N):
- time_steps[i] = opts.shooting_nodes[i+1] - opts.shooting_nodes[i] # TODO use commented code above
+ time_steps = opts.shooting_nodes[1:] - opts.shooting_nodes[0:-1]
+ # identify constant time_steps: due to numerical reasons the content of time_steps might vary a bit
+ avg_time_steps = np.average(time_steps)
+ # criterion for constant time step detection: the min/max difference in values normalized by the average
+ check_const_time_step = (np.max(time_steps)-np.min(time_steps)) / avg_time_steps
+ # if the criterion is small, we have a constant time_step
+ if check_const_time_step < 1e-9:
+ time_steps[:] = avg_time_steps # if we have a constant time_step: apply the average time_step
opts.time_steps = time_steps
@@ -525,8 +548,7 @@ def ocp_formulation_json_dump(acados_ocp, simulink_opts, json_file='acados_ocp_n
# strip shooting_nodes
ocp_nlp_dict['solver_options'].pop('shooting_nodes', None)
-
- dims_dict = acados_class2dict(acados_ocp.dims)
+ dims_dict = format_class_dict(acados_ocp.dims.__dict__)
ocp_check_against_layout(ocp_nlp_dict, dims_dict)
@@ -627,15 +649,25 @@ def ocp_generate_external_functions(acados_ocp, model):
generate_c_code_external_cost(model, 'terminal', opts)
-def ocp_render_templates(acados_ocp, json_file):
+def ocp_get_default_cmake_builder() -> CMakeBuilder:
+ """
+ If :py:class:`~acados_template.acados_ocp_solver.AcadosOcpSolver` is used with `CMake` this function returns a good first setting.
+ :return: default :py:class:`~acados_template.builders.CMakeBuilder`
+ """
+ cmake_builder = CMakeBuilder()
+ cmake_builder.options_on = ['BUILD_ACADOS_OCP_SOLVER_LIB']
+ return cmake_builder
+
+
+def ocp_render_templates(acados_ocp, json_file, cmake_builder=None):
name = acados_ocp.model.name
# setting up loader and environment
- json_path = os.path.join(os.getcwd(), json_file)
+ json_path = os.path.abspath(json_file)
if not os.path.exists(json_path):
- raise Exception('{} not found!'.format(json_path))
+ raise Exception(f'Path "{json_path}" not found!')
code_export_dir = acados_ocp.code_export_directory
template_dir = code_export_dir
@@ -657,9 +689,14 @@ def ocp_render_templates(acados_ocp, json_file):
out_file = f'acados_solver.pxd'
render_template(in_file, out_file, template_dir, json_path)
- in_file = 'Makefile.in'
- out_file = 'Makefile'
- render_template(in_file, out_file, template_dir, json_path)
+ if cmake_builder is not None:
+ in_file = 'CMakeLists.in.txt'
+ out_file = 'CMakeLists.txt'
+ render_template(in_file, out_file, template_dir, json_path)
+ else:
+ in_file = 'Makefile.in'
+ out_file = 'Makefile'
+ render_template(in_file, out_file, template_dir, json_path)
in_file = 'acados_solver_sfun.in.c'
out_file = f'acados_solver_sfunction_{name}.c'
@@ -769,21 +806,33 @@ class AcadosOcpSolver:
"""
Class to interact with the acados ocp solver C object.
- :param acados_ocp: type AcadosOcp - description of the OCP for acados
+ :param acados_ocp: type :py:class:`~acados_template.acados_ocp.AcadosOcp` - description of the OCP for acados
:param json_file: name for the json file used to render the templated code - default: acados_ocp_nlp.json
:param simulink_opts: Options to configure Simulink S-function blocks, mainly to activate possible Inputs and Outputs
"""
if sys.platform=="win32":
from ctypes import wintypes
- dlclose = ctypes.WinDLL('kernel32', use_last_error=True).FreeLibrary
+ from ctypes import WinDLL
+ dlclose = WinDLL('kernel32', use_last_error=True).FreeLibrary
dlclose.argtypes = [wintypes.HMODULE]
else:
dlclose = CDLL(None).dlclose
dlclose.argtypes = [c_void_p]
@classmethod
- def generate(cls, acados_ocp, json_file='acados_ocp_nlp.json', simulink_opts=None, build=True):
+ def generate(cls, acados_ocp, json_file='acados_ocp_nlp.json', simulink_opts=None, cmake_builder: CMakeBuilder = None):
+ """
+ Generates the code for an acados OCP solver, given the description in acados_ocp.
+ :param acados_ocp: type AcadosOcp - description of the OCP for acados
+ :param json_file: name for the json file used to render the templated code - default: `acados_ocp_nlp.json`
+ :param simulink_opts: Options to configure Simulink S-function blocks, mainly to activate possible inputs and
+ outputs; default: `None`
+ :param cmake_builder: type :py:class:`~acados_template.builders.CMakeBuilder` generate a `CMakeLists.txt` and use
+ the `CMake` pipeline instead of a `Makefile` (`CMake` seems to be the better option in conjunction with
+ `MS Visual Studio`); default: `None`
+ """
model = acados_ocp.model
+ acados_ocp.code_export_directory = os.path.abspath(acados_ocp.code_export_directory)
if simulink_opts is None:
simulink_opts = get_simulink_default_opts()
@@ -807,24 +856,106 @@ class AcadosOcpSolver:
# dump to json
ocp_formulation_json_dump(acados_ocp, simulink_opts, json_file)
- code_export_dir = acados_ocp.code_export_directory
# render templates
- ocp_render_templates(acados_ocp, json_file)
+ ocp_render_templates(acados_ocp, json_file, cmake_builder=cmake_builder)
+ acados_ocp.json_file = json_file
- if build:
- ## Compile solver
- cwd=os.getcwd()
- os.chdir(code_export_dir)
- os.system('make clean_ocp_shared_lib')
- os.system('make ocp_shared_lib')
- os.chdir(cwd)
-
- def __init__(self, model_name, N, code_export_dir):
- self.model_name = model_name
- self.N = N
+
+ @classmethod
+ def build(cls, code_export_dir, with_cython=False, cmake_builder: CMakeBuilder = None):
+ """
+ Builds the code for an acados OCP solver, that has been generated in code_export_dir
+ :param code_export_dir: directory in which acados OCP solver has been generated, see generate()
+ :param with_cython: option indicating if the cython interface is build, default: False.
+ :param cmake_builder: type :py:class:`~acados_template.builders.CMakeBuilder` generate a `CMakeLists.txt` and use
+ the `CMake` pipeline instead of a `Makefile` (`CMake` seems to be the better option in conjunction with
+ `MS Visual Studio`); default: `None`
+ """
+ code_export_dir = os.path.abspath(code_export_dir)
+ cwd=os.getcwd()
+ os.chdir(code_export_dir)
+ if with_cython:
+ os.system('make clean_ocp_cython')
+ os.system('make ocp_cython')
+ else:
+ if cmake_builder is not None:
+ cmake_builder.exec(code_export_dir)
+ else:
+ os.system('make clean_ocp_shared_lib')
+ os.system('make ocp_shared_lib')
+ os.chdir(cwd)
+
+
+ @classmethod
+ def create_cython_solver(cls, json_file):
+ """
+ Returns an `AcadosOcpSolverCython` object.
+
+ This is an alternative Cython based Python wrapper to the acados OCP solver in C.
+ This offers faster interaction with the solver, because getter and setter calls, which include shape checking are done in compiled C code.
+
+ The default wrapper `AcadosOcpSolver` is based on ctypes.
+ """
+ with open(json_file, 'r') as f:
+ acados_ocp_json = json.load(f)
+ code_export_directory = acados_ocp_json['code_export_directory']
+
+ importlib.invalidate_caches()
+ rel_code_export_directory = os.path.relpath(code_export_directory)
+ acados_ocp_solver_pyx = importlib.import_module(f'{rel_code_export_directory}.acados_ocp_solver_pyx')
+
+ AcadosOcpSolverCython = getattr(acados_ocp_solver_pyx, 'AcadosOcpSolverCython')
+ return AcadosOcpSolverCython(acados_ocp_json['model']['name'],
+ acados_ocp_json['solver_options']['nlp_solver_type'],
+ acados_ocp_json['dims']['N'])
+
+
+ def __init__(self, acados_ocp, json_file='acados_ocp_nlp.json', simulink_opts=None, build=True, generate=True, cmake_builder: CMakeBuilder = None):
self.solver_created = False
- self.shared_lib_name = f'{code_export_dir}/libacados_ocp_solver_{self.model_name}.so'
+ if generate:
+ self.generate(acados_ocp, json_file=json_file, simulink_opts=simulink_opts, cmake_builder=cmake_builder)
+
+ # load json, store options in object
+ with open(json_file, 'r') as f:
+ acados_ocp_json = json.load(f)
+ self.N = acados_ocp_json['dims']['N']
+ self.model_name = acados_ocp_json['model']['name']
+ self.solver_options = acados_ocp_json['solver_options']
+
+ acados_lib_path = acados_ocp_json['acados_lib_path']
+ code_export_directory = acados_ocp_json['code_export_directory']
+
+ if build:
+ self.build(code_export_directory, with_cython=False, cmake_builder=cmake_builder)
+
+ # prepare library loading
+ lib_prefix = 'lib'
+ lib_ext = '.so'
+ if os.name == 'nt':
+ lib_prefix = ''
+ lib_ext = ''
+ # ToDo: check for mac
+
+ # Load acados library to avoid unloading the library.
+ # This is necessary if acados was compiled with OpenMP, since the OpenMP threads can't be destroyed.
+ # Unloading a library which uses OpenMP results in a segfault (on any platform?).
+ # see [https://stackoverflow.com/questions/34439956/vc-crash-when-freeing-a-dll-built-with-openmp]
+ # or [https://python.hotexamples.com/examples/_ctypes/-/dlclose/python-dlclose-function-examples.html]
+ libacados_name = f'{lib_prefix}acados{lib_ext}'
+ libacados_filepath = os.path.join(acados_lib_path, libacados_name)
+ self.__acados_lib = CDLL(libacados_filepath)
+ # find out if acados was compiled with OpenMP
+ try:
+ self.__acados_lib_uses_omp = getattr(self.__acados_lib, 'omp_get_thread_num') is not None
+ except AttributeError as e:
+ self.__acados_lib_uses_omp = False
+ if self.__acados_lib_uses_omp:
+ print('acados was compiled with OpenMP.')
+ else:
+ print('acados was compiled without OpenMP.')
+ libacados_ocp_solver_name = f'{lib_prefix}acados_ocp_solver_{self.model_name}{lib_ext}'
+ self.shared_lib_name = os.path.join(code_export_directory, libacados_ocp_solver_name)
# get shared_lib
self.shared_lib = CDLL(self.shared_lib_name)
@@ -842,6 +973,8 @@ class AcadosOcpSolver:
# get pointers solver
self.__get_pointers_solver()
+ self.status = 0
+
def __get_pointers_solver(self):
"""
@@ -864,6 +997,10 @@ class AcadosOcpSolver:
getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_out").restype = c_void_p
self.nlp_out = getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_out")(self.capsule)
+ getattr(self.shared_lib, f"{self.model_name}_acados_get_sens_out").argtypes = [c_void_p]
+ getattr(self.shared_lib, f"{self.model_name}_acados_get_sens_out").restype = c_void_p
+ self.sens_out = getattr(self.shared_lib, f"{self.model_name}_acados_get_sens_out")(self.capsule)
+
getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_in").argtypes = [c_void_p]
getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_in").restype = c_void_p
self.nlp_in = getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_in")(self.capsule)
@@ -872,46 +1009,37 @@ class AcadosOcpSolver:
getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_solver").restype = c_void_p
self.nlp_solver = getattr(self.shared_lib, f"{self.model_name}_acados_get_nlp_solver")(self.capsule)
- # treat parameters separately
- getattr(self.shared_lib, f"{self.model_name}_acados_update_params").argtypes = [c_void_p, c_int, POINTER(c_double)]
- getattr(self.shared_lib, f"{self.model_name}_acados_update_params").restype = c_int
- self._set_param = getattr(self.shared_lib, f"{self.model_name}_acados_update_params")
-
- self.shared_lib.ocp_nlp_constraint_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, POINTER(c_int)]
- self.shared_lib.ocp_nlp_constraint_dims_get_from_attr.restype = c_int
-
- self.shared_lib.ocp_nlp_cost_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, POINTER(c_int)]
- self.shared_lib.ocp_nlp_cost_dims_get_from_attr.restype = c_int
-
- self.shared_lib.ocp_nlp_constraints_model_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_cost_model_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_out_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_set.argtypes = \
- [c_void_p, c_void_p, c_int, c_char_p, c_void_p]
def solve(self):
"""
Solve the ocp with current input.
"""
-
getattr(self.shared_lib, f"{self.model_name}_acados_solve").argtypes = [c_void_p]
getattr(self.shared_lib, f"{self.model_name}_acados_solve").restype = c_int
- status = getattr(self.shared_lib, f"{self.model_name}_acados_solve")(self.capsule)
- return status
+ self.status = getattr(self.shared_lib, f"{self.model_name}_acados_solve")(self.capsule)
+
+ return self.status
+
+
+ def reset(self):
+ """
+ Sets current iterate to all zeros.
+ """
+ getattr(self.shared_lib, f"{self.model_name}_acados_reset").argtypes = [c_void_p]
+ getattr(self.shared_lib, f"{self.model_name}_acados_reset").restype = c_int
+ getattr(self.shared_lib, f"{self.model_name}_acados_reset")(self.capsule)
+
+ return
def set_new_time_steps(self, new_time_steps):
"""
- Set new time steps before solving. Only reload library without code generation but with new time steps.
+ Set new time steps.
+ Recreates the solver if N changes.
- :param new_time_steps: vector of new time steps for the solver
+ :param new_time_steps: 1 dimensional np array of new time steps for the solver
- .. note:: This allows for different use-cases: either set a new size of time-steps or a new distribution of
+ .. note:: This allows for different use-cases: either set a new size of time_steps or a new distribution of
the shooting nodes without changing the number, e.g., to reach a different final time. Both cases
do not require a new code export and compilation.
"""
@@ -921,15 +1049,14 @@ class AcadosOcpSolver:
raise Exception('Solver was not yet created!')
# check if time steps really changed in value
- if np.array_equal(self.acados_ocp.solver_options.time_steps, new_time_steps):
+ if np.array_equal(self.solver_options['time_steps'], new_time_steps):
return
N = new_time_steps.size
- model = self.acados_ocp.model
new_time_steps_data = cast(new_time_steps.ctypes.data, POINTER(c_double))
# check if recreation of acados is necessary (no need to recreate acados if sizes are identical)
- if self.acados_ocp.solver_options.time_steps.size == N:
+ if len(self.solver_options['time_steps']) == N:
getattr(self.shared_lib, f"{self.model_name}_acados_update_time_steps").argtypes = [c_void_p, c_int, c_void_p]
getattr(self.shared_lib, f"{self.model_name}_acados_update_time_steps").restype = c_int
assert getattr(self.shared_lib, f"{self.model_name}_acados_update_time_steps")(self.capsule, N, new_time_steps_data) == 0
@@ -941,11 +1068,6 @@ class AcadosOcpSolver:
getattr(self.shared_lib, f"{self.model_name}_acados_free").restype = c_int
getattr(self.shared_lib, f"{self.model_name}_acados_free")(self.capsule)
- # store N and new time steps
- self.N = self.acados_ocp.dims.N = N
- self.acados_ocp.solver_options.time_steps = new_time_steps
- self.acados_ocp.solver_options.Tsim = self.acados_ocp.solver_options.time_steps[0]
-
# create solver with new time steps
getattr(self.shared_lib, f"{self.model_name}_acados_create_with_discretization").argtypes = [c_void_p, c_int, c_void_p]
getattr(self.shared_lib, f"{self.model_name}_acados_create_with_discretization").restype = c_int
@@ -956,6 +1078,75 @@ class AcadosOcpSolver:
# get pointers solver
self.__get_pointers_solver()
+ # store time_steps, N
+ self.solver_options['time_steps'] = new_time_steps
+ self.N = N
+ self.solver_options['Tsim'] = self.solver_options['time_steps'][0]
+
+
+ def update_qp_solver_cond_N(self, qp_solver_cond_N: int):
+ """
+ Recreate solver with new value `qp_solver_cond_N` with a partial condensing QP solver.
+ This function is relevant for code reuse, i.e., if either `set_new_time_steps(...)` is used or
+ the influence of a different `qp_solver_cond_N` is studied without code export and compilation.
+ :param qp_solver_cond_N: new number of condensing stages for the solver
+
+ .. note:: This function can only be used in combination with a partial condensing QP solver.
+
+ .. note:: After `set_new_time_steps(...)` is used and depending on the new number of time steps it might be
+ necessary to change `qp_solver_cond_N` as well (using this function), i.e., typically
+ `qp_solver_cond_N < N`.
+ """
+ # unlikely but still possible
+ if not self.solver_created:
+ raise Exception('Solver was not yet created!')
+ if self.N < qp_solver_cond_N:
+ raise Exception('Setting qp_solver_cond_N to be larger than N does not work!')
+ if self.solver_options['qp_solver_cond_N'] != qp_solver_cond_N:
+ self.solver_created = False
+
+ # recreate the solver
+ fun_name = f'{self.model_name}_acados_update_qp_solver_cond_N'
+ getattr(self.shared_lib, fun_name).argtypes = [c_void_p, c_int]
+ getattr(self.shared_lib, fun_name).restype = c_int
+ assert getattr(self.shared_lib, fun_name)(self.capsule, qp_solver_cond_N) == 0
+
+ # store the new value
+ self.solver_options['qp_solver_cond_N'] = qp_solver_cond_N
+ self.solver_created = True
+
+ # get pointers solver
+ self.__get_pointers_solver()
+
+
+ def eval_param_sens(self, index, stage=0, field="ex"):
+ """
+ Calculate the sensitivity of the curent solution with respect to the initial state component of index
+
+ :param index: integer corresponding to initial state index in range(nx)
+ """
+
+ field_ = field
+ field = field_.encode('utf-8')
+
+ # checks
+ if not isinstance(index, int):
+ raise Exception('AcadosOcpSolver.eval_param_sens(): index must be Integer.')
+
+ self.shared_lib.ocp_nlp_dims_get_from_attr.argtypes = [c_void_p, c_void_p, c_void_p, c_int, c_char_p]
+ self.shared_lib.ocp_nlp_dims_get_from_attr.restype = c_int
+ nx = self.shared_lib.ocp_nlp_dims_get_from_attr(self.nlp_config, self.nlp_dims, self.nlp_out, 0, "x".encode('utf-8'))
+
+ if index < 0 or index > nx:
+ raise Exception(f'AcadosOcpSolver.eval_param_sens(): index must be in [0, nx-1], got: {index}.')
+
+ # actual eval_param
+ self.shared_lib.ocp_nlp_eval_param_sens.argtypes = [c_void_p, c_char_p, c_int, c_int, c_void_p]
+ self.shared_lib.ocp_nlp_eval_param_sens.restype = None
+ self.shared_lib.ocp_nlp_eval_param_sens(self.nlp_solver, field, stage, index, self.sens_out)
+
+ return
+
def get(self, stage_, field_):
"""
@@ -978,23 +1169,30 @@ class AcadosOcpSolver:
out_fields = ['x', 'u', 'z', 'pi', 'lam', 't', 'sl', 'su']
# mem_fields = ['sl', 'su']
+ sens_fields = ['sens_u', "sens_x"]
+ all_fields = out_fields + sens_fields
+
field = field_
- field = field.encode('utf-8')
- if (field_ not in out_fields):
+ if (field_ not in all_fields):
raise Exception('AcadosOcpSolver.get(): {} is an invalid argument.\
- \n Possible values are {}. Exiting.'.format(field_, out_fields))
+ \n Possible values are {}. Exiting.'.format(field_, all_fields))
if not isinstance(stage_, int):
raise Exception('AcadosOcpSolver.get(): stage index must be Integer.')
if stage_ < 0 or stage_ > self.N:
- raise Exception('AcadosOcpSolver.get(): stage index must be in [0, N], got: {}.'.format(self.N))
+ raise Exception('AcadosOcpSolver.get(): stage index must be in [0, N], got: {}.'.format(stage_))
if stage_ == self.N and field_ == 'pi':
raise Exception('AcadosOcpSolver.get(): field {} does not exist at final stage {}.'\
.format(field_, stage_))
+ if field_ in sens_fields:
+ field = field_.replace('sens_', '')
+
+ field = field.encode('utf-8')
+
self.shared_lib.ocp_nlp_dims_get_from_attr.argtypes = \
[c_void_p, c_void_p, c_void_p, c_int, c_char_p]
self.shared_lib.ocp_nlp_dims_get_from_attr.restype = c_int
@@ -1015,6 +1213,11 @@ class AcadosOcpSolver:
# [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
# self.shared_lib.ocp_nlp_get_at_stage(self.nlp_config, \
# self.nlp_dims, self.nlp_solver, stage_, field, out_data)
+ elif field_ in sens_fields:
+ self.shared_lib.ocp_nlp_out_get.argtypes = \
+ [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
+ self.shared_lib.ocp_nlp_out_get(self.nlp_config, \
+ self.nlp_dims, self.sens_out, stage_, field, out_data)
return out
@@ -1029,6 +1232,7 @@ class AcadosOcpSolver:
- res_comp: residual wrt complementarity conditions
- qp_stat: status of QP solver
- qp_iter: number of QP iterations
+ - alpha: SQP step size
- qp_res_stat: stationarity residual of the last QP solution
- qp_res_eq: residual wrt equality constraints (dynamics) of the last QP solution
- qp_res_ineq: residual wrt inequality constraints (constraints) of the last QP solution
@@ -1036,19 +1240,18 @@ class AcadosOcpSolver:
"""
stat = self.get_stats("statistics")
- if self.acados_ocp.solver_options.nlp_solver_type == 'SQP':
- print('\niter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter')
- if stat.shape[0]>7:
+ if self.solver_options['nlp_solver_type'] == 'SQP':
+ print('\niter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter\talpha')
+ if stat.shape[0]>8:
print('\tqp_res_stat\tqp_res_eq\tqp_res_ineq\tqp_res_comp')
for jj in range(stat.shape[1]):
- print('{:d}\t{:e}\t{:e}\t{:e}\t{:e}\t{:d}\t{:d}'.format( \
- int(stat[0][jj]), stat[1][jj], stat[2][jj], \
- stat[3][jj], stat[4][jj], int(stat[5][jj]), int(stat[6][jj])))
- if stat.shape[0]>7:
+ print(f'{int(stat[0][jj]):d}\t{stat[1][jj]:e}\t{stat[2][jj]:e}\t{stat[3][jj]:e}\t' +
+ f'{stat[4][jj]:e}\t{int(stat[5][jj]):d}\t{int(stat[6][jj]):d}\t{stat[7][jj]:e}\t')
+ if stat.shape[0]>8:
print('\t{:e}\t{:e}\t{:e}\t{:e}'.format( \
- stat[7][jj], stat[8][jj], stat[9][jj], stat[10][jj]))
+ stat[8][jj], stat[9][jj], stat[10][jj], stat[11][jj]))
print('\n')
- elif self.acados_ocp.solver_options.nlp_solver_type == 'SQP_RTI':
+ elif self.solver_options['nlp_solver_type'] == 'SQP_RTI':
print('\niter\tqp_stat\tqp_iter')
if stat.shape[0]>3:
print('\tqp_res_stat\tqp_res_eq\tqp_res_ineq\tqp_res_comp')
@@ -1108,6 +1311,7 @@ class AcadosOcpSolver:
with open(filename, 'r') as f:
solution = json.load(f)
+ print(f"loading iterate {filename}")
for key in solution.keys():
(field, stage) = key.split('_')
self.set(int(stage), field, np.array(solution[key]))
@@ -1117,62 +1321,99 @@ class AcadosOcpSolver:
"""
Get the information of the last solver call.
- :param field: string in ['statistics', 'time_tot', 'time_lin', 'time_sim', 'time_sim_ad', 'time_sim_la', 'time_qp', 'time_qp_solver_call', 'time_reg', 'sqp_iter']
+ :param field: string in ['statistics', 'time_tot', 'time_lin', 'time_sim', 'time_sim_ad', 'time_sim_la', 'time_qp', 'time_qp_solver_call', 'time_reg', 'sqp_iter', 'residuals', 'qp_iter', 'alpha']
+
+ Available fileds:
+ - time_tot: total CPU time previous call
+ - time_lin: CPU time for linearization
+ - time_sim: CPU time for integrator
+ - time_sim_ad: CPU time for integrator contribution of external function calls
+ - time_sim_la: CPU time for integrator contribution of linear algebra
+ - time_qp: CPU time qp solution
+ - time_qp_solver_call: CPU time inside qp solver (without converting the QP)
+ - time_qp_xcond: time_glob: CPU time globalization
+ - time_solution_sensitivities: CPU time for previous call to eval_param_sens
+ - time_reg: CPU time regularization
+ - sqp_iter: number of SQP iterations
+ - qp_iter: vector of QP iterations for last SQP call
+ - statistics: table with info about last iteration
+ - stat_m: number of rows in statistics matrix
+ - stat_n: number of columns in statistics matrix
+ - residuals: residuals of last iterate
+ - alpha: step sizes of SQP iterations
"""
- fields = ['time_tot', # total cpu time previous call
- 'time_lin', # cpu time for linearization
- 'time_sim', # cpu time for integrator
- 'time_sim_ad', # cpu time for integrator contribution of external function calls
- 'time_sim_la', # cpu time for integrator contribution of linear algebra
- 'time_qp', # cpu time qp solution
- 'time_qp_solver_call', # cpu time inside qp solver (without converting the QP)
+ double_fields = ['time_tot',
+ 'time_lin',
+ 'time_sim',
+ 'time_sim_ad',
+ 'time_sim_la',
+ 'time_qp',
+ 'time_qp_solver_call',
'time_qp_xcond',
- 'time_glob', # cpu time globalization
- 'time_reg', # cpu time regularization
- 'sqp_iter', # number of SQP iterations
- 'qp_iter', # vector of QP iterations for last SQP call
- 'statistics', # table with info about last iteration
+ 'time_glob',
+ 'time_solution_sensitivities',
+ 'time_reg'
+ ]
+ fields = double_fields + [
+ 'sqp_iter',
+ 'qp_iter',
+ 'statistics',
'stat_m',
- 'stat_n',]
+ 'stat_n',
+ 'residuals',
+ 'alpha',
+ ]
+ field = field_.encode('utf-8')
- field = field_
- field = field.encode('utf-8')
- if (field_ not in fields):
- raise Exception('AcadosOcpSolver.get_stats(): {} is not a valid argument.\
- \n Possible values are {}. Exiting.'.format(fields, fields))
if field_ in ['sqp_iter', 'stat_m', 'stat_n']:
out = np.ascontiguousarray(np.zeros((1,)), dtype=np.int64)
out_data = cast(out.ctypes.data, POINTER(c_int64))
+ self.shared_lib.ocp_nlp_get.argtypes = [c_void_p, c_void_p, c_char_p, c_void_p]
+ self.shared_lib.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_data)
+ return out
+
+ # TODO: just return double instead of np.
+ elif field_ in double_fields:
+ out = np.zeros((1,))
+ out_data = cast(out.ctypes.data, POINTER(c_double))
+ self.shared_lib.ocp_nlp_get.argtypes = [c_void_p, c_void_p, c_char_p, c_void_p]
+ self.shared_lib.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_data)
+ return out
elif field_ == 'statistics':
sqp_iter = self.get_stats("sqp_iter")
stat_m = self.get_stats("stat_m")
stat_n = self.get_stats("stat_n")
-
min_size = min([stat_m, sqp_iter+1])
-
out = np.ascontiguousarray(
np.zeros((stat_n[0]+1, min_size[0])), dtype=np.float64)
out_data = cast(out.ctypes.data, POINTER(c_double))
+ self.shared_lib.ocp_nlp_get.argtypes = [c_void_p, c_void_p, c_char_p, c_void_p]
+ self.shared_lib.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_data)
+ return out
elif field_ == 'qp_iter':
full_stats = self.get_stats('statistics')
- if self.acados_ocp.solver_options.nlp_solver_type == 'SQP':
- out = full_stats[6, :]
- elif self.acados_ocp.solver_options.nlp_solver_type == 'SQP_RTI':
- out = full_stats[2, :]
+ if self.solver_options['nlp_solver_type'] == 'SQP':
+ return full_stats[6, :]
+ elif self.solver_options['nlp_solver_type'] == 'SQP_RTI':
+ return full_stats[2, :]
- else:
- out = np.ascontiguousarray(np.zeros((1,)), dtype=np.float64)
- out_data = cast(out.ctypes.data, POINTER(c_double))
+ elif field_ == 'alpha':
+ full_stats = self.get_stats('statistics')
+ if self.solver_options['nlp_solver_type'] == 'SQP':
+ return full_stats[7, :]
+ else: # self.solver_options['nlp_solver_type'] == 'SQP_RTI':
+ raise Exception("alpha values are not available for SQP_RTI")
- if not field_ == 'qp_iter':
- self.shared_lib.ocp_nlp_get.argtypes = [c_void_p, c_void_p, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_data)
+ elif field_ == 'residuals':
+ return self.get_residuals()
- return out
+ else:
+ raise Exception(f'AcadosOcpSolver.get_stats(): {field} is not a valid argument.'
+ + f'\n Possible values are {fields}.')
def get_cost(self):
@@ -1196,12 +1437,12 @@ class AcadosOcpSolver:
return out[0]
- def get_residuals(self):
+ def get_residuals(self, recompute=False):
"""
Returns an array of the form [res_stat, res_eq, res_ineq, res_comp].
"""
# compute residuals if RTI
- if self.acados_ocp.solver_options.nlp_solver_type == 'SQP_RTI':
+ if self.solver_options['nlp_solver_type'] == 'SQP_RTI' or recompute:
self.shared_lib.ocp_nlp_eval_residuals.argtypes = [c_void_p, c_void_p, c_void_p]
self.shared_lib.ocp_nlp_eval_residuals(self.nlp_solver, self.nlp_in, self.nlp_out)
@@ -1230,14 +1471,12 @@ class AcadosOcpSolver:
# Note: this function should not be used anymore, better use cost_set, constraints_set
-
def set(self, stage_, field_, value_):
-
"""
Set numerical data inside the solver.
:param stage: integer corresponding to shooting node
- :param field: string in ['x', 'u', 'pi', 'lam', 't', 'p']
+ :param field: string in ['x', 'u', 'pi', 'lam', 't', 'p', 'xdot_guess', 'z_guess']
.. note:: regarding lam, t: \n
the inequalities are internally organized in the following order: \n
@@ -1253,6 +1492,7 @@ class AcadosOcpSolver:
cost_fields = ['y_ref', 'yref']
constraints_fields = ['lbx', 'ubx', 'lbu', 'ubu']
out_fields = ['x', 'u', 'pi', 'lam', 't', 'z', 'sl', 'su']
+ mem_fields = ['xdot_guess', 'z_guess']
# cast value_ to avoid conversion issues
if isinstance(value_, (float, int)):
@@ -1294,18 +1534,25 @@ class AcadosOcpSolver:
value_data_p = cast((value_data), c_void_p)
if field_ in constraints_fields:
+ self.shared_lib.ocp_nlp_constraints_model_set.argtypes = \
+ [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
self.shared_lib.ocp_nlp_constraints_model_set(self.nlp_config, \
self.nlp_dims, self.nlp_in, stage, field, value_data_p)
elif field_ in cost_fields:
+ self.shared_lib.ocp_nlp_cost_model_set.argtypes = \
+ [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
self.shared_lib.ocp_nlp_cost_model_set(self.nlp_config, \
self.nlp_dims, self.nlp_in, stage, field, value_data_p)
elif field_ in out_fields:
+ self.shared_lib.ocp_nlp_out_set.argtypes = \
+ [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
self.shared_lib.ocp_nlp_out_set(self.nlp_config, \
self.nlp_dims, self.nlp_out, stage, field, value_data_p)
- # elif field_ in mem_fields:
- # self.shared_lib.ocp_nlp_set(self.nlp_config, \
- # self.nlp_solver, stage, field, value_data_p)
-
+ elif field_ in mem_fields:
+ self.shared_lib.ocp_nlp_set.argtypes = \
+ [c_void_p, c_void_p, c_int, c_char_p, c_void_p]
+ self.shared_lib.ocp_nlp_set(self.nlp_config, \
+ self.nlp_solver, stage, field, value_data_p)
return
@@ -1364,9 +1611,8 @@ class AcadosOcpSolver:
raise Exception("Unknown api: '{}'".format(api))
if value_shape != tuple(dims):
- raise Exception('AcadosOcpSolver.cost_set(): mismatching dimension', \
- ' for field "{}" with dimension {} (you have {})'.format( \
- field_, tuple(dims), value_shape))
+ raise Exception('AcadosOcpSolver.cost_set(): mismatching dimension' +
+ f' for field "{field_}" at stage {stage} with dimension {tuple(dims)} (you have {value_shape})')
value_data = cast(value_.ctypes.data, POINTER(c_double))
value_data_p = cast((value_data), c_void_p)
@@ -1433,8 +1679,8 @@ class AcadosOcpSolver:
raise Exception("Unknown api: '{}'".format(api))
if value_shape != tuple(dims):
- raise Exception('AcadosOcpSolver.constraints_set(): mismatching dimension' \
- ' for field "{}" with dimension {} (you have {})'.format(field_, tuple(dims), value_shape))
+ raise Exception(f'AcadosOcpSolver.constraints_set(): mismatching dimension' +
+ f' for field "{field_}" at stage {stage} with dimension {tuple(dims)} (you have {value_shape})')
value_data = cast(value_.ctypes.data, POINTER(c_double))
value_data_p = cast((value_data), c_void_p)
@@ -1490,11 +1736,21 @@ class AcadosOcpSolver:
"""
Set options of the solver.
- :param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction'
- :param value: of type int, float
+ :param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0'
+
+ :param value: of type int, float, string
+
+ - qp_tol_stat: QP solver tolerance stationarity
+ - qp_tol_eq: QP solver tolerance equalities
+ - qp_tol_ineq: QP solver tolerance inequalities
+ - qp_tol_comp: QP solver tolerance complementarity
+ - qp_tau_min: for HPIPM QP solvers: minimum value of barrier parameter in HPIPM
+ - qp_mu0: for HPIPM QP solvers: initial value for complementarity slackness
+ - warm_start_first_qp: indicates if first QP in SQP is warm_started
"""
- int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks']
- double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction']
+ int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'warm_start_first_qp']
+ double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction', 'eps_sufficient_descent',
+ 'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0']
string_fields = ['globalization']
# check field availability and type
@@ -1522,10 +1778,10 @@ class AcadosOcpSolver:
if field_ == 'rti_phase':
if value_ < 0 or value_ > 2:
- raise Exception('AcadosOcpSolver.solve(): argument \'rti_phase\' can '
+ raise Exception('AcadosOcpSolver.options_set(): argument \'rti_phase\' can '
'take only values 0, 1, 2 for SQP-RTI-type solvers')
- if self.acados_ocp.solver_options.nlp_solver_type != 'SQP_RTI' and value_ > 0:
- raise Exception('AcadosOcpSolver.solve(): argument \'rti_phase\' can '
+ if self.solver_options['nlp_solver_type'] != 'SQP_RTI' and value_ > 0:
+ raise Exception('AcadosOcpSolver.options_set(): argument \'rti_phase\' can '
'take only value 0 for SQP-type solvers')
# encode
diff --git a/pyextra/acados_template/acados_ocp_solver_fast.py b/pyextra/acados_template/acados_ocp_solver_fast.py
deleted file mode 100644
index 656d288f1c..0000000000
--- a/pyextra/acados_template/acados_ocp_solver_fast.py
+++ /dev/null
@@ -1,402 +0,0 @@
-import sys
-import os
-import json
-import numpy as np
-from datetime import datetime
-
-from ctypes import POINTER, CDLL, c_void_p, c_int, cast, c_double, c_char_p
-
-from copy import deepcopy
-
-from .generate_c_code_explicit_ode import generate_c_code_explicit_ode
-from .generate_c_code_implicit_ode import generate_c_code_implicit_ode
-from .generate_c_code_gnsf import generate_c_code_gnsf
-from .generate_c_code_discrete_dynamics import generate_c_code_discrete_dynamics
-from .generate_c_code_constraint import generate_c_code_constraint
-from .generate_c_code_nls_cost import generate_c_code_nls_cost
-from .generate_c_code_external_cost import generate_c_code_external_cost
-from .acados_ocp import AcadosOcp
-from .acados_model import acados_model_strip_casadi_symbolics
-from .utils import is_column, is_empty, casadi_length, render_template, acados_class2dict,\
- format_class_dict, ocp_check_against_layout, np_array_to_list, make_model_consistent,\
- set_up_imported_gnsf_model, get_acados_path
-
-
-class AcadosOcpSolverFast:
- dlclose = CDLL(None).dlclose
- dlclose.argtypes = [c_void_p]
-
- def __init__(self, model_name, N, code_export_dir):
-
- self.solver_created = False
- self.N = N
- self.model_name = model_name
-
- self.shared_lib_name = f'{code_export_dir}/libacados_ocp_solver_{model_name}.so'
-
- # get shared_lib
- self.shared_lib = CDLL(self.shared_lib_name)
-
- # create capsule
- getattr(self.shared_lib, f"{model_name}_acados_create_capsule").restype = c_void_p
- self.capsule = getattr(self.shared_lib, f"{model_name}_acados_create_capsule")()
-
- # create solver
- getattr(self.shared_lib, f"{model_name}_acados_create").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_create").restype = c_int
- assert getattr(self.shared_lib, f"{model_name}_acados_create")(self.capsule)==0
- self.solver_created = True
-
- # get pointers solver
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_opts").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_opts").restype = c_void_p
- self.nlp_opts = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_opts")(self.capsule)
-
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_dims").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_dims").restype = c_void_p
- self.nlp_dims = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_dims")(self.capsule)
-
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_config").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_config").restype = c_void_p
- self.nlp_config = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_config")(self.capsule)
-
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_out").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_out").restype = c_void_p
- self.nlp_out = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_out")(self.capsule)
-
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_in").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_in").restype = c_void_p
- self.nlp_in = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_in")(self.capsule)
-
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_solver").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_get_nlp_solver").restype = c_void_p
- self.nlp_solver = getattr(self.shared_lib, f"{model_name}_acados_get_nlp_solver")(self.capsule)
-
-
- def solve(self):
- """
- Solve the ocp with current input.
- """
- model_name = self.model_name
-
- getattr(self.shared_lib, f"{model_name}_acados_solve").argtypes = [c_void_p]
- getattr(self.shared_lib, f"{model_name}_acados_solve").restype = c_int
- status = getattr(self.shared_lib, f"{model_name}_acados_solve")(self.capsule)
- return status
-
- def cost_set(self, start_stage_, field_, value_, api='warn'):
- self.cost_set_slice(start_stage_, start_stage_+1, field_, value_[None], api='warn')
- return
-
- def cost_set_slice(self, start_stage_, end_stage_, field_, value_, api='warn'):
- """
- Set numerical data in the cost module of the solver.
-
- :param stage: integer corresponding to shooting node
- :param field: string, e.g. 'yref', 'W', 'ext_cost_num_hess'
- :param value: of appropriate size
- """
- # cast value_ to avoid conversion issues
- if isinstance(value_, (float, int)):
- value_ = np.array([value_])
- value_ = np.ascontiguousarray(np.copy(value_), dtype=np.float64)
- field = field_
- field = field.encode('utf-8')
- dim = np.product(value_.shape[1:])
-
- start_stage = c_int(start_stage_)
- end_stage = c_int(end_stage_)
- self.shared_lib.ocp_nlp_cost_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, POINTER(c_int)]
- self.shared_lib.ocp_nlp_cost_dims_get_from_attr.restype = c_int
-
- dims = np.ascontiguousarray(np.zeros((2,)), dtype=np.intc)
- dims_data = cast(dims.ctypes.data, POINTER(c_int))
-
- self.shared_lib.ocp_nlp_cost_dims_get_from_attr(self.nlp_config,
- self.nlp_dims, self.nlp_out, start_stage_, field, dims_data)
-
- value_shape = value_.shape
- expected_shape = tuple(np.concatenate([np.array([end_stage_ - start_stage_]), dims]))
- if len(value_shape) == 2:
- value_shape = (value_shape[0], value_shape[1], 0)
-
- elif len(value_shape) == 3:
- if api=='old':
- pass
- elif api=='warn':
- if not np.all(np.ravel(value_, order='F')==np.ravel(value_, order='K')):
- raise Exception("Ambiguity in API detected.\n"
- "Are you making an acados model from scrach? Add api='new' to cost_set and carry on.\n"
- "Are you seeing this error suddenly in previously running code? Read on.\n"
- " You are relying on a now-fixed bug in cost_set for field '{}'.\n".format(field_) +
- " acados_template now correctly passes on any matrices to acados in column major format.\n" +
- " Two options to fix this error: \n" +
- " * Add api='old' to cost_set to restore old incorrect behaviour\n" +
- " * Add api='new' to cost_set and remove any unnatural manipulation of the value argument " +
- "such as non-mathematical transposes, reshaping, casting to fortran order, etc... " +
- "If there is no such manipulation, then you have probably been getting an incorrect solution before.")
- # Get elements in column major order
- value_ = np.ravel(value_, order='F')
- elif api=='new':
- # Get elements in column major order
- value_ = np.ravel(value_, order='F')
- else:
- raise Exception("Unknown api: '{}'".format(api))
-
- if value_shape != expected_shape:
- raise Exception('AcadosOcpSolver.cost_set(): mismatching dimension',
- ' for field "{}" with dimension {} (you have {})'.format(
- field_, expected_shape, value_shape))
-
-
- value_data = cast(value_.ctypes.data, POINTER(c_double))
- value_data_p = cast((value_data), c_void_p)
-
- self.shared_lib.ocp_nlp_cost_model_set_slice.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_int, c_char_p, c_void_p, c_int]
- self.shared_lib.ocp_nlp_cost_model_set_slice(self.nlp_config,
- self.nlp_dims, self.nlp_in, start_stage, end_stage, field, value_data_p, dim)
- return
-
- def constraints_set(self, start_stage_, field_, value_, api='warn'):
- self.constraints_set_slice(start_stage_, start_stage_+1, field_, value_[None], api='warn')
- return
-
- def constraints_set_slice(self, start_stage_, end_stage_, field_, value_, api='warn'):
- """
- Set numerical data in the constraint module of the solver.
-
- :param stage: integer corresponding to shooting node
- :param field: string in ['lbx', 'ubx', 'lbu', 'ubu', 'lg', 'ug', 'lh', 'uh', 'uphi']
- :param value: of appropriate size
- """
- # cast value_ to avoid conversion issues
- if isinstance(value_, (float, int)):
- value_ = np.array([value_])
- value_ = value_.astype(float)
-
- field = field_
- field = field.encode('utf-8')
- dim = np.product(value_.shape[1:])
-
- start_stage = c_int(start_stage_)
- end_stage = c_int(end_stage_)
- self.shared_lib.ocp_nlp_constraint_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, POINTER(c_int)]
- self.shared_lib.ocp_nlp_constraint_dims_get_from_attr.restype = c_int
-
- dims = np.ascontiguousarray(np.zeros((2,)), dtype=np.intc)
- dims_data = cast(dims.ctypes.data, POINTER(c_int))
-
- self.shared_lib.ocp_nlp_constraint_dims_get_from_attr(self.nlp_config, \
- self.nlp_dims, self.nlp_out, start_stage_, field, dims_data)
-
- value_shape = value_.shape
- expected_shape = tuple(np.concatenate([np.array([end_stage_ - start_stage_]), dims]))
- if len(value_shape) == 2:
- value_shape = (value_shape[0], value_shape[1], 0)
- elif len(value_shape) == 3:
- if api=='old':
- pass
- elif api=='warn':
- if not np.all(np.ravel(value_, order='F')==np.ravel(value_, order='K')):
- raise Exception("Ambiguity in API detected.\n"
- "Are you making an acados model from scrach? Add api='new' to constraints_set and carry on.\n"
- "Are you seeing this error suddenly in previously running code? Read on.\n"
- " You are relying on a now-fixed bug in constraints_set for field '{}'.\n".format(field_) +
- " acados_template now correctly passes on any matrices to acados in column major format.\n" +
- " Two options to fix this error: \n" +
- " * Add api='old' to constraints_set to restore old incorrect behaviour\n" +
- " * Add api='new' to constraints_set and remove any unnatural manipulation of the value argument " +
- "such as non-mathematical transposes, reshaping, casting to fortran order, etc... " +
- "If there is no such manipulation, then you have probably been getting an incorrect solution before.")
- # Get elements in column major order
- value_ = np.ravel(value_, order='F')
- elif api=='new':
- # Get elements in column major order
- value_ = np.ravel(value_, order='F')
- else:
- raise Exception("Unknown api: '{}'".format(api))
- if value_shape != expected_shape:
- raise Exception('AcadosOcpSolver.constraints_set(): mismatching dimension' \
- ' for field "{}" with dimension {} (you have {})'.format(field_, expected_shape, value_shape))
-
- value_data = cast(value_.ctypes.data, POINTER(c_double))
- value_data_p = cast((value_data), c_void_p)
-
- self.shared_lib.ocp_nlp_constraints_model_set_slice.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_int, c_char_p, c_void_p, c_int]
- self.shared_lib.ocp_nlp_constraints_model_set_slice(self.nlp_config, \
- self.nlp_dims, self.nlp_in, start_stage, end_stage, field, value_data_p, dim)
- return
-
- # Note: this function should not be used anymore, better use cost_set, constraints_set
- def set(self, stage_, field_, value_):
- """
- Set numerical data inside the solver.
-
- :param stage: integer corresponding to shooting node
- :param field: string in ['x', 'u', 'pi', 'lam', 't', 'p']
-
- .. note:: regarding lam, t: \n
- the inequalities are internally organized in the following order: \n
- [ lbu lbx lg lh lphi ubu ubx ug uh uphi; \n
- lsbu lsbx lsg lsh lsphi usbu usbx usg ush usphi]
-
- .. note:: pi: multipliers for dynamics equality constraints \n
- lam: multipliers for inequalities \n
- t: slack variables corresponding to evaluation of all inequalities (at the solution) \n
- sl: slack variables of soft lower inequality constraints \n
- su: slack variables of soft upper inequality constraints \n
- """
- cost_fields = ['y_ref', 'yref']
- constraints_fields = ['lbx', 'ubx', 'lbu', 'ubu']
- out_fields = ['x', 'u', 'pi', 'lam', 't', 'z']
- mem_fields = ['sl', 'su']
-
- # cast value_ to avoid conversion issues
- if isinstance(value_, (float, int)):
- value_ = np.array([value_])
- value_ = value_.astype(float)
-
- model_name = self.model_name
-
- field = field_
- field = field.encode('utf-8')
-
- stage = c_int(stage_)
-
- # treat parameters separately
- if field_ == 'p':
- getattr(self.shared_lib, f"{model_name}_acados_update_params").argtypes = [c_void_p, c_int, POINTER(c_double)]
- getattr(self.shared_lib, f"{model_name}_acados_update_params").restype = c_int
-
- value_data = cast(value_.ctypes.data, POINTER(c_double))
-
- assert getattr(self.shared_lib, f"{model_name}_acados_update_params")(self.capsule, stage, value_data, value_.shape[0])==0
- else:
- if field_ not in constraints_fields + cost_fields + out_fields + mem_fields:
- raise Exception("AcadosOcpSolver.set(): {} is not a valid argument.\
- \nPossible values are {}. Exiting.".format(field, \
- constraints_fields + cost_fields + out_fields + ['p']))
-
- self.shared_lib.ocp_nlp_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p]
- self.shared_lib.ocp_nlp_dims_get_from_attr.restype = c_int
-
- dims = self.shared_lib.ocp_nlp_dims_get_from_attr(self.nlp_config, \
- self.nlp_dims, self.nlp_out, stage_, field)
-
- if value_.shape[0] != dims:
- msg = 'AcadosOcpSolver.set(): mismatching dimension for field "{}" '.format(field_)
- msg += 'with dimension {} (you have {})'.format(dims, value_.shape)
- raise Exception(msg)
-
- value_data = cast(value_.ctypes.data, POINTER(c_double))
- value_data_p = cast((value_data), c_void_p)
-
- if field_ in constraints_fields:
- self.shared_lib.ocp_nlp_constraints_model_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_constraints_model_set(self.nlp_config, \
- self.nlp_dims, self.nlp_in, stage, field, value_data_p)
- elif field_ in cost_fields:
- self.shared_lib.ocp_nlp_cost_model_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_cost_model_set(self.nlp_config, \
- self.nlp_dims, self.nlp_in, stage, field, value_data_p)
- elif field_ in out_fields:
- self.shared_lib.ocp_nlp_out_set.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_out_set(self.nlp_config, \
- self.nlp_dims, self.nlp_out, stage, field, value_data_p)
- elif field_ in mem_fields:
- self.shared_lib.ocp_nlp_set.argtypes = \
- [c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_set(self.nlp_config, \
- self.nlp_solver, stage, field, value_data_p)
- return
-
-
- def get_slice(self, start_stage_, end_stage_, field_):
- """
- Get the last solution of the solver:
-
- :param start_stage: integer corresponding to shooting node that indicates start of slice
- :param end_stage: integer corresponding to shooting node that indicates end of slice
- :param field: string in ['x', 'u', 'z', 'pi', 'lam', 't', 'sl', 'su',]
-
- .. note:: regarding lam, t: \n
- the inequalities are internally organized in the following order: \n
- [ lbu lbx lg lh lphi ubu ubx ug uh uphi; \n
- lsbu lsbx lsg lsh lsphi usbu usbx usg ush usphi]
-
- .. note:: pi: multipliers for dynamics equality constraints \n
- lam: multipliers for inequalities \n
- t: slack variables corresponding to evaluation of all inequalities (at the solution) \n
- sl: slack variables of soft lower inequality constraints \n
- su: slack variables of soft upper inequality constraints \n
- """
- out_fields = ['x', 'u', 'z', 'pi', 'lam', 't']
- mem_fields = ['sl', 'su']
- field = field_
- field = field.encode('utf-8')
-
- if (field_ not in out_fields + mem_fields):
- raise Exception('AcadosOcpSolver.get_slice(): {} is an invalid argument.\
- \n Possible values are {}. Exiting.'.format(field_, out_fields))
-
- if not isinstance(start_stage_, int):
- raise Exception('AcadosOcpSolver.get_slice(): stage index must be Integer.')
-
- if not isinstance(end_stage_, int):
- raise Exception('AcadosOcpSolver.get_slice(): stage index must be Integer.')
-
- if start_stage_ >= end_stage_:
- raise Exception('AcadosOcpSolver.get_slice(): end stage index must be larger than start stage index')
-
- if start_stage_ < 0 or end_stage_ > self.N + 1:
- raise Exception('AcadosOcpSolver.get_slice(): stage index must be in [0, N], got: {}.'.format(self.N))
- self.shared_lib.ocp_nlp_dims_get_from_attr.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p]
- self.shared_lib.ocp_nlp_dims_get_from_attr.restype = c_int
-
- dims = self.shared_lib.ocp_nlp_dims_get_from_attr(self.nlp_config, \
- self.nlp_dims, self.nlp_out, start_stage_, field)
-
- out = np.ascontiguousarray(np.zeros((end_stage_ - start_stage_, dims)), dtype=np.float64)
- out_data = cast(out.ctypes.data, POINTER(c_double))
-
- if (field_ in out_fields):
- self.shared_lib.ocp_nlp_out_get_slice.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_out_get_slice(self.nlp_config, \
- self.nlp_dims, self.nlp_out, start_stage_, end_stage_, field, out_data)
- elif field_ in mem_fields:
- self.shared_lib.ocp_nlp_get_at_stage.argtypes = \
- [c_void_p, c_void_p, c_void_p, c_int, c_char_p, c_void_p]
- self.shared_lib.ocp_nlp_get_at_stage(self.nlp_config, \
- self.nlp_dims, self.nlp_solver, start_stage_, end_stage_, field, out_data)
-
- return out
-
- def get_cost(self):
- """
- Returns the cost value of the current solution.
- """
- # compute cost internally
- self.shared_lib.ocp_nlp_eval_cost.argtypes = [c_void_p, c_void_p, c_void_p]
- self.shared_lib.ocp_nlp_eval_cost(self.nlp_solver, self.nlp_in, self.nlp_out)
-
- # create output array
- out = np.ascontiguousarray(np.zeros((1,)), dtype=np.float64)
- out_data = cast(out.ctypes.data, POINTER(c_double))
-
- # call getter
- self.shared_lib.ocp_nlp_get.argtypes = [c_void_p, c_void_p, c_char_p, c_void_p]
-
- field = "cost_value".encode('utf-8')
- self.shared_lib.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_data)
-
- return out[0]
diff --git a/pyextra/acados_template/acados_ocp_solver_pyx.pyx b/pyextra/acados_template/acados_ocp_solver_pyx.pyx
index 9bcf5bb46a..fe7fa8425a 100644
--- a/pyextra/acados_template/acados_ocp_solver_pyx.pyx
+++ b/pyextra/acados_template/acados_ocp_solver_pyx.pyx
@@ -39,21 +39,19 @@ cimport cython
from libc cimport string
cimport acados_solver_common
+# TODO: make this import more clear? it is not a general solver, but problem specific.
cimport acados_solver
cimport numpy as cnp
import os
+from datetime import datetime
import numpy as np
-cdef class AcadosOcpSolverFast:
+cdef class AcadosOcpSolverCython:
"""
Class to interact with the acados ocp solver C object.
-
- :param acados_ocp: type AcadosOcp - description of the OCP for acados
- :param json_file: name for the json file used to render the templated code - default: acados_ocp_nlp.json
- :param simulink_opts: Options to configure Simulink S-function blocks, mainly to activate possible Inputs and Outputs
"""
cdef acados_solver.nlp_solver_capsule *capsule
@@ -61,19 +59,26 @@ cdef class AcadosOcpSolverFast:
cdef acados_solver_common.ocp_nlp_dims *nlp_dims
cdef acados_solver_common.ocp_nlp_config *nlp_config
cdef acados_solver_common.ocp_nlp_out *nlp_out
+ cdef acados_solver_common.ocp_nlp_out *sens_out
cdef acados_solver_common.ocp_nlp_in *nlp_in
cdef acados_solver_common.ocp_nlp_solver *nlp_solver
+ cdef int status
+ cdef bint solver_created
+
cdef str model_name
cdef int N
- cdef bint solver_created
- def __cinit__(self, str model_name, int N, str code_export_dir):
- self.model_name = model_name
- self.N = N
+ cdef str nlp_solver_type
+
+ def __cinit__(self, model_name, nlp_solver_type, N):
self.solver_created = False
+ self.N = N
+ self.model_name = model_name
+ self.nlp_solver_type = nlp_solver_type
+
# create capsule
self.capsule = acados_solver.acados_create_capsule()
@@ -81,11 +86,21 @@ cdef class AcadosOcpSolverFast:
assert acados_solver.acados_create(self.capsule) == 0
self.solver_created = True
+ # get pointers solver
+ self.__get_pointers_solver()
+ self.status = 0
+
+
+ def __get_pointers_solver(self):
+ """
+ Private function to get the pointers for solver
+ """
# get pointers solver
self.nlp_opts = acados_solver.acados_get_nlp_opts(self.capsule)
self.nlp_dims = acados_solver.acados_get_nlp_dims(self.capsule)
self.nlp_config = acados_solver.acados_get_nlp_config(self.capsule)
self.nlp_out = acados_solver.acados_get_nlp_out(self.capsule)
+ self.sens_out = acados_solver.acados_get_sens_out(self.capsule)
self.nlp_in = acados_solver.acados_get_nlp_in(self.capsule)
self.nlp_solver = acados_solver.acados_get_nlp_solver(self.capsule)
@@ -97,17 +112,121 @@ cdef class AcadosOcpSolverFast:
return acados_solver.acados_solve(self.capsule)
+ def reset(self):
+ """
+ Sets current iterate to all zeros.
+ """
+ return acados_solver.acados_reset(self.capsule)
+
+
def set_new_time_steps(self, new_time_steps):
"""
- Set new time steps before solving. Only reload library without code generation but with new time steps.
+ Set new time steps.
+ Recreates the solver if N changes.
- :param new_time_steps: vector of new time steps for the solver
+ :param new_time_steps: 1 dimensional np array of new time steps for the solver
.. note:: This allows for different use-cases: either set a new size of time-steps or a new distribution of
the shooting nodes without changing the number, e.g., to reach a different final time. Both cases
do not require a new code export and compilation.
"""
- raise NotImplementedError()
+
+ raise NotImplementedError("AcadosOcpSolverCython: does not support set_new_time_steps() since it is only a prototyping feature")
+ # # unlikely but still possible
+ # if not self.solver_created:
+ # raise Exception('Solver was not yet created!')
+
+ # ## check if time steps really changed in value
+ # # get time steps
+ # cdef cnp.ndarray[cnp.float64_t, ndim=1] old_time_steps = np.ascontiguousarray(np.zeros((self.N,)), dtype=np.float64)
+ # assert acados_solver.acados_get_time_steps(self.capsule, self.N, old_time_steps.data)
+
+ # if np.array_equal(old_time_steps, new_time_steps):
+ # return
+
+ # N = new_time_steps.size
+ # cdef cnp.ndarray[cnp.float64_t, ndim=1] value = np.ascontiguousarray(new_time_steps, dtype=np.float64)
+
+ # # check if recreation of acados is necessary (no need to recreate acados if sizes are identical)
+ # if len(old_time_steps) == N:
+ # assert acados_solver.acados_update_time_steps(self.capsule, N, value.data) == 0
+
+ # else: # recreate the solver with the new time steps
+ # self.solver_created = False
+
+ # # delete old memory (analog to __del__)
+ # acados_solver.acados_free(self.capsule)
+
+ # # create solver with new time steps
+ # assert acados_solver.acados_create_with_discretization(self.capsule, N, value.data) == 0
+
+ # self.solver_created = True
+
+ # # get pointers solver
+ # self.__get_pointers_solver()
+
+ # # store time_steps, N
+ # self.time_steps = new_time_steps
+ # self.N = N
+
+
+ def update_qp_solver_cond_N(self, qp_solver_cond_N: int):
+ """
+ Recreate solver with new value `qp_solver_cond_N` with a partial condensing QP solver.
+ This function is relevant for code reuse, i.e., if either `set_new_time_steps(...)` is used or
+ the influence of a different `qp_solver_cond_N` is studied without code export and compilation.
+ :param qp_solver_cond_N: new number of condensing stages for the solver
+
+ .. note:: This function can only be used in combination with a partial condensing QP solver.
+
+ .. note:: After `set_new_time_steps(...)` is used and depending on the new number of time steps it might be
+ necessary to change `qp_solver_cond_N` as well (using this function), i.e., typically
+ `qp_solver_cond_N < N`.
+ """
+ raise NotImplementedError("AcadosOcpSolverCython: does not support update_qp_solver_cond_N() since it is only a prototyping feature")
+
+ # # unlikely but still possible
+ # if not self.solver_created:
+ # raise Exception('Solver was not yet created!')
+ # if self.N < qp_solver_cond_N:
+ # raise Exception('Setting qp_solver_cond_N to be larger than N does not work!')
+ # if self.qp_solver_cond_N != qp_solver_cond_N:
+ # self.solver_created = False
+
+ # # recreate the solver
+ # acados_solver.acados_update_qp_solver_cond_N(self.capsule, qp_solver_cond_N)
+
+ # # store the new value
+ # self.qp_solver_cond_N = qp_solver_cond_N
+ # self.solver_created = True
+
+ # # get pointers solver
+ # self.__get_pointers_solver()
+
+
+ def eval_param_sens(self, index, stage=0, field="ex"):
+ """
+ Calculate the sensitivity of the curent solution with respect to the initial state component of index
+
+ :param index: integer corresponding to initial state index in range(nx)
+ """
+
+ field_ = field
+ field = field_.encode('utf-8')
+
+ # checks
+ if not isinstance(index, int):
+ raise Exception('AcadosOcpSolverCython.eval_param_sens(): index must be Integer.')
+
+ cdef int nx = acados_solver_common.ocp_nlp_dims_get_from_attr(self.nlp_config, self.nlp_dims, self.nlp_out, 0, "x".encode('utf-8'))
+
+ if index < 0 or index > nx:
+ raise Exception(f'AcadosOcpSolverCython.eval_param_sens(): index must be in [0, nx-1], got: {index}.')
+
+ # actual eval_param
+ acados_solver_common.ocp_nlp_eval_param_sens(self.nlp_solver, field, stage, index, self.sens_out)
+
+ return
def get(self, int stage, str field_):
@@ -133,14 +252,14 @@ cdef class AcadosOcpSolverFast:
field = field_.encode('utf-8')
if field_ not in out_fields:
- raise Exception('AcadosOcpSolver.get(): {} is an invalid argument.\
+ raise Exception('AcadosOcpSolverCython.get(): {} is an invalid argument.\
\n Possible values are {}. Exiting.'.format(field_, out_fields))
if stage < 0 or stage > self.N:
- raise Exception('AcadosOcpSolver.get(): stage index must be in [0, N], got: {}.'.format(self.N))
+ raise Exception('AcadosOcpSolverCython.get(): stage index must be in [0, N], got: {}.'.format(self.N))
if stage == self.N and field_ == 'pi':
- raise Exception('AcadosOcpSolver.get(): field {} does not exist at final stage {}.'\
+ raise Exception('AcadosOcpSolverCython.get(): field {} does not exist at final stage {}.'\
.format(field_, stage))
cdef int dims = acados_solver_common.ocp_nlp_dims_get_from_attr(self.nlp_config,
@@ -168,7 +287,7 @@ cdef class AcadosOcpSolverFast:
- qp_res_ineq: residual wrt inequality constraints (constraints) of the last QP solution
- qp_res_comp: residual wrt complementarity conditions of the last QP solution
"""
- raise NotImplementedError()
+ acados_solver.acados_print_stats(self.capsule)
def store_iterate(self, filename='', overwrite=False):
@@ -178,14 +297,50 @@ cdef class AcadosOcpSolverFast:
:param filename: if not set, use model_name + timestamp + '.json'
:param overwrite: if false and filename exists add timestamp to filename
"""
- raise NotImplementedError()
+ import json
+ if filename == '':
+ filename += self.model_name + '_' + 'iterate' + '.json'
+
+ if not overwrite:
+ # append timestamp
+ if os.path.isfile(filename):
+ filename = filename[:-5]
+ filename += datetime.utcnow().strftime('%Y-%m-%d-%H:%M:%S.%f') + '.json'
+
+ # get iterate:
+ solution = dict()
+
+ for i in range(self.N+1):
+ solution['x_'+str(i)] = self.get(i,'x')
+ solution['u_'+str(i)] = self.get(i,'u')
+ solution['z_'+str(i)] = self.get(i,'z')
+ solution['lam_'+str(i)] = self.get(i,'lam')
+ solution['t_'+str(i)] = self.get(i, 't')
+ solution['sl_'+str(i)] = self.get(i, 'sl')
+ solution['su_'+str(i)] = self.get(i, 'su')
+ for i in range(self.N):
+ solution['pi_'+str(i)] = self.get(i,'pi')
+
+ # save
+ with open(filename, 'w') as f:
+ json.dump(solution, f, default=lambda x: x.tolist(), indent=4, sort_keys=True)
+ print("stored current iterate in ", os.path.join(os.getcwd(), filename))
def load_iterate(self, filename):
"""
Loads the iterate stored in json file with filename into the ocp solver.
"""
- raise NotImplementedError()
+ import json
+ if not os.path.isfile(filename):
+ raise Exception('load_iterate: failed, file does not exist: ' + os.path.join(os.getcwd(), filename))
+
+ with open(filename, 'r') as f:
+ solution = json.load(f)
+
+ for key in solution.keys():
+ (field, stage) = key.split('_')
+ self.set(int(stage), field, np.array(solution[key]))
def get_stats(self, field_):
@@ -193,8 +348,97 @@ cdef class AcadosOcpSolverFast:
Get the information of the last solver call.
:param field: string in ['statistics', 'time_tot', 'time_lin', 'time_sim', 'time_sim_ad', 'time_sim_la', 'time_qp', 'time_qp_solver_call', 'time_reg', 'sqp_iter']
+ Available fileds:
+ - time_tot: total CPU time previous call
+ - time_lin: CPU time for linearization
+ - time_sim: CPU time for integrator
+ - time_sim_ad: CPU time for integrator contribution of external function calls
+ - time_sim_la: CPU time for integrator contribution of linear algebra
+ - time_qp: CPU time qp solution
+ - time_qp_solver_call: CPU time inside qp solver (without converting the QP)
+ - time_qp_xcond: time_glob: CPU time globalization
+ - time_solution_sensitivities: CPU time for previous call to eval_param_sens
+ - time_reg: CPU time regularization
+ - sqp_iter: number of SQP iterations
+ - qp_iter: vector of QP iterations for last SQP call
+ - statistics: table with info about last iteration
+ - stat_m: number of rows in statistics matrix
+ - stat_n: number of columns in statistics matrix
+ - residuals: residuals of last iterate
+ - alpha: step sizes of SQP iterations
"""
- raise NotImplementedError()
+
+ double_fields = ['time_tot',
+ 'time_lin',
+ 'time_sim',
+ 'time_sim_ad',
+ 'time_sim_la',
+ 'time_qp',
+ 'time_qp_solver_call',
+ 'time_qp_xcond',
+ 'time_glob',
+ 'time_solution_sensitivities',
+ 'time_reg'
+ ]
+ fields = double_fields + [
+ 'sqp_iter',
+ 'qp_iter',
+ 'statistics',
+ 'stat_m',
+ 'stat_n',
+ 'residuals',
+ 'alpha',
+ ]
+ field = field_.encode('utf-8')
+
+ if field_ in ['sqp_iter', 'stat_m', 'stat_n']:
+ return self.__get_stat_int(field)
+
+ elif field_ in double_fields:
+ return self.__get_stat_double(field)
+
+ elif field_ == 'statistics':
+ sqp_iter = self.get_stats("sqp_iter")
+ stat_m = self.get_stats("stat_m")
+ stat_n = self.get_stats("stat_n")
+ min_size = min([stat_m, sqp_iter+1])
+ return self.__get_stat_matrix(field, stat_n+1, min_size)
+
+ elif field_ == 'qp_iter':
+ full_stats = self.get_stats('statistics')
+ if self.nlp_solver_type == 'SQP':
+ return full_stats[6, :]
+ elif self.nlp_solver_type == 'SQP_RTI':
+ return full_stats[2, :]
+
+ elif field_ == 'alpha':
+ full_stats = self.get_stats('statistics')
+ if self.nlp_solver_type == 'SQP':
+ return full_stats[7, :]
+ else: # self.nlp_solver_type == 'SQP_RTI':
+ raise Exception("alpha values are not available for SQP_RTI")
+
+ elif field_ == 'residuals':
+ return self.get_residuals()
+
+ else:
+ raise NotImplementedError("TODO!")
+
+
+ def __get_stat_int(self, field):
+ cdef int out
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, &out)
+ return out
+
+ def __get_stat_double(self, field):
+ cdef cnp.ndarray[cnp.float64_t, ndim=1] out = np.zeros((1,))
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out.data)
+ return out
+
+ def __get_stat_matrix(self, field, n, m):
+ cdef cnp.ndarray[cnp.float64_t, ndim=2] out_mat = np.ascontiguousarray(np.zeros((n, m)), dtype=np.float64)
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, out_mat.data)
+ return out_mat
def get_cost(self):
@@ -213,11 +457,35 @@ cdef class AcadosOcpSolverFast:
return out
- def get_residuals(self):
+ def get_residuals(self, recompute=False):
"""
Returns an array of the form [res_stat, res_eq, res_ineq, res_comp].
"""
- raise NotImplementedError()
+ # compute residuals if RTI
+ if self.nlp_solver_type == 'SQP_RTI' or recompute:
+ acados_solver_common.ocp_nlp_eval_residuals(self.nlp_solver, self.nlp_in, self.nlp_out)
+
+ # create output array
+ cdef cnp.ndarray[cnp.float64_t, ndim=1] out = np.ascontiguousarray(np.zeros((4,), dtype=np.float64))
+ cdef double double_value
+
+ field = "res_stat".encode('utf-8')
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, &double_value)
+ out[0] = double_value
+
+ field = "res_eq".encode('utf-8')
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, &double_value)
+ out[1] = double_value
+
+ field = "res_ineq".encode('utf-8')
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, &double_value)
+ out[2] = double_value
+
+ field = "res_comp".encode('utf-8')
+ acados_solver_common.ocp_nlp_get(self.nlp_config, self.nlp_solver, field, &double_value)
+ out[3] = double_value
+
+ return out
# Note: this function should not be used anymore, better use cost_set, constraints_set
@@ -243,18 +511,18 @@ cdef class AcadosOcpSolverFast:
cost_fields = ['y_ref', 'yref']
constraints_fields = ['lbx', 'ubx', 'lbu', 'ubu']
out_fields = ['x', 'u', 'pi', 'lam', 't', 'z', 'sl', 'su']
+ mem_fields = ['xdot_guess', 'z_guess']
field = field_.encode('utf-8')
- cdef double[::1] value
+ cdef cnp.ndarray[cnp.float64_t, ndim=1] value = np.ascontiguousarray(value_, dtype=np.float64)
# treat parameters separately
if field_ == 'p':
- value = np.ascontiguousarray(value_, dtype=np.double)
- assert acados_solver.acados_update_params(self.capsule, stage, &value[0], value.shape[0]) == 0
+ assert acados_solver.acados_update_params(self.capsule, stage, value.data, value.shape[0]) == 0
else:
if field_ not in constraints_fields + cost_fields + out_fields:
- raise Exception("AcadosOcpSolver.set(): {} is not a valid argument.\
+ raise Exception("AcadosOcpSolverCython.set(): {} is not a valid argument.\
\nPossible values are {}. Exiting.".format(field, \
constraints_fields + cost_fields + out_fields + ['p']))
@@ -262,20 +530,22 @@ cdef class AcadosOcpSolverFast:
self.nlp_dims, self.nlp_out, stage, field)
if value_.shape[0] != dims:
- msg = 'AcadosOcpSolver.set(): mismatching dimension for field "{}" '.format(field_)
+ msg = 'AcadosOcpSolverCython.set(): mismatching dimension for field "{}" '.format(field_)
msg += 'with dimension {} (you have {})'.format(dims, value_.shape[0])
raise Exception(msg)
- value = np.ascontiguousarray(value_, dtype=np.double)
if field_ in constraints_fields:
acados_solver_common.ocp_nlp_constraints_model_set(self.nlp_config,
- self.nlp_dims, self.nlp_in, stage, field, &value[0])
+ self.nlp_dims, self.nlp_in, stage, field, value.data)
elif field_ in cost_fields:
acados_solver_common.ocp_nlp_cost_model_set(self.nlp_config,
- self.nlp_dims, self.nlp_in, stage, field, &value[0])
+ self.nlp_dims, self.nlp_in, stage, field, value.data)
elif field_ in out_fields:
acados_solver_common.ocp_nlp_out_set(self.nlp_config,
- self.nlp_dims, self.nlp_out, stage, field, &value[0])
+ self.nlp_dims, self.nlp_out, stage, field, value.data)
+ elif field_ in mem_fields:
+ acados_solver_common.ocp_nlp_set(self.nlp_config, \
+ self.nlp_solver, stage, field, value.data)
def cost_set(self, int stage, str field_, value_):
@@ -304,9 +574,8 @@ cdef class AcadosOcpSolverFast:
value = np.asfortranarray(value_)
if value_shape[0] != dims[0] or value_shape[1] != dims[1]:
- raise Exception('AcadosOcpSolver.cost_set(): mismatching dimension', \
- ' for field "{}" with dimension {} (you have {})'.format( \
- field_, tuple(dims), value_shape))
+ raise Exception('AcadosOcpSolverCython.cost_set(): mismatching dimension' +
+ f' for field "{field_}" at stage {stage} with dimension {tuple(dims)} (you have {value_shape})')
acados_solver_common.ocp_nlp_cost_model_set(self.nlp_config, \
self.nlp_dims, self.nlp_in, stage, field, &value[0][0])
@@ -338,8 +607,8 @@ cdef class AcadosOcpSolverFast:
value = np.asfortranarray(value_)
if value_shape[0] != dims[0] or value_shape[1] != dims[1]:
- raise Exception('AcadosOcpSolver.constraints_set(): mismatching dimension' \
- ' for field "{}" with dimension {} (you have {})'.format(field_, tuple(dims), value_shape))
+ raise Exception(f'AcadosOcpSolverCython.constraints_set(): mismatching dimension' +
+ f' for field "{field_}" at stage {stage} with dimension {tuple(dims)} (you have {value_shape})')
acados_solver_common.ocp_nlp_constraints_model_set(self.nlp_config, \
self.nlp_dims, self.nlp_in, stage, field, &value[0][0])
@@ -361,7 +630,7 @@ cdef class AcadosOcpSolverFast:
acados_solver_common.ocp_nlp_dynamics_dims_get_from_attr(self.nlp_config, self.nlp_dims, self.nlp_out, stage, field, &dims[0])
# create output data
- out = np.zeros((dims[0], dims[1]), order='F', dtype=np.float64)
+ cdef cnp.ndarray[cnp.float64_t, ndim=2] out = np.zeros((dims[0], dims[1]), order='F')
# call getter
acados_solver_common.ocp_nlp_get_at_stage(self.nlp_config, self.nlp_dims, self.nlp_solver, stage, field, out.data)
@@ -373,11 +642,21 @@ cdef class AcadosOcpSolverFast:
"""
Set options of the solver.
- :param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction'
- :param value: of type int, float
+ :param field: string, e.g. 'print_level', 'rti_phase', 'initialize_t_slacks', 'step_length', 'alpha_min', 'alpha_reduction', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0'
+
+ :param value: of type int, float, string
+
+ - qp_tol_stat: QP solver tolerance stationarity
+ - qp_tol_eq: QP solver tolerance equalities
+ - qp_tol_ineq: QP solver tolerance inequalities
+ - qp_tol_comp: QP solver tolerance complementarity
+ - qp_tau_min: for HPIPM QP solvers: minimum value of barrier parameter in HPIPM
+ - qp_mu0: for HPIPM QP solvers: initial value for complementarity slackness
+ - warm_start_first_qp: indicates if first QP in SQP is warm_started
"""
- int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks']
- double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction']
+ int_fields = ['print_level', 'rti_phase', 'initialize_t_slacks', 'qp_warm_start', 'line_search_use_sufficient_descent', 'full_step_dual', 'globalization_use_SOC', 'warm_start_first_qp']
+ double_fields = ['step_length', 'tol_eq', 'tol_stat', 'tol_ineq', 'tol_comp', 'alpha_min', 'alpha_reduction', 'eps_sufficient_descent',
+ 'qp_tol_stat', 'qp_tol_eq', 'qp_tol_ineq', 'qp_tol_comp', 'qp_tau_min', 'qp_mu0']
string_fields = ['globalization']
# encode
@@ -394,10 +673,10 @@ cdef class AcadosOcpSolverFast:
if field_ == 'rti_phase':
if value_ < 0 or value_ > 2:
- raise Exception('AcadosOcpSolver.solve(): argument \'rti_phase\' can '
+ raise Exception('AcadosOcpSolverCython.solve(): argument \'rti_phase\' can '
'take only values 0, 1, 2 for SQP-RTI-type solvers')
- if self.acados_ocp.solver_options.nlp_solver_type != 'SQP_RTI' and value_ > 0:
- raise Exception('AcadosOcpSolver.solve(): argument \'rti_phase\' can '
+ if self.nlp_solver_type != 'SQP_RTI' and value_ > 0:
+ raise Exception('AcadosOcpSolverCython.solve(): argument \'rti_phase\' can '
'take only value 0 for SQP-type solvers')
int_value = value_
@@ -418,7 +697,7 @@ cdef class AcadosOcpSolverFast:
acados_solver_common.ocp_nlp_solver_opts_set(self.nlp_config, self.nlp_opts, field, &string_value[0])
else:
- raise Exception('AcadosOcpSolver.options_set() does not support field {}.'\
+ raise Exception('AcadosOcpSolverCython.options_set() does not support field {}.'\
'\n Possible values are {}.'.format(field_, ', '.join(int_fields + double_fields + string_fields)))
diff --git a/pyextra/acados_template/acados_sim.py b/pyextra/acados_template/acados_sim.py
index d7ad1487dc..93d5f298db 100644
--- a/pyextra/acados_template/acados_sim.py
+++ b/pyextra/acados_template/acados_sim.py
@@ -70,28 +70,28 @@ class AcadosSimDims:
@nx.setter
def nx(self, nx):
- if type(nx) == int and nx > 0:
+ if isinstance(nx, int) and nx > 0:
self.__nx = nx
else:
raise Exception('Invalid nx value, expected positive integer. Exiting.')
@nz.setter
def nz(self, nz):
- if type(nz) == int and nz > -1:
+ if isinstance(nz, int) and nz > -1:
self.__nz = nz
else:
raise Exception('Invalid nz value, expected nonnegative integer. Exiting.')
@nu.setter
def nu(self, nu):
- if type(nu) == int and nu > -1:
+ if isinstance(nu, int) and nu > -1:
self.__nu = nu
else:
raise Exception('Invalid nu value, expected nonnegative integer. Exiting.')
@np.setter
def np(self, np):
- if type(np) == int and np > -1:
+ if isinstance(np, int) and np > -1:
self.__np = np
else:
raise Exception('Invalid np value, expected nonnegative integer. Exiting.')
@@ -294,14 +294,15 @@ class AcadosSim:
self.solver_options = AcadosSimOpts()
"""Solver Options, type :py:class:`acados_template.acados_sim.AcadosSimOpts`"""
- self.acados_include_path = f'{acados_path}/include'
- """Path to acados include directors (set automatically), type: `string`"""
- self.acados_lib_path = f'{acados_path}/lib'
+ self.acados_include_path = os.path.join(acados_path, 'include').replace(os.sep, '/') # the replace part is important on Windows for CMake
+ """Path to acados include directory (set automatically), type: `string`"""
+ self.acados_lib_path = os.path.join(acados_path, 'lib').replace(os.sep, '/') # the replace part is important on Windows for CMake
"""Path to where acados library is located (set automatically), type: `string`"""
self.code_export_directory = 'c_generated_code'
"""Path to where code will be exported. Default: `c_generated_code`."""
+ self.cython_include_dirs = ''
self.__parameter_values = np.array([])
@property
diff --git a/pyextra/acados_template/acados_sim_solver.py b/pyextra/acados_template/acados_sim_solver.py
index 145f90293e..3588dd38cd 100644
--- a/pyextra/acados_template/acados_sim_solver.py
+++ b/pyextra/acados_template/acados_sim_solver.py
@@ -47,6 +47,7 @@ from .acados_ocp import AcadosOcp
from .acados_model import acados_model_strip_casadi_symbolics
from .utils import is_column, render_template, format_class_dict, np_array_to_list,\
make_model_consistent, set_up_imported_gnsf_model, get_python_interface_path
+from .builders import CMakeBuilder
def make_sim_dims_consistent(acados_sim):
@@ -111,7 +112,17 @@ def sim_formulation_json_dump(acados_sim, json_file='acados_sim.json'):
json.dump(sim_json, f, default=np_array_to_list, indent=4, sort_keys=True)
-def sim_render_templates(json_file, model_name, code_export_dir):
+def sim_get_default_cmake_builder() -> CMakeBuilder:
+ """
+ If :py:class:`~acados_template.acados_sim_solver.AcadosSimSolver` is used with `CMake` this function returns a good first setting.
+ :return: default :py:class:`~acados_template.builders.CMakeBuilder`
+ """
+ cmake_builder = CMakeBuilder()
+ cmake_builder.options_on = ['BUILD_ACADOS_SIM_SOLVER_LIB']
+ return cmake_builder
+
+
+def sim_render_templates(json_file, model_name, code_export_dir, cmake_options: CMakeBuilder = None):
# setting up loader and environment
json_path = os.path.join(os.getcwd(), json_file)
@@ -129,9 +140,15 @@ def sim_render_templates(json_file, model_name, code_export_dir):
out_file = f'acados_sim_solver_{model_name}.h'
render_template(in_file, out_file, template_dir, json_path)
- in_file = 'Makefile.in'
- out_file = f'Makefile'
- render_template(in_file, out_file, template_dir, json_path)
+ # Builder
+ if cmake_options is not None:
+ in_file = 'CMakeLists.in.txt'
+ out_file = 'CMakeLists.txt'
+ render_template(in_file, out_file, template_dir, json_path)
+ else:
+ in_file = 'Makefile.in'
+ out_file = 'Makefile'
+ render_template(in_file, out_file, template_dir, json_path)
in_file = 'main_sim.in.c'
out_file = f'main_sim_{model_name}.c'
@@ -161,15 +178,19 @@ def sim_generate_casadi_functions(acados_sim):
elif integrator_type == 'GNSF':
generate_c_code_gnsf(model, opts)
+
class AcadosSimSolver:
"""
Class to interact with the acados integrator C object.
- :param acados_sim: type :py:class:`acados_template.acados_ocp.AcadosOcp` (takes values to generate an instance :py:class:`acados_template.acados_sim.AcadosSim`) or :py:class:`acados_template.acados_sim.AcadosSim`
- :param json_file: Default: 'acados_sim.json'
- :param build: Default: True
+ :param acados_sim: type :py:class:`~acados_template.acados_ocp.AcadosOcp` (takes values to generate an instance :py:class:`~acados_template.acados_sim.AcadosSim`) or :py:class:`~acados_template.acados_sim.AcadosSim`
+ :param json_file: Default: 'acados_sim.json'
+ :param build: Default: True
+ :param cmake_builder: type :py:class:`~acados_template.utils.CMakeBuilder` generate a `CMakeLists.txt` and use
+ the `CMake` pipeline instead of a `Makefile` (`CMake` seems to be the better option in conjunction with
+ `MS Visual Studio`); default: `None`
"""
- def __init__(self, acados_sim_, json_file='acados_sim.json', build=True):
+ def __init__(self, acados_sim_, json_file='acados_sim.json', build=True, cmake_builder: CMakeBuilder = None):
self.solver_created = False
@@ -203,21 +224,50 @@ class AcadosSimSolver:
code_export_dir = acados_sim.code_export_directory
if build:
# render templates
- sim_render_templates(json_file, model_name, code_export_dir)
+ sim_render_templates(json_file, model_name, code_export_dir, cmake_builder)
- ## Compile solver
+ # Compile solver
cwd = os.getcwd()
+ code_export_dir = os.path.abspath(code_export_dir)
os.chdir(code_export_dir)
- os.system('make sim_shared_lib')
+ if cmake_builder is not None:
+ cmake_builder.exec(code_export_dir)
+ else:
+ os.system('make sim_shared_lib')
os.chdir(cwd)
self.sim_struct = acados_sim
model_name = self.sim_struct.model.name
self.model_name = model_name
+ # Load acados library to avoid unloading the library.
+ # This is necessary if acados was compiled with OpenMP, since the OpenMP threads can't be destroyed.
+ # Unloading a library which uses OpenMP results in a segfault (on any platform?).
+ # see [https://stackoverflow.com/questions/34439956/vc-crash-when-freeing-a-dll-built-with-openmp]
+ # or [https://python.hotexamples.com/examples/_ctypes/-/dlclose/python-dlclose-function-examples.html]
+ libacados_name = 'libacados.so'
+ libacados_filepath = os.path.join(acados_sim.acados_lib_path, libacados_name)
+ self.__acados_lib = CDLL(libacados_filepath)
+ # find out if acados was compiled with OpenMP
+ try:
+ self.__acados_lib_uses_omp = getattr(self.__acados_lib, 'omp_get_thread_num') is not None
+ except AttributeError as e:
+ self.__acados_lib_uses_omp = False
+ if self.__acados_lib_uses_omp:
+ print('acados was compiled with OpenMP.')
+ else:
+ print('acados was compiled without OpenMP.')
+
# Ctypes
- shared_lib = f'{code_export_dir}/libacados_sim_solver_{model_name}.so'
- self.shared_lib = CDLL(shared_lib)
+ lib_prefix = 'lib'
+ lib_ext = '.so'
+ if os.name == 'nt':
+ lib_prefix = ''
+ lib_ext = ''
+ self.shared_lib_name = os.path.join(code_export_dir, f'{lib_prefix}acados_sim_solver_{model_name}{lib_ext}')
+ print(f'self.shared_lib_name = "{self.shared_lib_name}"')
+
+ self.shared_lib = CDLL(self.shared_lib_name)
# create capsule
diff --git a/pyextra/acados_template/acados_solver_common.pxd b/pyextra/acados_template/acados_solver_common.pxd
index 9314802e61..fedd7190d9 100644
--- a/pyextra/acados_template/acados_solver_common.pxd
+++ b/pyextra/acados_template/acados_solver_common.pxd
@@ -95,6 +95,7 @@ cdef extern from "acados_c/ocp_nlp_interface.h":
# solver
void ocp_nlp_eval_residuals(ocp_nlp_solver *solver, ocp_nlp_in *nlp_in, ocp_nlp_out *nlp_out)
+ void ocp_nlp_eval_param_sens(ocp_nlp_solver *solver, char *field, int stage, int index, ocp_nlp_out *sens_nlp_out)
void ocp_nlp_eval_cost(ocp_nlp_solver *solver, ocp_nlp_in *nlp_in_, ocp_nlp_out *nlp_out)
# get/set
diff --git a/pyextra/acados_template/builders.py b/pyextra/acados_template/builders.py
new file mode 100644
index 0000000000..f595033ceb
--- /dev/null
+++ b/pyextra/acados_template/builders.py
@@ -0,0 +1,116 @@
+# -*- coding: future_fstrings -*-
+#
+# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
+# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
+# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
+# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
+#
+# This file is part of acados.
+#
+# The 2-Clause BSD License
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.;
+#
+
+import os
+import sys
+from subprocess import call
+
+
+class CMakeBuilder:
+ """
+ Class to work with the `CMake` build system.
+ """
+ def __init__(self):
+ self._source_dir = None # private source directory, this is set to code_export_dir
+ self.build_dir = 'build'
+ self._build_dir = None # private build directory, usually rendered to abspath(build_dir)
+ self.generator = None
+ """Defines the generator, options can be found via `cmake --help` under 'Generator'. Type: string. Linux default 'Unix Makefiles', Windows 'Visual Studio 15 2017 Win64'; default value: `None`."""
+ # set something for Windows
+ if os.name == 'nt':
+ self.generator = 'Visual Studio 15 2017 Win64'
+ self.build_targets = None
+ """A comma-separated list of the build targets, if `None` then all targets will be build; type: List of strings; default: `None`."""
+ self.options_on = None
+ """List of strings as CMake options which are translated to '-D Opt[0]=ON -D Opt[1]=ON ...'; default: `None`."""
+
+ # Generate the command string for handling the cmake command.
+ def get_cmd1_cmake(self):
+ defines_str = ''
+ if self.options_on is not None:
+ defines_arr = [f' -D{opt}=ON' for opt in self.options_on]
+ defines_str = ' '.join(defines_arr)
+ generator_str = ''
+ if self.generator is not None:
+ generator_str = f' -G"{self.generator}"'
+ return f'cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="{self._source_dir}"{defines_str}{generator_str} -Wdev -S"{self._source_dir}" -B"{self._build_dir}"'
+
+ # Generate the command string for handling the build.
+ def get_cmd2_build(self):
+ import multiprocessing
+ cmd = f'cmake --build "{self._build_dir}" --config Release -j{multiprocessing.cpu_count()}'
+ if self.build_targets is not None:
+ cmd += f' -t {self.build_targets}'
+ return cmd
+
+ # Generate the command string for handling the install command.
+ def get_cmd3_install(self):
+ return f'cmake --install "{self._build_dir}"'
+
+ def exec(self, code_export_directory):
+ """
+ Execute the compilation using `CMake` with the given settings.
+ :param code_export_directory: must be the absolute path to the directory where the code was exported to
+ """
+ if(os.path.isabs(code_export_directory) is False):
+ print(f'(W) the code export directory "{code_export_directory}" is not an absolute path!')
+ self._source_dir = code_export_directory
+ self._build_dir = os.path.abspath(self.build_dir)
+ try:
+ os.mkdir(self._build_dir)
+ except FileExistsError as e:
+ pass
+
+ try:
+ os.chdir(self._build_dir)
+ cmd_str = self.get_cmd1_cmake()
+ print(f'call("{cmd_str})"')
+ retcode = call(cmd_str, shell=True)
+ if retcode != 0:
+ raise RuntimeError(f'CMake command "{cmd_str}" was terminated by signal {retcode}')
+ cmd_str = self.get_cmd2_build()
+ print(f'call("{cmd_str}")')
+ retcode = call(cmd_str, shell=True)
+ if retcode != 0:
+ raise RuntimeError(f'Build command "{cmd_str}" was terminated by signal {retcode}')
+ cmd_str = self.get_cmd3_install()
+ print(f'call("{cmd_str}")')
+ retcode = call(cmd_str, shell=True)
+ if retcode != 0:
+ raise RuntimeError(f'Install command "{cmd_str}" was terminated by signal {retcode}')
+ except OSError as e:
+ print("Execution failed:", e, file=sys.stderr)
+ except Exception as e:
+ print("Execution failed:", e, file=sys.stderr)
+ exit(1)
diff --git a/pyextra/acados_template/c_templates_tera/CMakeLists.in.txt b/pyextra/acados_template/c_templates_tera/CMakeLists.in.txt
new file mode 100644
index 0000000000..3d6483b5d2
--- /dev/null
+++ b/pyextra/acados_template/c_templates_tera/CMakeLists.in.txt
@@ -0,0 +1,374 @@
+#
+# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
+# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
+# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
+# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
+#
+# This file is part of acados.
+#
+# The 2-Clause BSD License
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.;
+#
+
+{%- if solver_options.qp_solver %}
+ {%- set qp_solver = solver_options.qp_solver %}
+{%- else %}
+ {%- set qp_solver = "FULL_CONDENSING_HPIPM" %}
+{%- endif %}
+
+{%- if solver_options.hessian_approx %}
+ {%- set hessian_approx = solver_options.hessian_approx %}
+{%- elif solver_options.sens_hess %}
+ {%- set hessian_approx = "EXACT" %}
+{%- else %}
+ {%- set hessian_approx = "GAUSS_NEWTON" %}
+{%- endif %}
+
+{%- if constraints.constr_type %}
+ {%- set constr_type = constraints.constr_type %}
+{%- else %}
+ {%- set constr_type = "NONE" %}
+{%- endif %}
+
+{%- if constraints.constr_type_e %}
+ {%- set constr_type_e = constraints.constr_type_e %}
+{%- else %}
+ {%- set constr_type_e = "NONE" %}
+{%- endif %}
+
+{%- if cost.cost_type %}
+ {%- set cost_type = cost.cost_type %}
+{%- else %}
+ {%- set cost_type = "NONE" %}
+{%- endif %}
+
+{%- if cost.cost_type_e %}
+ {%- set cost_type_e = cost.cost_type_e %}
+{%- else %}
+ {%- set cost_type_e = "NONE" %}
+{%- endif %}
+
+{%- if cost.cost_type_0 %}
+ {%- set cost_type_0 = cost.cost_type_0 %}
+{%- else %}
+ {%- set cost_type_0 = "NONE" %}
+{%- endif %}
+
+{%- if dims.nh %}
+ {%- set dims_nh = dims.nh %}
+{%- else %}
+ {%- set dims_nh = 0 %}
+{%- endif %}
+
+{%- if dims.nphi %}
+ {%- set dims_nphi = dims.nphi %}
+{%- else %}
+ {%- set dims_nphi = 0 %}
+{%- endif %}
+
+{%- if dims.nh_e %}
+ {%- set dims_nh_e = dims.nh_e %}
+{%- else %}
+ {%- set dims_nh_e = 0 %}
+{%- endif %}
+
+{%- if dims.nphi_e %}
+ {%- set dims_nphi_e = dims.nphi_e %}
+{%- else %}
+ {%- set dims_nphi_e = 0 %}
+{%- endif %}
+
+{%- if solver_options.model_external_shared_lib_dir %}
+ {%- set model_external_shared_lib_dir = solver_options.model_external_shared_lib_dir %}
+{%- endif %}
+
+{%- if solver_options.model_external_shared_lib_name %}
+ {%- set model_external_shared_lib_name = solver_options.model_external_shared_lib_name %}
+{%- endif %}
+
+{#- control operator #}
+{%- if os and os == "pc" %}
+ {%- set control = "&" %}
+{%- else %}
+ {%- set control = ";" %}
+{%- endif %}
+
+{%- if acados_link_libs and os and os == "pc" %}{# acados linking libraries and flags #}
+ {%- set link_libs = acados_link_libs.qpoases ~ " " ~ acados_link_libs.hpmpc ~ " " ~ acados_link_libs.osqp -%}
+ {%- set openmp_flag = acados_link_libs.openmp %}
+{%- else %}
+ {%- set openmp_flag = " " %}
+ {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
+ {%- set link_libs = "-lqpOASES_e" %}
+ {%- else %}
+ {%- set link_libs = "" %}
+ {%- endif %}
+{%- endif %}
+
+cmake_minimum_required(VERSION 3.10)
+
+project({{ model.name }})
+
+# build options.
+option(BUILD_ACADOS_SOLVER_LIB "Should the solver library acados_solver_{{ model.name }} be build?" OFF)
+option(BUILD_ACADOS_OCP_SOLVER_LIB "Should the OCP solver library acados_ocp_solver_{{ model.name }} be build?" OFF)
+option(BUILD_EXAMPLE "Should the example main_{{ model.name }} be build?" OFF)
+{%- if solver_options.integrator_type != "DISCRETE" %}
+option(BUILD_SIM_EXAMPLE "Should the simulation example main_sim_{{ model.name }} be build?" OFF)
+option(BUILD_ACADOS_SIM_SOLVER_LIB "Should the simulation solver library acados_sim_solver_{{ model.name }} be build?" OFF)
+{%- endif %}
+
+# object target names
+set(MODEL_OBJ model_{{ model.name }})
+set(OCP_OBJ ocp_{{ model.name }})
+set(SIM_OBJ sim_{{ model.name }})
+
+# model
+set(MODEL_SRC
+{%- if solver_options.integrator_type == "ERK" %}
+ {{ model.name }}_model/{{ model.name }}_expl_ode_fun.c
+ {{ model.name }}_model/{{ model.name }}_expl_vde_forw.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_model/{{ model.name }}_expl_ode_hess.c
+ {%- endif %}
+{%- elif solver_options.integrator_type == "IRK" %}
+ {{ model.name }}_model/{{ model.name }}_impl_dae_fun.c
+ {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_z.c
+ {{ model.name }}_model/{{ model.name }}_impl_dae_jac_x_xdot_u_z.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_model/{{ model.name }}_impl_dae_hess.c
+ {%- endif %}
+{%- elif solver_options.integrator_type == "LIFTED_IRK" %}
+ {{ model.name }}_model/{{ model.name }}_impl_dae_fun.c
+ {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_u.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_model/{{ model.name }}_impl_dae_hess.c
+ {%- endif %}
+{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
+ {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun.c
+ {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun_jac_y.c
+ {{ model.name }}_model/{{ model.name }}_gnsf_phi_jac_y_uhat.c
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
+ {{ model.name }}_model/{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.c
+ {%- endif %}
+ {%- endif %}
+ {{ model.name }}_model/{{ model.name }}_gnsf_get_matrices_fun.c
+{%- elif solver_options.integrator_type == "DISCRETE" %}
+ {%- if model.dyn_ext_fun_type == "casadi" %}
+ {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun.c
+ {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac_hess.c
+ {%- endif %}
+ {%- else %}
+ {{ model.name }}_model/{{ model.dyn_source_discrete }}
+ {%- endif %}
+{%- endif -%}
+)
+add_library(${MODEL_OBJ} OBJECT ${MODEL_SRC} )
+
+# optimal control problem - mostly CasADi exports
+if(${BUILD_ACADOS_SOLVER_LIB} OR ${BUILD_ACADOS_OCP_SOLVER_LIB} OR ${BUILD_EXAMPLE})
+ set(OCP_SRC
+{%- if constr_type == "BGP" and dims_nphi > 0 %}
+ {{ model.name }}_constraints/{{ model.name }}_phi_constraint.c
+{%- endif %}
+{%- if constr_type_e == "BGP" and dims_nphi_e > 0 %}
+ {{ model.name }}_constraints/{{ model.name }}_phi_e_constraint.c
+{%- endif %}
+
+{%- if constr_type == "BGH" and dims_nh > 0 %}
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt.c
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_fun.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt_hess.c
+ {%- endif %}
+{%- endif %}
+
+{%- if constr_type_e == "BGH" and dims_nh_e > 0 %}
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt.c
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun.c
+ {%- if hessian_approx == "EXACT" %}
+ {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess.c
+ {%- endif %}
+{%- endif %}
+
+{%- if cost_type_0 == "NONLINEAR_LS" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun_jac_ut_xt.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_0_hess.c
+{%- elif cost_type_0 == "EXTERNAL" %}
+ {%- if cost.cost_ext_fun_type_0 == "casadi" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac_hess.c
+ {%- else %}
+ {{ model.name }}_cost/{{ cost.cost_source_ext_cost_0 }}
+ {%- endif %}
+{%- endif %}
+{%- if cost_type == "NONLINEAR_LS" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_y_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_fun_jac_ut_xt.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_hess.c
+{%- elif cost_type == "EXTERNAL" %}
+ {%- if cost.cost_ext_fun_type == "casadi" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac_hess.c
+ {%- elif cost.cost_source_ext_cost != cost.cost_source_ext_cost_0 %}
+ {{ model.name }}_cost/{{ cost.cost_source_ext_cost }}
+ {%- endif %}
+{%- endif %}
+{%- if cost_type_e == "NONLINEAR_LS" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun_jac_ut_xt.c
+ {{ model.name }}_cost/{{ model.name }}_cost_y_e_hess.c
+{%- elif cost_type_e == "EXTERNAL" %}
+ {%- if cost.cost_ext_fun_type_e == "casadi" %}
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac.c
+ {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac_hess.c
+ {%- elif cost.cost_source_ext_cost_e != cost.cost_source_ext_cost_0 %}
+ {{ model.name }}_cost/{{ cost.cost_source_ext_cost_e }}
+ {%- endif %}
+{%- endif %}
+ acados_solver_{{ model.name }}.c)
+ add_library(${OCP_OBJ} OBJECT ${OCP_SRC})
+endif()
+
+{%- if solver_options.integrator_type != "DISCRETE" %}
+# for sim solver
+if(${BUILD_ACADOS_SOLVER_LIB} OR ${BUILD_EXAMPLE}
+ {%- if solver_options.integrator_type != "DISCRETE" %}
+ OR ${BUILD_SIM_EXAMPLE} OR ${BUILD_ACADOS_SIM_SOLVER_LIB}
+ {%- endif -%}
+ )
+ set(SIM_SRC acados_sim_solver_{{ model.name }}.c)
+ add_library(${SIM_OBJ} OBJECT ${SIM_SRC})
+endif()
+{%- endif %}
+
+# for target example
+set(EX_SRC main_{{ model.name }}.c)
+set(EX_EXE main_{{ model.name }})
+
+{%- if model_external_shared_lib_dir and model_external_shared_lib_name %}
+set(EXTERNAL_DIR {{ model_external_shared_lib_dir }})
+set(EXTERNAL_LIB {{ model_external_shared_lib_name }})
+{%- else %}
+set(EXTERNAL_DIR)
+set(EXTERNAL_LIB)
+{%- endif %}
+
+# set some search paths for preprocessor and linker
+set(ACADOS_INCLUDE_PATH {{ acados_include_path }} CACHE PATH "Define the path which contains the include directory for acados.")
+set(ACADOS_LIB_PATH {{ acados_lib_path }} CACHE PATH "Define the path which contains the lib directory for acados.")
+
+# c-compiler flags for debugging
+set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb")
+
+set(CMAKE_C_FLAGS "
+{%- if qp_solver == "FULL_CONDENSING_QPOASES" -%}
+ -DACADOS_WITH_QPOASES
+{%- endif -%}
+{%- if qp_solver == "PARTIAL_CONDENSING_OSQP" -%}
+ -DACADOS_WITH_OSQP
+{%- endif -%}
+{%- if qp_solver == "PARTIAL_CONDENSING_QPDUNES" -%}
+ -DACADOS_WITH_QPDUNES
+{%- endif -%}
+ -fPIC -std=c99 {{ openmp_flag }}")
+#-fno-diagnostics-show-line-numbers -g
+
+include_directories(
+ ${ACADOS_INCLUDE_PATH}
+ ${ACADOS_INCLUDE_PATH}/acados
+ ${ACADOS_INCLUDE_PATH}/blasfeo/include
+ ${ACADOS_INCLUDE_PATH}/hpipm/include
+{%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
+ ${ACADOS_INCLUDE_PATH}/qpOASES_e/
+{%- endif %}
+)
+
+# linker flags
+link_directories(${ACADOS_LIB_PATH})
+
+# link to libraries
+if(UNIX)
+ link_libraries(acados hpipm blasfeo m {{ link_libs }})
+else()
+ link_libraries(acados hpipm blasfeo {{ link_libs }})
+endif()
+
+# the targets
+
+# bundled_shared_lib
+if(${BUILD_ACADOS_SOLVER_LIB})
+ set(LIB_ACADOS_SOLVER acados_solver_{{ model.name }})
+ add_library(${LIB_ACADOS_SOLVER} SHARED $ $
+ {%- if solver_options.integrator_type != "DISCRETE" %}
+ $
+ {%- endif -%}
+ )
+ install(TARGETS ${LIB_ACADOS_SOLVER} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(${BUILD_ACADOS_SOLVER_LIB})
+
+# ocp_shared_lib
+if(${BUILD_ACADOS_OCP_SOLVER_LIB})
+ set(LIB_ACADOS_OCP_SOLVER acados_ocp_solver_{{ model.name }})
+ add_library(${LIB_ACADOS_OCP_SOLVER} SHARED $ $)
+ # Specify libraries or flags to use when linking a given target and/or its dependents.
+ target_link_libraries(${LIB_ACADOS_OCP_SOLVER} PRIVATE ${EXTERNAL_LIB})
+ target_link_directories(${LIB_ACADOS_OCP_SOLVER} PRIVATE ${EXTERNAL_DIR})
+ install(TARGETS ${LIB_ACADOS_OCP_SOLVER} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(${BUILD_ACADOS_OCP_SOLVER_LIB})
+
+# example
+if(${BUILD_EXAMPLE})
+ add_executable(${EX_EXE} ${EX_SRC} $ $
+ {%- if solver_options.integrator_type != "DISCRETE" %}
+ $
+ {%- endif -%}
+ )
+ install(TARGETS ${EX_EXE} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(${BUILD_EXAMPLE})
+
+{% if solver_options.integrator_type != "DISCRETE" -%}
+# example_sim
+if(${BUILD_SIM_EXAMPLE})
+ set(EX_SIM_SRC main_sim_{{ model.name }}.c)
+ set(EX_SIM_EXE main_sim_{{ model.name }})
+ add_executable(${EX_SIM_EXE} ${EX_SIM_SRC} $ $)
+ install(TARGETS ${EX_SIM_EXE} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(${BUILD_SIM_EXAMPLE})
+
+# sim_shared_lib
+if(${BUILD_ACADOS_SIM_SOLVER_LIB})
+ set(LIB_ACADOS_SIM_SOLVER acados_sim_solver_{{ model.name }})
+ add_library(${LIB_ACADOS_SIM_SOLVER} SHARED $ $)
+ install(TARGETS ${LIB_ACADOS_SIM_SOLVER} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(${BUILD_ACADOS_SIM_SOLVER_LIB})
+{%- endif %}
+
diff --git a/pyextra/acados_template/c_templates_tera/Makefile.in b/pyextra/acados_template/c_templates_tera/Makefile.in
index 487e66ab07..d45be0a9c7 100644
--- a/pyextra/acados_template/c_templates_tera/Makefile.in
+++ b/pyextra/acados_template/c_templates_tera/Makefile.in
@@ -125,134 +125,134 @@
{%- endif %}
{%- endif %}
-{# acados flags #}
-ACADOS_FLAGS = -fPIC -std=c99 {{ openmp_flag }} #-fno-diagnostics-show-line-numbers -g
-{%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
-ACADOS_FLAGS += -DACADOS_WITH_QPOASES
-{%- endif %}
-{%- if qp_solver == "PARTIAL_CONDENSING_OSQP" %}
-ACADOS_FLAGS += -DACADOS_WITH_OSQP
-{%- endif %}
-{%- if qp_solver == "PARTIAL_CONDENSING_QPDUNES" %}
-ACADOS_FLAGS += -DACADOS_WITH_QPDUNES
-{%- endif %}
-# # Debugging
-# ACADOS_FLAGS += -g3
+# define sources and use make's implicit rules to generate object files (*.o)
-MODEL_OBJ=
+# model
+MODEL_SRC=
{%- if solver_options.integrator_type == "ERK" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_expl_ode_fun.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_expl_vde_forw.o
-{%- if hessian_approx == "EXACT" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_expl_ode_hess.o
-{%- endif %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_expl_ode_fun.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_expl_vde_forw.c
+ {%- if hessian_approx == "EXACT" %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_expl_ode_hess.c
+ {%- endif %}
{%- elif solver_options.integrator_type == "IRK" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_z.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_jac_x_xdot_u_z.o
-{%- if hessian_approx == "EXACT" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_hess.o
-{%- endif %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_z.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_jac_x_xdot_u_z.c
+ {%- if hessian_approx == "EXACT" %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_hess.c
+ {%- endif %}
{%- elif solver_options.integrator_type == "LIFTED_IRK" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_u.o
-{%- if hessian_approx == "EXACT" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_impl_dae_hess.o
-{%- endif %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_fun_jac_x_xdot_u.c
+ {%- if hessian_approx == "EXACT" %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_impl_dae_hess.c
+ {%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun_jac_y.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_jac_y_uhat.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_gnsf_get_matrices_fun.o
+ {% if model.gnsf.purely_linear != 1 %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_fun_jac_y.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_gnsf_phi_jac_y_uhat.c
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.c
+ {%- endif %}
+ {%- endif %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_gnsf_get_matrices_fun.c
{%- elif solver_options.integrator_type == "DISCRETE" %}
-{%- if model.dyn_ext_fun_type == "casadi" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun.o
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac.o
-{%- if hessian_approx == "EXACT" %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac_hess.o
-{%- endif %}
-{%- else %}
-MODEL_OBJ+= {{ model.name }}_model/{{ model.dyn_source_discrete }}
-{%- endif %}
+ {%- if model.dyn_ext_fun_type == "casadi" %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun.c
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac.c
+ {%- if hessian_approx == "EXACT" %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun_jac_hess.c
+ {%- endif %}
+ {%- else %}
+MODEL_SRC+= {{ model.name }}_model/{{ model.dyn_source_discrete }}
+ {%- endif %}
{%- endif %}
+MODEL_OBJ := $(MODEL_SRC:.c=.o)
-
-OCP_OBJ=
+# optimal control problem - mostly CasADi exports
+OCP_SRC=
{%- if constr_type == "BGP" and dims_nphi > 0 %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_phi_constraint.o
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_phi_constraint.c
{%- endif %}
{%- if constr_type_e == "BGP" and dims_nphi_e > 0 %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_phi_e_constraint.o
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_phi_e_constraint.c
{%- endif %}
{%- if constr_type == "BGH" and dims_nh > 0 %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt.o
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun.o
-{%- if hessian_approx == "EXACT" %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt_hess.o
-{%- endif %}
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt.c
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun.c
+ {%- if hessian_approx == "EXACT" %}
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_fun_jac_uxt_zt_hess.c
+ {%- endif %}
{%- endif %}
{%- if constr_type_e == "BGH" and dims_nh_e > 0 %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt.o
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun.o
-{%- if hessian_approx == "EXACT" %}
-OCP_OBJ+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess.o
-{%- endif %}
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt.c
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun.c
+ {%- if hessian_approx == "EXACT" %}
+OCP_SRC+= {{ model.name }}_constraints/{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess.c
+ {%- endif %}
{%- endif %}
{%- if cost_type_0 == "NONLINEAR_LS" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun_jac_ut_xt.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_hess.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_fun_jac_ut_xt.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_0_hess.c
{%- elif cost_type_0 == "EXTERNAL" %}
-{% if cost.cost_ext_fun_type_0 == "casadi" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac_hess.c
-{% else %}
-OCP_OBJ+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost_0 }}
-{% endif %}
+ {%- if cost.cost_ext_fun_type_0 == "casadi" %}
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_0_fun_jac_hess.c
+ {%- else %}
+OCP_SRC+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost_0 }}
+ {%- endif %}
{%- endif %}
{%- if cost_type == "NONLINEAR_LS" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_fun_jac_ut_xt.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_hess.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_fun_jac_ut_xt.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_hess.c
{%- elif cost_type == "EXTERNAL" %}
-{% if cost.cost_ext_fun_type == "casadi" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac_hess.c
-{% elif cost.cost_source_ext_cost != cost.cost_source_ext_cost_0 %}
-OCP_OBJ+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost }}
-{% endif %}
+ {%- if cost.cost_ext_fun_type == "casadi" %}
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_fun_jac_hess.c
+ {%- elif cost.cost_source_ext_cost != cost.cost_source_ext_cost_0 %}
+OCP_SRC+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost }}
+ {%- endif %}
{%- endif %}
{%- if cost_type_e == "NONLINEAR_LS" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun_jac_ut_xt.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_hess.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_fun_jac_ut_xt.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_y_e_hess.c
{%- elif cost_type_e == "EXTERNAL" %}
-{% if cost.cost_ext_fun_type_e == "casadi" %}
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac.c
-OCP_OBJ+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac_hess.c
-{% elif cost.cost_source_ext_cost_e != cost.cost_source_ext_cost_0 %}
-OCP_OBJ+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost_e }}
-{% endif %}
+ {%- if cost.cost_ext_fun_type_e == "casadi" %}
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac.c
+OCP_SRC+= {{ model.name }}_cost/{{ model.name }}_cost_ext_cost_e_fun_jac_hess.c
+ {%- elif cost.cost_source_ext_cost_e != cost.cost_source_ext_cost_0 %}
+OCP_SRC+= {{ model.name }}_cost/{{ cost.cost_source_ext_cost_e }}
+ {%- endif %}
{%- endif %}
-OCP_OBJ+= acados_solver_{{ model.name }}.o
+OCP_SRC+= acados_solver_{{ model.name }}.c
+OCP_OBJ := $(OCP_SRC:.c=.o)
+# for sim solver
+SIM_SRC= acados_sim_solver_{{ model.name }}.c
+SIM_OBJ := $(SIM_SRC:.c=.o)
-SIM_OBJ=
-SIM_OBJ+= acados_sim_solver_{{ model.name }}.o
+# for target example
+EX_SRC= main_{{ model.name }}.c
+EX_OBJ := $(EX_SRC:.c=.o)
+EX_EXE := $(EX_SRC:.c=)
-EX_OBJ=
-EX_OBJ+= main_{{ model.name }}.o
-
-EX_SIM_OBJ=
-EX_SIM_OBJ+= main_sim_{{ model.name }}.o
+# for target example_sim
+EX_SIM_SRC= main_sim_{{ model.name }}.c
+EX_SIM_OBJ := $(EX_SIM_SRC:.c=.o)
+EX_SIM_EXE := $(EX_SIM_SRC:.c=)
+# combine model, sim and ocp object files
OBJ=
OBJ+= $(MODEL_OBJ)
{%- if solver_options.integrator_type != "DISCRETE" %}
@@ -271,233 +271,103 @@ EXTERNAL_LIB+= {{ model_external_shared_lib_name }}
INCLUDE_PATH = {{ acados_include_path }}
LIB_PATH = {{ acados_lib_path }}
-{%- if solver_options.integrator_type == "DISCRETE" %}
-all: clean casadi_fun example
-shared_lib: ocp_shared_lib
-{%- else %}
-all: clean casadi_fun example_sim example
-shared_lib: bundled_shared_lib ocp_shared_lib sim_shared_lib
-{%- endif %}
-
-CASADI_MODEL_SOURCE=
-{%- if solver_options.integrator_type == "ERK" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_expl_ode_fun.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_expl_vde_forw.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_expl_ode_hess.c
-{%- endif %}
-{%- elif solver_options.integrator_type == "IRK" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_fun.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_fun_jac_x_xdot_z.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_jac_x_xdot_u_z.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_hess.c
-{%- endif %}
-{%- elif solver_options.integrator_type == "LIFTED_IRK" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_fun.c
-# CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_fun_jac_x_xdot_z.c
-# CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_jac_x_xdot_u_z.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_fun_jac_x_xdot_u.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_impl_dae_hess.c
-{%- endif %}
-{%- elif solver_options.integrator_type == "GNSF" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_gnsf_phi_fun.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_gnsf_phi_fun_jac_y.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_gnsf_phi_jac_y_uhat.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_gnsf_get_matrices_fun.c
-{%- elif solver_options.integrator_type == "DISCRETE" and model.dyn_ext_fun_type == "casadi" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_dyn_disc_phi_fun.c
-CASADI_MODEL_SOURCE+= {{ model.name }}_dyn_disc_phi_fun_jac.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_MODEL_SOURCE+= {{ model.name }}_dyn_disc_phi_fun_jac_hess.c
-{%- endif %}
-{%- endif %}
-{%- if constr_type == "BGP" and dims_nphi > 0 %}
-CASADI_CON_PHI_SOURCE=
-CASADI_CON_PHI_SOURCE+= {{ model.name }}_phi_constraint.c
-{%- endif %}
-{%- if constr_type_e == "BGP" and dims_nphi_e > 0 %}
-CASADI_CON_PHI_E_SOURCE=
-CASADI_CON_PHI_E_SOURCE+= {{ model.name }}_phi_e_constraint.c
-{%- endif %}
-{%- if constr_type == "BGH" and dims_nh > 0 %}
-CASADI_CON_H_SOURCE=
-CASADI_CON_H_SOURCE+= {{ model.name }}_constr_h_fun_jac_uxt_zt.c
-CASADI_CON_H_SOURCE+= {{ model.name }}_constr_h_fun.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_CON_H_SOURCE+= {{ model.name }}_constr_h_fun_jac_uxt_zt_hess.c
-{%- endif %}
-{%- endif %}
-
-{%- if dims_nh_e > 0 %}
-CASADI_CON_H_E_SOURCE=
-CASADI_CON_H_E_SOURCE+= {{ model.name }}_constr_h_e_fun_jac_uxt_zt.c
-CASADI_CON_H_E_SOURCE+= {{ model.name }}_constr_h_e_fun.c
-{%- if hessian_approx == "EXACT" %}
-CASADI_CON_H_E_SOURCE+= {{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess.c
-{%- endif %}
-{%- endif %}
-
-{%- if cost_type == "NONLINEAR_LS" %}
-CASADI_COST_Y_SOURCE=
-CASADI_COST_Y_SOURCE+= {{ model.name }}_cost_y_fun.c
-CASADI_COST_Y_SOURCE+= {{ model.name }}_cost_y_fun_jac_ut_xt.c
-CASADI_COST_Y_SOURCE+= {{ model.name }}_cost_y_hess.c
-{%- endif %}
-{%- if cost_type_e == "NONLINEAR_LS" %}
-CASADI_COST_Y_E_SOURCE=
-CASADI_COST_Y_E_SOURCE+= {{ model.name }}_cost_y_e_fun.c
-CASADI_COST_Y_E_SOURCE+= {{ model.name }}_cost_y_e_fun_jac_ut_xt.c
-CASADI_COST_Y_E_SOURCE+= {{ model.name }}_cost_y_e_hess.c
+# preprocessor flags for make's implicit rules
+{%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
+CPPFLAGS += -DACADOS_WITH_QPOASES
{%- endif %}
-{%- if cost_type_0 == "NONLINEAR_LS" %}
-CASADI_COST_Y_0_SOURCE=
-CASADI_COST_Y_0_SOURCE+= {{ model.name }}_cost_y_0_fun.c
-CASADI_COST_Y_0_SOURCE+= {{ model.name }}_cost_y_0_fun_jac_ut_xt.c
-CASADI_COST_Y_0_SOURCE+= {{ model.name }}_cost_y_0_hess.c
+{%- if qp_solver == "PARTIAL_CONDENSING_OSQP" %}
+CPPFLAGS += -DACADOS_WITH_OSQP
{%- endif %}
+{%- if qp_solver == "PARTIAL_CONDENSING_QPDUNES" %}
+CPPFLAGS += -DACADOS_WITH_QPDUNES
+{%- endif %}
+CPPFLAGS+= -I$(INCLUDE_PATH)
+CPPFLAGS+= -I$(INCLUDE_PATH)/acados
+CPPFLAGS+= -I$(INCLUDE_PATH)/blasfeo/include
+CPPFLAGS+= -I$(INCLUDE_PATH)/hpipm/include
+ {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
+CPPFLAGS+= -I $(INCLUDE_PATH)/qpOASES_e/
+ {%- endif %}
+
+{# c-compiler flags #}
+# define the c-compiler flags for make's implicit rules
+CFLAGS = -fPIC -std=c99 {{ openmp_flag }} #-fno-diagnostics-show-line-numbers -g
+# # Debugging
+# CFLAGS += -g3
-casadi_fun:
- {%- if model.dyn_ext_fun_type == "casadi" %}
- ( cd {{ model.name }}_model {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_MODEL_SOURCE))
- {%- endif %}
- {%- if constr_type == "BGP" and dims_nphi > 0 %}
- ( cd {{ model.name }}_constraints {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_CON_PHI_SOURCE))
- {%- endif %}
- {%- if constr_type_e == "BGP" and dims_nphi_e > 0 %}
- ( cd {{ model.name }}_constraints {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_CON_PHI_E_SOURCE))
- {%- endif %}
- {%- if constr_type == "BGH" and dims_nh > 0 %}
- ( cd {{ model.name }}_constraints {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_CON_H_SOURCE))
- {%- endif %}
- {%- if constr_type_e == "BGH" and dims_nh_e > 0 %}
- ( cd {{ model.name }}_constraints {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_CON_H_E_SOURCE))
- {%- endif %}
- {%- if cost_type == "NONLINEAR_LS" %}
- ( cd {{ model.name }}_cost {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_COST_Y_SOURCE))
- {%- endif %}
- {%- if cost_type_e == "NONLINEAR_LS" %}
- ( cd {{ model.name }}_cost {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_COST_Y_E_SOURCE))
- {%- endif %}
- {%- if cost_type_0 == "NONLINEAR_LS" %}
- ( cd {{ model.name }}_cost {{ control }} gcc $(ACADOS_FLAGS) -c $(CASADI_COST_Y_0_SOURCE))
- {%- endif %}
-
-main:
- gcc $(ACADOS_FLAGS) -c main_{{ model.name }}.c -I $(INCLUDE_PATH)/blasfeo/include/ -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) -I $(INCLUDE_PATH)/acados/ \
- {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
- -I $(INCLUDE_PATH)/qpOASES_e/
- {%- endif %}
+# linker flags
+LDFLAGS+= -L$(LIB_PATH)
-main_sim:
- gcc $(ACADOS_FLAGS) -c main_sim_{{ model.name }}.c -I $(INCLUDE_PATH)/blasfeo/include/ -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) -I $(INCLUDE_PATH)/acados/
+# link to libraries
+LDLIBS+= -lacados
+LDLIBS+= -lhpipm
+LDLIBS+= -lblasfeo
+LDLIBS+= -lm
+LDLIBS+= {{ link_libs }}
-ocp_solver:
- gcc $(ACADOS_FLAGS) -c acados_solver_{{ model.name }}.c -I $(INCLUDE_PATH)/blasfeo/include/ -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) -I $(INCLUDE_PATH)/acados/ \
- {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
- -I $(INCLUDE_PATH)/qpOASES_e/
- {%- endif %}
+# libraries
+LIBACADOS_SOLVER=libacados_solver_{{ model.name }}.so
+LIBACADOS_OCP_SOLVER=libacados_ocp_solver_{{ model.name }}.so
+LIBACADOS_SIM_SOLVER=lib$(SIM_SRC:.c=.so)
-sim_solver:
- gcc $(ACADOS_FLAGS) -c acados_sim_solver_{{ model.name }}.c -I $(INCLUDE_PATH)/blasfeo/include/ -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) -I $(INCLUDE_PATH)/acados/ \
- {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
- -I $(INCLUDE_PATH)/qpOASES_e/
- {%- endif %}
+# virtual targets
+.PHONY : all clean
-example: ocp_solver main
- gcc $(ACADOS_FLAGS) -o main_{{ model.name }} $(EX_OBJ) $(OBJ) -L $(LIB_PATH) \
- -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
- -I $(INCLUDE_PATH)/blasfeo/include/ \
- -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) \
- -I $(INCLUDE_PATH)/acados/ \
- {%- if qp_solver == "FULL_CONDENSING_QPOASES" %}
- -I $(INCLUDE_PATH)/qpOASES_e/
- {%- endif %}
-
-
-example_sim: sim_solver main_sim
- gcc $(ACADOS_FLAGS) -o main_sim_{{ model.name }} $(EX_SIM_OBJ) $(MODEL_OBJ) $(SIM_OBJ) -L $(LIB_PATH) \
- -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
- -I $(INCLUDE_PATH)/blasfeo/include/ \
- -I $(INCLUDE_PATH)/acados/ \
+#all: clean example_sim example shared_lib
+{% if solver_options.integrator_type == "DISCRETE" -%}
+all: clean example
+shared_lib: ocp_shared_lib
+{%- else %}
+all: clean example_sim example
+shared_lib: bundled_shared_lib ocp_shared_lib sim_shared_lib
+{%- endif %}
-{%- if solver_options.integrator_type != "DISCRETE" %}
+# some linker targets
+example: $(EX_OBJ) $(OBJ)
+ $(CC) $^ -o $(EX_EXE) $(LDFLAGS) $(LDLIBS)
-bundled_shared_lib: casadi_fun ocp_solver sim_solver
- gcc $(ACADOS_FLAGS) -shared -o libacados_solver_{{ model.name }}.so $(OBJ) \
- -I $(INCLUDE_PATH)/blasfeo/include/ \
- -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) \
- -L $(LIB_PATH) \
- -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
+example_sim: $(EX_SIM_OBJ) $(MODEL_OBJ) $(SIM_OBJ)
+ $(CC) $^ -o $(EX_SIM_EXE) $(LDFLAGS) $(LDLIBS)
-ocp_shared_lib: casadi_fun ocp_solver
- gcc $(ACADOS_FLAGS) -shared -o libacados_ocp_solver_{{ model.name }}.so $(OCP_OBJ) $(MODEL_OBJ) \
- -I $(INCLUDE_PATH)/blasfeo/include/ \
- -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) \
- -L$(EXTERNAL_DIR) -l$(EXTERNAL_LIB) \
- -L $(LIB_PATH) -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
+{% if solver_options.integrator_type != "DISCRETE" -%}
+bundled_shared_lib: $(OBJ)
+ $(CC) -shared $^ -o $(LIBACADOS_SOLVER) $(LDFLAGS) $(LDLIBS)
+{%- endif %}
-{%- else %}
+ocp_shared_lib: $(OCP_OBJ) $(MODEL_OBJ)
+ $(CC) -shared $^ -o $(LIBACADOS_OCP_SOLVER) $(LDFLAGS) $(LDLIBS) \
+ -L$(EXTERNAL_DIR) -l$(EXTERNAL_LIB)
-ocp_shared_lib: casadi_fun ocp_solver
- gcc $(ACADOS_FLAGS) -shared -o libacados_ocp_solver_{{ model.name }}.so $(OCP_OBJ) $(MODEL_OBJ) \
- -I $(INCLUDE_PATH)/blasfeo/include/ \
- -I $(INCLUDE_PATH)/hpipm/include/ \
- -I $(INCLUDE_PATH) \
- -L$(EXTERNAL_DIR) -l$(EXTERNAL_LIB) \
- -L $(LIB_PATH) -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
+sim_shared_lib: $(SIM_OBJ) $(MODEL_OBJ)
+ $(CC) -shared $^ -o $(LIBACADOS_SIM_SOLVER) $(LDFLAGS) $(LDLIBS)
-{%- endif %}
+# Cython targets
ocp_cython_c: ocp_shared_lib
cython \
-o acados_ocp_solver_pyx.c \
-I $(INCLUDE_PATH)/../interfaces/acados_template/acados_template \
$(INCLUDE_PATH)/../interfaces/acados_template/acados_template/acados_ocp_solver_pyx.pyx \
+ -I {{ code_export_directory }} \
ocp_cython_o: ocp_cython_c
- clang $(ACADOS_FLAGS) -c -O2 \
+ $(CC) $(ACADOS_FLAGS) -c -O2 \
+ -fPIC \
-o acados_ocp_solver_pyx.o \
-I /usr/include/python3.8 \
-I $(INCLUDE_PATH)/blasfeo/include/ \
-I $(INCLUDE_PATH)/hpipm/include/ \
-I $(INCLUDE_PATH) \
+ -I {{ cython_include_dirs }} \
acados_ocp_solver_pyx.c \
ocp_cython: ocp_cython_o
- clang $(ACADOS_FLAGS) -shared \
+ $(CC) $(ACADOS_FLAGS) -shared \
-o acados_ocp_solver_pyx.so \
-Wl,-rpath=$(LIB_PATH) \
acados_ocp_solver_pyx.o \
$(abspath .)/libacados_ocp_solver_{{ model.name }}.so \
- -L $(LIB_PATH) -lacados -lhpipm -lblasfeo -lqpOASES_e \
- {{ link_libs }} \
- -lm \
-
-sim_shared_lib: casadi_fun sim_solver
- gcc $(ACADOS_FLAGS) -shared -o libacados_sim_solver_{{ model.name }}.so $(SIM_OBJ) $(MODEL_OBJ) -L$(EXTERNAL_DIR) -l$(EXTERNAL_LIB) \
- -L $(LIB_PATH) -lacados -lhpipm -lblasfeo \
- {{ link_libs }} \
- -lm \
+ $(LDFLAGS) $(LDLIBS)
{%- if os and os == "pc" %}
@@ -510,15 +380,27 @@ clean_ocp_shared_lib:
del \Q libacados_ocp_solver_{{ model.name }}.so 2>nul
del \Q acados_solver_{{ model.name }}.o 2>nul
+clean_ocp_cython:
+ del \Q libacados_ocp_solver_{{ model.name }}.so 2>nul
+ del \Q acados_solver_{{ model.name }}.o 2>nul
+ del \Q acados_ocp_solver_pyx.so 2>nul
+ del \Q acados_ocp_solver_pyx.o 2>nul
+
{%- else %}
clean:
- rm -f *.o
- rm -f *.so
- rm -f main_{{ model.name }}
+ $(RM) $(OBJ) $(EX_OBJ) $(EX_SIM_OBJ)
+ $(RM) $(LIBACADOS_SOLVER) $(LIBACADOS_OCP_SOLVER) $(LIBACADOS_SIM_SOLVER)
+ $(RM) $(EX_EXE) $(EX_SIM_EXE)
clean_ocp_shared_lib:
- rm -f libacados_ocp_solver_{{ model.name }}.so
- rm -f acados_solver_{{ model.name }}.o
+ $(RM) $(LIBACADOS_OCP_SOLVER)
+ $(RM) $(OCP_OBJ)
+
+clean_ocp_cython:
+ $(RM) libacados_ocp_solver_{{ model.name }}.so
+ $(RM) acados_solver_{{ model.name }}.o
+ $(RM) acados_ocp_solver_pyx.so
+ $(RM) acados_ocp_solver_pyx.o
{%- endif %}
diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c b/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c
index 251b249b2f..e67a51567a 100644
--- a/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c
@@ -63,7 +63,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
mexPrintf("{{ model.name }}_acados_create() -> success!\n");
// get pointers to nlp solver related objects
- ocp_nlp_plan *nlp_plan = {{ model.name }}_acados_get_nlp_plan(acados_ocp_capsule);
+ ocp_nlp_plan_t *nlp_plan = {{ model.name }}_acados_get_nlp_plan(acados_ocp_capsule);
ocp_nlp_config *nlp_config = {{ model.name }}_acados_get_nlp_config(acados_ocp_capsule);
ocp_nlp_dims *nlp_dims = {{ model.name }}_acados_get_nlp_dims(acados_ocp_capsule);
ocp_nlp_in *nlp_in = {{ model.name }}_acados_get_nlp_in(acados_ocp_capsule);
@@ -238,14 +238,18 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
l_ptr[0] = (long long) acados_ocp_capsule->impl_dae_hess;
{%- endif %}
{% elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
l_ptr = mxGetData(gnsf_phi_fun_mat);
l_ptr[0] = (long long) acados_ocp_capsule->gnsf_phi_fun;
l_ptr = mxGetData(gnsf_phi_fun_jac_y_mat);
l_ptr[0] = (long long) acados_ocp_capsule->gnsf_phi_fun_jac_y;
l_ptr = mxGetData(gnsf_phi_jac_y_uhat_mat);
l_ptr[0] = (long long) acados_ocp_capsule->gnsf_phi_jac_y_uhat;
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
l_ptr = mxGetData(gnsf_f_lo_jac_x1_x1dot_u_z_mat);
l_ptr[0] = (long long) acados_ocp_capsule->gnsf_f_lo_jac_x1_x1dot_u_z;
+ {%- endif %}
+ {%- endif %}
l_ptr = mxGetData(gnsf_get_matrices_fun_mat);
l_ptr[0] = (long long) acados_ocp_capsule->gnsf_get_matrices_fun;
{% elif solver_options.integrator_type == "DISCRETE" %}
diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c b/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c
index ae8c311557..f8e1e5e445 100644
--- a/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c
@@ -69,7 +69,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{{ model.name }}_solver_capsule *capsule = ({{ model.name }}_solver_capsule *) ptr[0];
// plan
ptr = (long long *) mxGetData( mxGetField( C_ocp, 0, "plan" ) );
- ocp_nlp_plan *plan = (ocp_nlp_plan *) ptr[0];
+ ocp_nlp_plan_t *plan = (ocp_nlp_plan_t *) ptr[0];
// config
ptr = (long long *) mxGetData( mxGetField( C_ocp, 0, "config" ) );
ocp_nlp_config *config = (ocp_nlp_config *) ptr[0];
@@ -404,7 +404,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
}
else if (!strcmp(field, "init_z"))
{
- sim_solver_plan sim_plan = plan->sim_solver_plan[0];
+ sim_solver_plan_t sim_plan = plan->sim_solver_plan[0];
sim_solver_t type = sim_plan.sim_solver;
if (type == IRK)
{
@@ -426,7 +426,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
}
else if (!strcmp(field, "init_xdot"))
{
- sim_solver_plan sim_plan = plan->sim_solver_plan[0];
+ sim_solver_plan_t sim_plan = plan->sim_solver_plan[0];
sim_solver_t type = sim_plan.sim_solver;
if (type == IRK)
{
@@ -448,7 +448,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
}
else if (!strcmp(field, "init_gnsf_phi"))
{
- sim_solver_plan sim_plan = plan->sim_solver_plan[0];
+ sim_solver_plan_t sim_plan = plan->sim_solver_plan[0];
sim_solver_t type = sim_plan.sim_solver;
if (type == GNSF)
{
diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c b/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c
index 971fb8a78f..f2e75058c1 100644
--- a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c
@@ -164,12 +164,17 @@ int {{ model.name }}_acados_sim_create(sim_solver_capsule * capsule)
{%- endif %}
{% elif solver_options.integrator_type == "GNSF" -%}
+ {% if model.gnsf.purely_linear != 1 %}
capsule->sim_gnsf_phi_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi));
capsule->sim_gnsf_phi_fun_jac_y = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi));
capsule->sim_gnsf_phi_jac_y_uhat = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi));
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi));
+ {%- endif %}
+ {%- endif %}
capsule->sim_gnsf_get_matrices_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi));
+ {% if model.gnsf.purely_linear != 1 %}
capsule->sim_gnsf_phi_fun->casadi_fun = &{{ model.name }}_gnsf_phi_fun;
capsule->sim_gnsf_phi_fun->casadi_n_in = &{{ model.name }}_gnsf_phi_fun_n_in;
capsule->sim_gnsf_phi_fun->casadi_n_out = &{{ model.name }}_gnsf_phi_fun_n_out;
@@ -194,6 +199,7 @@ int {{ model.name }}_acados_sim_create(sim_solver_capsule * capsule)
capsule->sim_gnsf_phi_jac_y_uhat->casadi_work = &{{ model.name }}_gnsf_phi_jac_y_uhat_work;
external_function_param_casadi_create(capsule->sim_gnsf_phi_jac_y_uhat, np);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z->casadi_fun = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz;
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z->casadi_n_in = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_in;
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z->casadi_n_out = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_out;
@@ -201,6 +207,8 @@ int {{ model.name }}_acados_sim_create(sim_solver_capsule * capsule)
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z->casadi_sparsity_out = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_sparsity_out;
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z->casadi_work = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_work;
external_function_param_casadi_create(capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z, np);
+ {%- endif %}
+ {%- endif %}
capsule->sim_gnsf_get_matrices_fun->casadi_fun = &{{ model.name }}_gnsf_get_matrices_fun;
capsule->sim_gnsf_get_matrices_fun->casadi_n_in = &{{ model.name }}_gnsf_get_matrices_fun_n_in;
@@ -212,7 +220,7 @@ int {{ model.name }}_acados_sim_create(sim_solver_capsule * capsule)
{% endif %}
// sim plan & config
- sim_solver_plan plan;
+ sim_solver_plan_t plan;
plan.sim_solver = {{ solver_options.integrator_type }};
// create correct config based on plan
@@ -307,14 +315,18 @@ int {{ model.name }}_acados_sim_create(sim_solver_capsule * capsule)
"expl_ode_hess", capsule->sim_expl_ode_hess);
{%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
{{ model.name }}_sim_config->model_set({{ model.name }}_sim_in->model,
"phi_fun", capsule->sim_gnsf_phi_fun);
{{ model.name }}_sim_config->model_set({{ model.name }}_sim_in->model,
"phi_fun_jac_y", capsule->sim_gnsf_phi_fun_jac_y);
{{ model.name }}_sim_config->model_set({{ model.name }}_sim_in->model,
"phi_jac_y_uhat", capsule->sim_gnsf_phi_jac_y_uhat);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
{{ model.name }}_sim_config->model_set({{ model.name }}_sim_in->model,
"f_lo_jac_x1_x1dot_u_z", capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z);
+ {%- endif %}
+ {%- endif %}
{{ model.name }}_sim_config->model_set({{ model.name }}_sim_in->model,
"gnsf_get_matrices_fun", capsule->sim_gnsf_get_matrices_fun);
{%- endif %}
@@ -409,10 +421,14 @@ int {{ model.name }}_acados_sim_free(sim_solver_capsule *capsule)
external_function_param_casadi_free(capsule->sim_expl_ode_hess);
{%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
external_function_param_casadi_free(capsule->sim_gnsf_phi_fun);
external_function_param_casadi_free(capsule->sim_gnsf_phi_fun_jac_y);
external_function_param_casadi_free(capsule->sim_gnsf_phi_jac_y_uhat);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
external_function_param_casadi_free(capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z);
+ {%- endif %}
+ {%- endif %}
external_function_param_casadi_free(capsule->sim_gnsf_get_matrices_fun);
{% endif %}
@@ -445,10 +461,14 @@ int {{ model.name }}_acados_sim_update_params(sim_solver_capsule *capsule, doubl
capsule->sim_impl_dae_hess[0].set_param(capsule->sim_impl_dae_hess, p);
{%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
capsule->sim_gnsf_phi_fun[0].set_param(capsule->sim_gnsf_phi_fun, p);
capsule->sim_gnsf_phi_fun_jac_y[0].set_param(capsule->sim_gnsf_phi_fun_jac_y, p);
capsule->sim_gnsf_phi_jac_y_uhat[0].set_param(capsule->sim_gnsf_phi_jac_y_uhat, p);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z[0].set_param(capsule->sim_gnsf_f_lo_jac_x1_x1dot_u_z, p);
+ {%- endif %}
+ {%- endif %}
capsule->sim_gnsf_get_matrices_fun[0].set_param(capsule->sim_gnsf_get_matrices_fun, p);
{% endif %}
diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h b/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h
index 4da8202d3c..7306491baf 100644
--- a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h
+++ b/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h
@@ -80,21 +80,21 @@ typedef struct sim_solver_capsule
} sim_solver_capsule;
-int {{ model.name }}_acados_sim_create(sim_solver_capsule *capsule);
-int {{ model.name }}_acados_sim_solve(sim_solver_capsule *capsule);
-int {{ model.name }}_acados_sim_free(sim_solver_capsule *capsule);
-int {{ model.name }}_acados_sim_update_params(sim_solver_capsule *capsule, double *value, int np);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_sim_create(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_sim_solve(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_sim_free(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_sim_update_params(sim_solver_capsule *capsule, double *value, int np);
-sim_config * {{ model.name }}_acados_get_sim_config(sim_solver_capsule *capsule);
-sim_in * {{ model.name }}_acados_get_sim_in(sim_solver_capsule *capsule);
-sim_out * {{ model.name }}_acados_get_sim_out(sim_solver_capsule *capsule);
-void * {{ model.name }}_acados_get_sim_dims(sim_solver_capsule *capsule);
-sim_opts * {{ model.name }}_acados_get_sim_opts(sim_solver_capsule *capsule);
-sim_solver * {{ model.name }}_acados_get_sim_solver(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_config * {{ model.name }}_acados_get_sim_config(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_in * {{ model.name }}_acados_get_sim_in(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_out * {{ model.name }}_acados_get_sim_out(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT void * {{ model.name }}_acados_get_sim_dims(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_opts * {{ model.name }}_acados_get_sim_opts(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_solver * {{ model.name }}_acados_get_sim_solver(sim_solver_capsule *capsule);
-sim_solver_capsule * {{ model.name }}_acados_sim_solver_create_capsule(void);
-int {{ model.name }}_acados_sim_solver_free_capsule(sim_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT sim_solver_capsule * {{ model.name }}_acados_sim_solver_create_capsule(void);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_sim_solver_free_capsule(sim_solver_capsule *capsule);
#ifdef __cplusplus
}
diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c b/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c
index 234295fa29..68a6a3f80f 100644
--- a/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c
@@ -37,7 +37,7 @@
#define MDL_START
// acados
-#include "acados/utils/print.h"
+// #include "acados/utils/print.h"
#include "acados_c/ocp_nlp_interface.h"
#include "acados_c/external_function_interface.h"
diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.c b/pyextra/acados_template/c_templates_tera/acados_solver.in.c
index 6e13242a41..0af8127709 100644
--- a/pyextra/acados_template/c_templates_tera/acados_solver.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_solver.in.c
@@ -34,8 +34,9 @@
// standard
#include
#include
+#include
// acados
-#include "acados/utils/print.h"
+// #include "acados/utils/print.h"
#include "acados_c/ocp_nlp_interface.h"
#include "acados_c/external_function_interface.h"
@@ -121,14 +122,15 @@ int {{ model.name }}_acados_free_capsule({{ model.name }}_solver_capsule *capsul
}
-int {{ model.name }}_acados_create({{ model.name }}_solver_capsule * capsule)
+int {{ model.name }}_acados_create({{ model.name }}_solver_capsule* capsule)
{
int N_shooting_intervals = {{ model.name | upper }}_N;
double* new_time_steps = NULL; // NULL -> don't alter the code generated time-steps
return {{ model.name }}_acados_create_with_discretization(capsule, N_shooting_intervals, new_time_steps);
}
-int {{ model.name }}_acados_update_time_steps({{ model.name }}_solver_capsule * capsule, int N, double* new_time_steps)
+
+int {{ model.name }}_acados_update_time_steps({{ model.name }}_solver_capsule* capsule, int N, double* new_time_steps)
{
if (N != capsule->nlp_solver_plan->N) {
fprintf(stderr, "{{ model.name }}_acados_update_time_steps: given number of time steps (= %d) " \
@@ -151,30 +153,20 @@ int {{ model.name }}_acados_update_time_steps({{ model.name }}_solver_capsule *
return 0;
}
-int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_capsule * capsule, int N, double* new_time_steps)
+/**
+ * Internal function for {{ model.name }}_acados_create: step 1
+ */
+void {{ model.name }}_acados_create_1_set_plan(ocp_nlp_plan_t* nlp_solver_plan, const int N)
{
- int status = 0;
- // If N does not match the number of shooting intervals used for code generation, new_time_steps must be given.
- if (N != {{ model.name | upper }}_N && !new_time_steps) {
- fprintf(stderr, "{{ model.name }}_acados_create_with_discretization: new_time_steps is NULL " \
- "but the number of shooting intervals (= %d) differs from the number of " \
- "shooting intervals (= %d) during code generation! Please provide a new vector of time_stamps!\n", \
- N, {{ model.name | upper }}_N);
- return 1;
- }
-
- // number of expected runtime parameters
- capsule->nlp_np = NP;
+ assert(N == nlp_solver_plan->N);
/************************************************
- * plan & config
+ * plan
************************************************/
- ocp_nlp_plan * nlp_solver_plan = ocp_nlp_plan_create(N);
- capsule->nlp_solver_plan = nlp_solver_plan;
{%- if solver_options.nlp_solver_type == "SQP" %}
nlp_solver_plan->nlp_solver = SQP;
- {% else %}
+ {%- else %}
nlp_solver_plan->nlp_solver = SQP_RTI;
{%- endif %}
@@ -188,11 +180,11 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
for (int i = 0; i < N; i++)
{
- {% if solver_options.integrator_type == "DISCRETE" %}
+ {%- if solver_options.integrator_type == "DISCRETE" %}
nlp_solver_plan->nlp_dynamics[i] = DISCRETE_MODEL;
// discrete dynamics does not need sim solver option, this field is ignored
nlp_solver_plan->sim_solver_plan[i].sim_solver = INVALID_SIM_SOLVER;
- {% else %}
+ {%- else %}
nlp_solver_plan->nlp_dynamics[i] = CONTINUOUS_MODEL;
nlp_solver_plan->sim_solver_plan[i].sim_solver = {{ solver_options.integrator_type }};
{%- endif %}
@@ -200,7 +192,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
for (int i = 0; i < N; i++)
{
- {% if constraints.constr_type == "BGP" %}
+ {%- if constraints.constr_type == "BGP" %}
nlp_solver_plan->nlp_constraints[i] = BGP;
{%- else -%}
nlp_solver_plan->nlp_constraints[i] = BGH;
@@ -209,7 +201,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- if constraints.constr_type_e == "BGP" %}
nlp_solver_plan->nlp_constraints[N] = BGP;
- {% else %}
+ {%- else %}
nlp_solver_plan->nlp_constraints[N] = BGH;
{%- endif %}
@@ -226,10 +218,18 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
nlp_solver_plan->regularization = CONVEXIFY;
{%- endif %}
{%- endif %}
- ocp_nlp_config * nlp_config = ocp_nlp_config_create(*nlp_solver_plan);
- capsule->nlp_config = nlp_config;
+}
+/**
+ * Internal function for {{ model.name }}_acados_create: step 2
+ */
+ocp_nlp_dims* {{ model.name }}_acados_create_2_create_and_set_dimensions({{ model.name }}_solver_capsule* capsule)
+{
+ ocp_nlp_plan_t* nlp_solver_plan = capsule->nlp_solver_plan;
+ const int N = nlp_solver_plan->N;
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
+
/************************************************
* dimensions
************************************************/
@@ -307,7 +307,6 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
/* create and set ocp_nlp_dims */
ocp_nlp_dims * nlp_dims = ocp_nlp_dims_create(nlp_config);
- capsule->nlp_dims = nlp_dims;
ocp_nlp_dims_set_opt_vars(nlp_config, nlp_dims, "nx", nx);
ocp_nlp_dims_set_opt_vars(nlp_config, nlp_dims, "nu", nu);
@@ -325,14 +324,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, i, "nbxe", &nbxe[i]);
}
- {%- if cost.cost_type_0 == "NONLINEAR_LS" or cost.cost_type_0 == "LINEAR_LS" %}
+{%- if cost.cost_type_0 == "NONLINEAR_LS" or cost.cost_type_0 == "LINEAR_LS" %}
ocp_nlp_dims_set_cost(nlp_config, nlp_dims, 0, "ny", &ny[0]);
- {%- endif %}
+{%- endif %}
- {%- if cost.cost_type == "NONLINEAR_LS" or cost.cost_type == "LINEAR_LS" %}
+{%- if cost.cost_type == "NONLINEAR_LS" or cost.cost_type == "LINEAR_LS" %}
for (int i = 1; i < N; i++)
ocp_nlp_dims_set_cost(nlp_config, nlp_dims, i, "ny", &ny[i]);
- {%- endif %}
+{%- endif %}
for (int i = 0; i < N; i++)
{
@@ -346,21 +345,20 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endif %}
}
- {%- if constraints.constr_type_e == "BGH" %}
+{%- if constraints.constr_type_e == "BGH" %}
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, N, "nh", &nh[N]);
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, N, "nsh", &nsh[N]);
- {%- elif constraints.constr_type_e == "BGP" %}
+{%- elif constraints.constr_type_e == "BGP" %}
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, N, "nr", &nr[N]);
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, N, "nphi", &nphi[N]);
ocp_nlp_dims_set_constraints(nlp_config, nlp_dims, N, "nsphi", &nsphi[N]);
- {%- endif %}
- {%- if cost.cost_type_e == "NONLINEAR_LS" or cost.cost_type_e == "LINEAR_LS" %}
+{%- endif %}
+{%- if cost.cost_type_e == "NONLINEAR_LS" or cost.cost_type_e == "LINEAR_LS" %}
ocp_nlp_dims_set_cost(nlp_config, nlp_dims, N, "ny", &ny[N]);
- {%- endif %}
-
+{%- endif %}
free(intNp1mem);
-{% if solver_options.integrator_type == "GNSF" -%}
+{%- if solver_options.integrator_type == "GNSF" -%}
// GNSF specific dimensions
int gnsf_nx1 = {{ dims.gnsf_nx1 }};
int gnsf_nz1 = {{ dims.gnsf_nz1 }};
@@ -380,136 +378,91 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
}
{%- endif %}
+return nlp_dims;
+}
+
+
+/**
+ * Internal function for {{ model.name }}_acados_create: step 3
+ */
+void {{ model.name }}_acados_create_3_create_and_set_functions({{ model.name }}_solver_capsule* capsule)
+{
+ const int N = capsule->nlp_solver_plan->N;
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
/************************************************
* external functions
************************************************/
- {%- if constraints.constr_type == "BGP" %}
+
+#define MAP_CASADI_FNC(__CAPSULE_FNC__, __MODEL_BASE_FNC__) do{ \
+ capsule->__CAPSULE_FNC__.casadi_fun = & __MODEL_BASE_FNC__ ;\
+ capsule->__CAPSULE_FNC__.casadi_n_in = & __MODEL_BASE_FNC__ ## _n_in; \
+ capsule->__CAPSULE_FNC__.casadi_n_out = & __MODEL_BASE_FNC__ ## _n_out; \
+ capsule->__CAPSULE_FNC__.casadi_sparsity_in = & __MODEL_BASE_FNC__ ## _sparsity_in; \
+ capsule->__CAPSULE_FNC__.casadi_sparsity_out = & __MODEL_BASE_FNC__ ## _sparsity_out; \
+ capsule->__CAPSULE_FNC__.casadi_work = & __MODEL_BASE_FNC__ ## _work; \
+ external_function_param_casadi_create(&capsule->__CAPSULE_FNC__ , {{ dims.np }}); \
+ }while(false)
+
+{% if constraints.constr_type == "BGP" %}
+ // constraints.constr_type == "BGP"
capsule->phi_constraint = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++)
{
// nonlinear part of convex-composite constraint
- capsule->phi_constraint[i].casadi_fun = &{{ model.name }}_phi_constraint;
- capsule->phi_constraint[i].casadi_n_in = &{{ model.name }}_phi_constraint_n_in;
- capsule->phi_constraint[i].casadi_n_out = &{{ model.name }}_phi_constraint_n_out;
- capsule->phi_constraint[i].casadi_sparsity_in = &{{ model.name }}_phi_constraint_sparsity_in;
- capsule->phi_constraint[i].casadi_sparsity_out = &{{ model.name }}_phi_constraint_sparsity_out;
- capsule->phi_constraint[i].casadi_work = &{{ model.name }}_phi_constraint_work;
-
- external_function_param_casadi_create(&capsule->phi_constraint[i], {{ dims.np }});
+ MAP_CASADI_FNC(phi_constraint[i], {{ model.name }}_phi_constraint);
}
- {%- endif %}
+{%- endif %}
- {%- if constraints.constr_type_e == "BGP" %}
+{%- if constraints.constr_type_e == "BGP" %}
+ // constraints.constr_type_e == "BGP"
// nonlinear part of convex-composite constraint
- capsule->phi_e_constraint.casadi_fun = &{{ model.name }}_phi_e_constraint;
- capsule->phi_e_constraint.casadi_n_in = &{{ model.name }}_phi_e_constraint_n_in;
- capsule->phi_e_constraint.casadi_n_out = &{{ model.name }}_phi_e_constraint_n_out;
- capsule->phi_e_constraint.casadi_sparsity_in = &{{ model.name }}_phi_e_constraint_sparsity_in;
- capsule->phi_e_constraint.casadi_sparsity_out = &{{ model.name }}_phi_e_constraint_sparsity_out;
- capsule->phi_e_constraint.casadi_work = &{{ model.name }}_phi_e_constraint_work;
-
- external_function_param_casadi_create(&capsule->phi_e_constraint, {{ dims.np }});
- {% endif %}
+ MAP_CASADI_FNC(phi_e_constraint, {{ model.name }}_phi_e_constraint);
+{%- endif %}
- {%- if constraints.constr_type == "BGH" and dims.nh > 0 %}
+{%- if constraints.constr_type == "BGH" and dims.nh > 0 %}
+ // constraints.constr_type == "BGH" and dims.nh > 0
capsule->nl_constr_h_fun_jac = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->nl_constr_h_fun_jac[i].casadi_fun = &{{ model.name }}_constr_h_fun_jac_uxt_zt;
- capsule->nl_constr_h_fun_jac[i].casadi_n_in = &{{ model.name }}_constr_h_fun_jac_uxt_zt_n_in;
- capsule->nl_constr_h_fun_jac[i].casadi_n_out = &{{ model.name }}_constr_h_fun_jac_uxt_zt_n_out;
- capsule->nl_constr_h_fun_jac[i].casadi_sparsity_in = &{{ model.name }}_constr_h_fun_jac_uxt_zt_sparsity_in;
- capsule->nl_constr_h_fun_jac[i].casadi_sparsity_out = &{{ model.name }}_constr_h_fun_jac_uxt_zt_sparsity_out;
- capsule->nl_constr_h_fun_jac[i].casadi_work = &{{ model.name }}_constr_h_fun_jac_uxt_zt_work;
- external_function_param_casadi_create(&capsule->nl_constr_h_fun_jac[i], {{ dims.np }});
+ MAP_CASADI_FNC(nl_constr_h_fun_jac[i], {{ model.name }}_constr_h_fun_jac_uxt_zt);
}
capsule->nl_constr_h_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->nl_constr_h_fun[i].casadi_fun = &{{ model.name }}_constr_h_fun;
- capsule->nl_constr_h_fun[i].casadi_n_in = &{{ model.name }}_constr_h_fun_n_in;
- capsule->nl_constr_h_fun[i].casadi_n_out = &{{ model.name }}_constr_h_fun_n_out;
- capsule->nl_constr_h_fun[i].casadi_sparsity_in = &{{ model.name }}_constr_h_fun_sparsity_in;
- capsule->nl_constr_h_fun[i].casadi_sparsity_out = &{{ model.name }}_constr_h_fun_sparsity_out;
- capsule->nl_constr_h_fun[i].casadi_work = &{{ model.name }}_constr_h_fun_work;
- external_function_param_casadi_create(&capsule->nl_constr_h_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(nl_constr_h_fun[i], {{ model.name }}_constr_h_fun);
}
{% if solver_options.hessian_approx == "EXACT" %}
capsule->nl_constr_h_fun_jac_hess = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->nl_constr_h_fun_jac_hess[i].casadi_fun = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess;
- capsule->nl_constr_h_fun_jac_hess[i].casadi_n_in = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess_n_in;
- capsule->nl_constr_h_fun_jac_hess[i].casadi_n_out = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess_n_out;
- capsule->nl_constr_h_fun_jac_hess[i].casadi_sparsity_in = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess_sparsity_in;
- capsule->nl_constr_h_fun_jac_hess[i].casadi_sparsity_out = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess_sparsity_out;
- capsule->nl_constr_h_fun_jac_hess[i].casadi_work = &{{ model.name }}_constr_h_fun_jac_uxt_zt_hess_work;
-
- external_function_param_casadi_create(&capsule->nl_constr_h_fun_jac_hess[i], {{ dims.np }});
+ MAP_CASADI_FNC(nl_constr_h_fun_jac_hess[i], {{ model.name }}_constr_h_fun_jac_uxt_zt_hess);
}
{% endif %}
- {% endif %}
+{% endif %}
- {%- if constraints.constr_type_e == "BGH" and dims.nh_e > 0 %}
- capsule->nl_constr_h_e_fun_jac.casadi_fun = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt;
- capsule->nl_constr_h_e_fun_jac.casadi_n_in = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_n_in;
- capsule->nl_constr_h_e_fun_jac.casadi_n_out = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_n_out;
- capsule->nl_constr_h_e_fun_jac.casadi_sparsity_in = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_sparsity_in;
- capsule->nl_constr_h_e_fun_jac.casadi_sparsity_out = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_sparsity_out;
- capsule->nl_constr_h_e_fun_jac.casadi_work = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_work;
- external_function_param_casadi_create(&capsule->nl_constr_h_e_fun_jac, {{ dims.np }});
-
- capsule->nl_constr_h_e_fun.casadi_fun = &{{ model.name }}_constr_h_e_fun;
- capsule->nl_constr_h_e_fun.casadi_n_in = &{{ model.name }}_constr_h_e_fun_n_in;
- capsule->nl_constr_h_e_fun.casadi_n_out = &{{ model.name }}_constr_h_e_fun_n_out;
- capsule->nl_constr_h_e_fun.casadi_sparsity_in = &{{ model.name }}_constr_h_e_fun_sparsity_in;
- capsule->nl_constr_h_e_fun.casadi_sparsity_out = &{{ model.name }}_constr_h_e_fun_sparsity_out;
- capsule->nl_constr_h_e_fun.casadi_work = &{{ model.name }}_constr_h_e_fun_work;
- external_function_param_casadi_create(&capsule->nl_constr_h_e_fun, {{ dims.np }});
+{%- if constraints.constr_type_e == "BGH" and dims.nh_e > 0 %}
+ MAP_CASADI_FNC(nl_constr_h_e_fun_jac, {{ model.name }}_constr_h_e_fun_jac_uxt_zt);
+ MAP_CASADI_FNC(nl_constr_h_e_fun, {{ model.name }}_constr_h_e_fun);
- {% if solver_options.hessian_approx == "EXACT" %}
- capsule->nl_constr_h_e_fun_jac_hess.casadi_fun = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess;
- capsule->nl_constr_h_e_fun_jac_hess.casadi_n_in = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess_n_in;
- capsule->nl_constr_h_e_fun_jac_hess.casadi_n_out = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess_n_out;
- capsule->nl_constr_h_e_fun_jac_hess.casadi_sparsity_in = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess_sparsity_in;
- capsule->nl_constr_h_e_fun_jac_hess.casadi_sparsity_out = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess_sparsity_out;
- capsule->nl_constr_h_e_fun_jac_hess.casadi_work = &{{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess_work;
- external_function_param_casadi_create(&capsule->nl_constr_h_e_fun_jac_hess, {{ dims.np }});
+ {%- if solver_options.hessian_approx == "EXACT" %}
+ MAP_CASADI_FNC(nl_constr_h_e_fun_jac_hess, {{ model.name }}_constr_h_e_fun_jac_uxt_zt_hess);
{% endif %}
- {%- endif %}
+{%- endif %}
{% if solver_options.integrator_type == "ERK" %}
// explicit ode
capsule->forw_vde_casadi = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->forw_vde_casadi[i].casadi_fun = &{{ model.name }}_expl_vde_forw;
- capsule->forw_vde_casadi[i].casadi_n_in = &{{ model.name }}_expl_vde_forw_n_in;
- capsule->forw_vde_casadi[i].casadi_n_out = &{{ model.name }}_expl_vde_forw_n_out;
- capsule->forw_vde_casadi[i].casadi_sparsity_in = &{{ model.name }}_expl_vde_forw_sparsity_in;
- capsule->forw_vde_casadi[i].casadi_sparsity_out = &{{ model.name }}_expl_vde_forw_sparsity_out;
- capsule->forw_vde_casadi[i].casadi_work = &{{ model.name }}_expl_vde_forw_work;
- external_function_param_casadi_create(&capsule->forw_vde_casadi[i], {{ dims.np }});
+ MAP_CASADI_FNC(forw_vde_casadi[i], {{ model.name }}_expl_vde_forw);
}
capsule->expl_ode_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->expl_ode_fun[i].casadi_fun = &{{ model.name }}_expl_ode_fun;
- capsule->expl_ode_fun[i].casadi_n_in = &{{ model.name }}_expl_ode_fun_n_in;
- capsule->expl_ode_fun[i].casadi_n_out = &{{ model.name }}_expl_ode_fun_n_out;
- capsule->expl_ode_fun[i].casadi_sparsity_in = &{{ model.name }}_expl_ode_fun_sparsity_in;
- capsule->expl_ode_fun[i].casadi_sparsity_out = &{{ model.name }}_expl_ode_fun_sparsity_out;
- capsule->expl_ode_fun[i].casadi_work = &{{ model.name }}_expl_ode_fun_work;
- external_function_param_casadi_create(&capsule->expl_ode_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(expl_ode_fun[i], {{ model.name }}_expl_ode_fun);
}
{%- if solver_options.hessian_approx == "EXACT" %}
capsule->hess_vde_casadi = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->hess_vde_casadi[i].casadi_fun = &{{ model.name }}_expl_ode_hess;
- capsule->hess_vde_casadi[i].casadi_n_in = &{{ model.name }}_expl_ode_hess_n_in;
- capsule->hess_vde_casadi[i].casadi_n_out = &{{ model.name }}_expl_ode_hess_n_out;
- capsule->hess_vde_casadi[i].casadi_sparsity_in = &{{ model.name }}_expl_ode_hess_sparsity_in;
- capsule->hess_vde_casadi[i].casadi_sparsity_out = &{{ model.name }}_expl_ode_hess_sparsity_out;
- capsule->hess_vde_casadi[i].casadi_work = &{{ model.name }}_expl_ode_hess_work;
- external_function_param_casadi_create(&capsule->hess_vde_casadi[i], {{ dims.np }});
+ MAP_CASADI_FNC(hess_vde_casadi[i], {{ model.name }}_expl_ode_hess);
}
{%- endif %}
@@ -517,126 +470,64 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
// implicit dae
capsule->impl_dae_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->impl_dae_fun[i].casadi_fun = &{{ model.name }}_impl_dae_fun;
- capsule->impl_dae_fun[i].casadi_work = &{{ model.name }}_impl_dae_fun_work;
- capsule->impl_dae_fun[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_fun_sparsity_in;
- capsule->impl_dae_fun[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_fun_sparsity_out;
- capsule->impl_dae_fun[i].casadi_n_in = &{{ model.name }}_impl_dae_fun_n_in;
- capsule->impl_dae_fun[i].casadi_n_out = &{{ model.name }}_impl_dae_fun_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(impl_dae_fun[i], {{ model.name }}_impl_dae_fun);
}
capsule->impl_dae_fun_jac_x_xdot_z = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_fun = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z;
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_work = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z_work;
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z_sparsity_in;
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z_sparsity_out;
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_n_in = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z_n_in;
- capsule->impl_dae_fun_jac_x_xdot_z[i].casadi_n_out = &{{ model.name }}_impl_dae_fun_jac_x_xdot_z_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_fun_jac_x_xdot_z[i], {{ dims.np }});
+ MAP_CASADI_FNC(impl_dae_fun_jac_x_xdot_z[i], {{ model.name }}_impl_dae_fun_jac_x_xdot_z);
}
capsule->impl_dae_jac_x_xdot_u_z = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_fun = &{{ model.name }}_impl_dae_jac_x_xdot_u_z;
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_work = &{{ model.name }}_impl_dae_jac_x_xdot_u_z_work;
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_jac_x_xdot_u_z_sparsity_in;
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_jac_x_xdot_u_z_sparsity_out;
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_n_in = &{{ model.name }}_impl_dae_jac_x_xdot_u_z_n_in;
- capsule->impl_dae_jac_x_xdot_u_z[i].casadi_n_out = &{{ model.name }}_impl_dae_jac_x_xdot_u_z_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_jac_x_xdot_u_z[i], {{ dims.np }});
+ MAP_CASADI_FNC(impl_dae_jac_x_xdot_u_z[i], {{ model.name }}_impl_dae_jac_x_xdot_u_z);
}
{%- if solver_options.hessian_approx == "EXACT" %}
capsule->impl_dae_hess = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->impl_dae_hess[i].casadi_fun = &{{ model.name }}_impl_dae_hess;
- capsule->impl_dae_hess[i].casadi_work = &{{ model.name }}_impl_dae_hess_work;
- capsule->impl_dae_hess[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_hess_sparsity_in;
- capsule->impl_dae_hess[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_hess_sparsity_out;
- capsule->impl_dae_hess[i].casadi_n_in = &{{ model.name }}_impl_dae_hess_n_in;
- capsule->impl_dae_hess[i].casadi_n_out = &{{ model.name }}_impl_dae_hess_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_hess[i], {{ dims.np }});
+ MAP_CASADI_FNC(impl_dae_hess[i], {{ model.name }}_impl_dae_hess);
}
{%- endif %}
{% elif solver_options.integrator_type == "LIFTED_IRK" %}
// external functions (implicit model)
capsule->impl_dae_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->impl_dae_fun[i].casadi_fun = &{{ model.name }}_impl_dae_fun;
- capsule->impl_dae_fun[i].casadi_work = &{{ model.name }}_impl_dae_fun_work;
- capsule->impl_dae_fun[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_fun_sparsity_in;
- capsule->impl_dae_fun[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_fun_sparsity_out;
- capsule->impl_dae_fun[i].casadi_n_in = &{{ model.name }}_impl_dae_fun_n_in;
- capsule->impl_dae_fun[i].casadi_n_out = &{{ model.name }}_impl_dae_fun_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(impl_dae_fun[i], {{ model.name }}_impl_dae_fun);
}
- capsule->impl_dae_fun_jac_x_xdot_u = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N); for (int i = 0; i < N; i++) {
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_fun = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u;
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_work = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u_work;
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_sparsity_in = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u_sparsity_in;
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_sparsity_out = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u_sparsity_out;
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_n_in = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u_n_in;
- capsule->impl_dae_fun_jac_x_xdot_u[i].casadi_n_out = &{{ model.name }}_impl_dae_fun_jac_x_xdot_u_n_out;
- external_function_param_casadi_create(&capsule->impl_dae_fun_jac_x_xdot_u[i], {{ dims.np }});
+ capsule->impl_dae_fun_jac_x_xdot_u = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
+ for (int i = 0; i < N; i++) {
+ MAP_CASADI_FNC(impl_dae_fun_jac_x_xdot_u[i], {{ model.name }}_impl_dae_fun_jac_x_xdot_u);
}
{% elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
capsule->gnsf_phi_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->gnsf_phi_fun[i].casadi_fun = &{{ model.name }}_gnsf_phi_fun;
- capsule->gnsf_phi_fun[i].casadi_work = &{{ model.name }}_gnsf_phi_fun_work;
- capsule->gnsf_phi_fun[i].casadi_sparsity_in = &{{ model.name }}_gnsf_phi_fun_sparsity_in;
- capsule->gnsf_phi_fun[i].casadi_sparsity_out = &{{ model.name }}_gnsf_phi_fun_sparsity_out;
- capsule->gnsf_phi_fun[i].casadi_n_in = &{{ model.name }}_gnsf_phi_fun_n_in;
- capsule->gnsf_phi_fun[i].casadi_n_out = &{{ model.name }}_gnsf_phi_fun_n_out;
- external_function_param_casadi_create(&capsule->gnsf_phi_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(gnsf_phi_fun[i], {{ model.name }}_gnsf_phi_fun);
}
capsule->gnsf_phi_fun_jac_y = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->gnsf_phi_fun_jac_y[i].casadi_fun = &{{ model.name }}_gnsf_phi_fun_jac_y;
- capsule->gnsf_phi_fun_jac_y[i].casadi_work = &{{ model.name }}_gnsf_phi_fun_jac_y_work;
- capsule->gnsf_phi_fun_jac_y[i].casadi_sparsity_in = &{{ model.name }}_gnsf_phi_fun_jac_y_sparsity_in;
- capsule->gnsf_phi_fun_jac_y[i].casadi_sparsity_out = &{{ model.name }}_gnsf_phi_fun_jac_y_sparsity_out;
- capsule->gnsf_phi_fun_jac_y[i].casadi_n_in = &{{ model.name }}_gnsf_phi_fun_jac_y_n_in;
- capsule->gnsf_phi_fun_jac_y[i].casadi_n_out = &{{ model.name }}_gnsf_phi_fun_jac_y_n_out;
- external_function_param_casadi_create(&capsule->gnsf_phi_fun_jac_y[i], {{ dims.np }});
+ MAP_CASADI_FNC(gnsf_phi_fun_jac_y[i], {{ model.name }}_gnsf_phi_fun_jac_y);
}
capsule->gnsf_phi_jac_y_uhat = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->gnsf_phi_jac_y_uhat[i].casadi_fun = &{{ model.name }}_gnsf_phi_jac_y_uhat;
- capsule->gnsf_phi_jac_y_uhat[i].casadi_work = &{{ model.name }}_gnsf_phi_jac_y_uhat_work;
- capsule->gnsf_phi_jac_y_uhat[i].casadi_sparsity_in = &{{ model.name }}_gnsf_phi_jac_y_uhat_sparsity_in;
- capsule->gnsf_phi_jac_y_uhat[i].casadi_sparsity_out = &{{ model.name }}_gnsf_phi_jac_y_uhat_sparsity_out;
- capsule->gnsf_phi_jac_y_uhat[i].casadi_n_in = &{{ model.name }}_gnsf_phi_jac_y_uhat_n_in;
- capsule->gnsf_phi_jac_y_uhat[i].casadi_n_out = &{{ model.name }}_gnsf_phi_jac_y_uhat_n_out;
- external_function_param_casadi_create(&capsule->gnsf_phi_jac_y_uhat[i], {{ dims.np }});
+ MAP_CASADI_FNC(gnsf_phi_jac_y_uhat[i], {{ model.name }}_gnsf_phi_jac_y_uhat);
}
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
capsule->gnsf_f_lo_jac_x1_x1dot_u_z = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_fun = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz;
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_work = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_work;
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_sparsity_in = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_sparsity_in;
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_sparsity_out = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_sparsity_out;
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_n_in = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_in;
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i].casadi_n_out = &{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_out;
- external_function_param_casadi_create(&capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i], {{ dims.np }});
+ MAP_CASADI_FNC(gnsf_f_lo_jac_x1_x1dot_u_z[i], {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz);
}
-
+ {%- endif %}
+ {%- endif %}
capsule->gnsf_get_matrices_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N; i++) {
- capsule->gnsf_get_matrices_fun[i].casadi_fun = &{{ model.name }}_gnsf_get_matrices_fun;
- capsule->gnsf_get_matrices_fun[i].casadi_work = &{{ model.name }}_gnsf_get_matrices_fun_work;
- capsule->gnsf_get_matrices_fun[i].casadi_sparsity_in = &{{ model.name }}_gnsf_get_matrices_fun_sparsity_in;
- capsule->gnsf_get_matrices_fun[i].casadi_sparsity_out = &{{ model.name }}_gnsf_get_matrices_fun_sparsity_out;
- capsule->gnsf_get_matrices_fun[i].casadi_n_in = &{{ model.name }}_gnsf_get_matrices_fun_n_in;
- capsule->gnsf_get_matrices_fun[i].casadi_n_out = &{{ model.name }}_gnsf_get_matrices_fun_n_out;
- external_function_param_casadi_create(&capsule->gnsf_get_matrices_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(gnsf_get_matrices_fun[i], {{ model.name }}_gnsf_get_matrices_fun);
}
{% elif solver_options.integrator_type == "DISCRETE" %}
// discrete dynamics
@@ -644,32 +535,22 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
for (int i = 0; i < N; i++)
{
{%- if model.dyn_ext_fun_type == "casadi" %}
- capsule->discr_dyn_phi_fun[i].casadi_fun = &{{ model.name }}_dyn_disc_phi_fun;
- capsule->discr_dyn_phi_fun[i].casadi_n_in = &{{ model.name }}_dyn_disc_phi_fun_n_in;
- capsule->discr_dyn_phi_fun[i].casadi_n_out = &{{ model.name }}_dyn_disc_phi_fun_n_out;
- capsule->discr_dyn_phi_fun[i].casadi_sparsity_in = &{{ model.name }}_dyn_disc_phi_fun_sparsity_in;
- capsule->discr_dyn_phi_fun[i].casadi_sparsity_out = &{{ model.name }}_dyn_disc_phi_fun_sparsity_out;
- capsule->discr_dyn_phi_fun[i].casadi_work = &{{ model.name }}_dyn_disc_phi_fun_work;
+ MAP_CASADI_FNC(discr_dyn_phi_fun[i], {{ model.name }}_dyn_disc_phi_fun);
{%- else %}
capsule->discr_dyn_phi_fun[i].fun = &{{ model.dyn_disc_fun }};
- {%- endif %}
external_function_param_{{ model.dyn_ext_fun_type }}_create(&capsule->discr_dyn_phi_fun[i], {{ dims.np }});
+ {%- endif %}
}
capsule->discr_dyn_phi_fun_jac_ut_xt = (external_function_param_{{ model.dyn_ext_fun_type }} *) malloc(sizeof(external_function_param_{{ model.dyn_ext_fun_type }})*N);
for (int i = 0; i < N; i++)
{
{%- if model.dyn_ext_fun_type == "casadi" %}
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_fun = &{{ model.name }}_dyn_disc_phi_fun_jac;
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_n_in = &{{ model.name }}_dyn_disc_phi_fun_jac_n_in;
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_n_out = &{{ model.name }}_dyn_disc_phi_fun_jac_n_out;
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_sparsity_in = &{{ model.name }}_dyn_disc_phi_fun_jac_sparsity_in;
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_sparsity_out = &{{ model.name }}_dyn_disc_phi_fun_jac_sparsity_out;
- capsule->discr_dyn_phi_fun_jac_ut_xt[i].casadi_work = &{{ model.name }}_dyn_disc_phi_fun_jac_work;
+ MAP_CASADI_FNC(discr_dyn_phi_fun_jac_ut_xt[i], {{ model.name }}_dyn_disc_phi_fun_jac);
{%- else %}
capsule->discr_dyn_phi_fun_jac_ut_xt[i].fun = &{{ model.dyn_disc_fun_jac }};
- {%- endif %}
external_function_param_{{ model.dyn_ext_fun_type }}_create(&capsule->discr_dyn_phi_fun_jac_ut_xt[i], {{ dims.np }});
+ {%- endif %}
}
{%- if solver_options.hessian_approx == "EXACT" %}
@@ -677,86 +558,46 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
for (int i = 0; i < N; i++)
{
{%- if model.dyn_ext_fun_type == "casadi" %}
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_fun = &{{ model.name }}_dyn_disc_phi_fun_jac_hess;
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_n_in = &{{ model.name }}_dyn_disc_phi_fun_jac_hess_n_in;
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_n_out = &{{ model.name }}_dyn_disc_phi_fun_jac_hess_n_out;
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_sparsity_in = &{{ model.name }}_dyn_disc_phi_fun_jac_hess_sparsity_in;
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_sparsity_out = &{{ model.name }}_dyn_disc_phi_fun_jac_hess_sparsity_out;
- capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].casadi_work = &{{ model.name }}_dyn_disc_phi_fun_jac_hess_work;
+ MAP_CASADI_FNC(discr_dyn_phi_fun_jac_ut_xt_hess[i], {{ model.name }}_dyn_disc_phi_fun_jac_hess);
{%- else %}
capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i].fun = &{{ model.dyn_disc_fun_jac_hess }};
- {%- endif %}
external_function_param_{{ model.dyn_ext_fun_type }}_create(&capsule->discr_dyn_phi_fun_jac_ut_xt_hess[i], {{ dims.np }});
+ {%- endif %}
}
{%- endif %}
{%- endif %}
{%- if cost.cost_type_0 == "NONLINEAR_LS" %}
- // nonlinear least square function
- capsule->cost_y_0_fun.casadi_fun = &{{ model.name }}_cost_y_0_fun;
- capsule->cost_y_0_fun.casadi_n_in = &{{ model.name }}_cost_y_0_fun_n_in;
- capsule->cost_y_0_fun.casadi_n_out = &{{ model.name }}_cost_y_0_fun_n_out;
- capsule->cost_y_0_fun.casadi_sparsity_in = &{{ model.name }}_cost_y_0_fun_sparsity_in;
- capsule->cost_y_0_fun.casadi_sparsity_out = &{{ model.name }}_cost_y_0_fun_sparsity_out;
- capsule->cost_y_0_fun.casadi_work = &{{ model.name }}_cost_y_0_fun_work;
- external_function_param_casadi_create(&capsule->cost_y_0_fun, {{ dims.np }});
-
- capsule->cost_y_0_fun_jac_ut_xt.casadi_fun = &{{ model.name }}_cost_y_0_fun_jac_ut_xt;
- capsule->cost_y_0_fun_jac_ut_xt.casadi_n_in = &{{ model.name }}_cost_y_0_fun_jac_ut_xt_n_in;
- capsule->cost_y_0_fun_jac_ut_xt.casadi_n_out = &{{ model.name }}_cost_y_0_fun_jac_ut_xt_n_out;
- capsule->cost_y_0_fun_jac_ut_xt.casadi_sparsity_in = &{{ model.name }}_cost_y_0_fun_jac_ut_xt_sparsity_in;
- capsule->cost_y_0_fun_jac_ut_xt.casadi_sparsity_out = &{{ model.name }}_cost_y_0_fun_jac_ut_xt_sparsity_out;
- capsule->cost_y_0_fun_jac_ut_xt.casadi_work = &{{ model.name }}_cost_y_0_fun_jac_ut_xt_work;
- external_function_param_casadi_create(&capsule->cost_y_0_fun_jac_ut_xt, {{ dims.np }});
-
- capsule->cost_y_0_hess.casadi_fun = &{{ model.name }}_cost_y_0_hess;
- capsule->cost_y_0_hess.casadi_n_in = &{{ model.name }}_cost_y_0_hess_n_in;
- capsule->cost_y_0_hess.casadi_n_out = &{{ model.name }}_cost_y_0_hess_n_out;
- capsule->cost_y_0_hess.casadi_sparsity_in = &{{ model.name }}_cost_y_0_hess_sparsity_in;
- capsule->cost_y_0_hess.casadi_sparsity_out = &{{ model.name }}_cost_y_0_hess_sparsity_out;
- capsule->cost_y_0_hess.casadi_work = &{{ model.name }}_cost_y_0_hess_work;
- external_function_param_casadi_create(&capsule->cost_y_0_hess, {{ dims.np }});
+ // nonlinear least squares function
+ MAP_CASADI_FNC(cost_y_0_fun, {{ model.name }}_cost_y_0_fun);
+ MAP_CASADI_FNC(cost_y_0_fun_jac_ut_xt, {{ model.name }}_cost_y_0_fun_jac_ut_xt);
+ MAP_CASADI_FNC(cost_y_0_hess, {{ model.name }}_cost_y_0_hess);
{%- elif cost.cost_type_0 == "EXTERNAL" %}
// external cost
- {% if cost.cost_ext_fun_type_0 == "casadi" %}
- capsule->ext_cost_0_fun.casadi_fun = &{{ model.name }}_cost_ext_cost_0_fun;
- capsule->ext_cost_0_fun.casadi_n_in = &{{ model.name }}_cost_ext_cost_0_fun_n_in;
- capsule->ext_cost_0_fun.casadi_n_out = &{{ model.name }}_cost_ext_cost_0_fun_n_out;
- capsule->ext_cost_0_fun.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_0_fun_sparsity_in;
- capsule->ext_cost_0_fun.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_0_fun_sparsity_out;
- capsule->ext_cost_0_fun.casadi_work = &{{ model.name }}_cost_ext_cost_0_fun_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type_0 == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_0_fun, {{ model.name }}_cost_ext_cost_0_fun);
+ {%- else %}
capsule->ext_cost_0_fun.fun = &{{ cost.cost_function_ext_cost_0 }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_0 }}_create(&capsule->ext_cost_0_fun, {{ dims.np }});
+ {%- endif %}
// external cost
- {% if cost.cost_ext_fun_type_0 == "casadi" %}
- capsule->ext_cost_0_fun_jac.casadi_fun = &{{ model.name }}_cost_ext_cost_0_fun_jac;
- capsule->ext_cost_0_fun_jac.casadi_n_in = &{{ model.name }}_cost_ext_cost_0_fun_jac_n_in;
- capsule->ext_cost_0_fun_jac.casadi_n_out = &{{ model.name }}_cost_ext_cost_0_fun_jac_n_out;
- capsule->ext_cost_0_fun_jac.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_0_fun_jac_sparsity_in;
- capsule->ext_cost_0_fun_jac.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_0_fun_jac_sparsity_out;
- capsule->ext_cost_0_fun_jac.casadi_work = &{{ model.name }}_cost_ext_cost_0_fun_jac_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type_0 == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_0_fun_jac, {{ model.name }}_cost_ext_cost_0_fun_jac);
+ {%- else %}
capsule->ext_cost_0_fun_jac.fun = &{{ cost.cost_function_ext_cost_0 }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_0 }}_create(&capsule->ext_cost_0_fun_jac, {{ dims.np }});
+ {%- endif %}
// external cost
- {% if cost.cost_ext_fun_type_0 == "casadi" %}
- capsule->ext_cost_0_fun_jac_hess.casadi_fun = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess;
- capsule->ext_cost_0_fun_jac_hess.casadi_n_in = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess_n_in;
- capsule->ext_cost_0_fun_jac_hess.casadi_n_out = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess_n_out;
- capsule->ext_cost_0_fun_jac_hess.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess_sparsity_in;
- capsule->ext_cost_0_fun_jac_hess.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess_sparsity_out;
- capsule->ext_cost_0_fun_jac_hess.casadi_work = &{{ model.name }}_cost_ext_cost_0_fun_jac_hess_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type_0 == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_0_fun_jac_hess, {{ model.name }}_cost_ext_cost_0_fun_jac_hess);
+ {%- else %}
capsule->ext_cost_0_fun_jac_hess.fun = &{{ cost.cost_function_ext_cost_0 }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_0 }}_create(&capsule->ext_cost_0_fun_jac_hess, {{ dims.np }});
+ {%- endif %}
{%- endif %}
{%- if cost.cost_type == "NONLINEAR_LS" %}
@@ -764,164 +605,130 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
capsule->cost_y_fun = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N-1; i++)
{
- capsule->cost_y_fun[i].casadi_fun = &{{ model.name }}_cost_y_fun;
- capsule->cost_y_fun[i].casadi_n_in = &{{ model.name }}_cost_y_fun_n_in;
- capsule->cost_y_fun[i].casadi_n_out = &{{ model.name }}_cost_y_fun_n_out;
- capsule->cost_y_fun[i].casadi_sparsity_in = &{{ model.name }}_cost_y_fun_sparsity_in;
- capsule->cost_y_fun[i].casadi_sparsity_out = &{{ model.name }}_cost_y_fun_sparsity_out;
- capsule->cost_y_fun[i].casadi_work = &{{ model.name }}_cost_y_fun_work;
-
- external_function_param_casadi_create(&capsule->cost_y_fun[i], {{ dims.np }});
+ MAP_CASADI_FNC(cost_y_fun[i], {{ model.name }}_cost_y_fun);
}
capsule->cost_y_fun_jac_ut_xt = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N-1; i++)
{
- capsule->cost_y_fun_jac_ut_xt[i].casadi_fun = &{{ model.name }}_cost_y_fun_jac_ut_xt;
- capsule->cost_y_fun_jac_ut_xt[i].casadi_n_in = &{{ model.name }}_cost_y_fun_jac_ut_xt_n_in;
- capsule->cost_y_fun_jac_ut_xt[i].casadi_n_out = &{{ model.name }}_cost_y_fun_jac_ut_xt_n_out;
- capsule->cost_y_fun_jac_ut_xt[i].casadi_sparsity_in = &{{ model.name }}_cost_y_fun_jac_ut_xt_sparsity_in;
- capsule->cost_y_fun_jac_ut_xt[i].casadi_sparsity_out = &{{ model.name }}_cost_y_fun_jac_ut_xt_sparsity_out;
- capsule->cost_y_fun_jac_ut_xt[i].casadi_work = &{{ model.name }}_cost_y_fun_jac_ut_xt_work;
-
- external_function_param_casadi_create(&capsule->cost_y_fun_jac_ut_xt[i], {{ dims.np }});
+ MAP_CASADI_FNC(cost_y_fun_jac_ut_xt[i], {{ model.name }}_cost_y_fun_jac_ut_xt);
}
capsule->cost_y_hess = (external_function_param_casadi *) malloc(sizeof(external_function_param_casadi)*N);
for (int i = 0; i < N-1; i++)
{
- capsule->cost_y_hess[i].casadi_fun = &{{ model.name }}_cost_y_hess;
- capsule->cost_y_hess[i].casadi_n_in = &{{ model.name }}_cost_y_hess_n_in;
- capsule->cost_y_hess[i].casadi_n_out = &{{ model.name }}_cost_y_hess_n_out;
- capsule->cost_y_hess[i].casadi_sparsity_in = &{{ model.name }}_cost_y_hess_sparsity_in;
- capsule->cost_y_hess[i].casadi_sparsity_out = &{{ model.name }}_cost_y_hess_sparsity_out;
- capsule->cost_y_hess[i].casadi_work = &{{ model.name }}_cost_y_hess_work;
-
- external_function_param_casadi_create(&capsule->cost_y_hess[i], {{ dims.np }});
+ MAP_CASADI_FNC(cost_y_hess[i], {{ model.name }}_cost_y_hess);
}
{%- elif cost.cost_type == "EXTERNAL" %}
// external cost
capsule->ext_cost_fun = (external_function_param_{{ cost.cost_ext_fun_type }} *) malloc(sizeof(external_function_param_{{ cost.cost_ext_fun_type }})*N);
for (int i = 0; i < N-1; i++)
{
- {% if cost.cost_ext_fun_type == "casadi" %}
- capsule->ext_cost_fun[i].casadi_fun = &{{ model.name }}_cost_ext_cost_fun;
- capsule->ext_cost_fun[i].casadi_n_in = &{{ model.name }}_cost_ext_cost_fun_n_in;
- capsule->ext_cost_fun[i].casadi_n_out = &{{ model.name }}_cost_ext_cost_fun_n_out;
- capsule->ext_cost_fun[i].casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_fun_sparsity_in;
- capsule->ext_cost_fun[i].casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_fun_sparsity_out;
- capsule->ext_cost_fun[i].casadi_work = &{{ model.name }}_cost_ext_cost_fun_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_fun[i], {{ model.name }}_cost_ext_cost_fun);
+ {%- else %}
capsule->ext_cost_fun[i].fun = &{{ cost.cost_function_ext_cost }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type }}_create(&capsule->ext_cost_fun[i], {{ dims.np }});
+ {%- endif %}
}
capsule->ext_cost_fun_jac = (external_function_param_{{ cost.cost_ext_fun_type }} *) malloc(sizeof(external_function_param_{{ cost.cost_ext_fun_type }})*N);
for (int i = 0; i < N-1; i++)
{
- {% if cost.cost_ext_fun_type == "casadi" %}
- capsule->ext_cost_fun_jac[i].casadi_fun = &{{ model.name }}_cost_ext_cost_fun_jac;
- capsule->ext_cost_fun_jac[i].casadi_n_in = &{{ model.name }}_cost_ext_cost_fun_jac_n_in;
- capsule->ext_cost_fun_jac[i].casadi_n_out = &{{ model.name }}_cost_ext_cost_fun_jac_n_out;
- capsule->ext_cost_fun_jac[i].casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_fun_jac_sparsity_in;
- capsule->ext_cost_fun_jac[i].casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_fun_jac_sparsity_out;
- capsule->ext_cost_fun_jac[i].casadi_work = &{{ model.name }}_cost_ext_cost_fun_jac_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_fun_jac[i], {{ model.name }}_cost_ext_cost_fun_jac);
+ {%- else %}
capsule->ext_cost_fun_jac[i].fun = &{{ cost.cost_function_ext_cost }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type }}_create(&capsule->ext_cost_fun_jac[i], {{ dims.np }});
+ {%- endif %}
}
capsule->ext_cost_fun_jac_hess = (external_function_param_{{ cost.cost_ext_fun_type }} *) malloc(sizeof(external_function_param_{{ cost.cost_ext_fun_type }})*N);
for (int i = 0; i < N-1; i++)
{
- {% if cost.cost_ext_fun_type == "casadi" %}
- capsule->ext_cost_fun_jac_hess[i].casadi_fun = &{{ model.name }}_cost_ext_cost_fun_jac_hess;
- capsule->ext_cost_fun_jac_hess[i].casadi_n_in = &{{ model.name }}_cost_ext_cost_fun_jac_hess_n_in;
- capsule->ext_cost_fun_jac_hess[i].casadi_n_out = &{{ model.name }}_cost_ext_cost_fun_jac_hess_n_out;
- capsule->ext_cost_fun_jac_hess[i].casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_fun_jac_hess_sparsity_in;
- capsule->ext_cost_fun_jac_hess[i].casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_fun_jac_hess_sparsity_out;
- capsule->ext_cost_fun_jac_hess[i].casadi_work = &{{ model.name }}_cost_ext_cost_fun_jac_hess_work;
- {% else %}
+ {%- if cost.cost_ext_fun_type == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_fun_jac_hess[i], {{ model.name }}_cost_ext_cost_fun_jac_hess);
+ {%- else %}
capsule->ext_cost_fun_jac_hess[i].fun = &{{ cost.cost_function_ext_cost }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type }}_create(&capsule->ext_cost_fun_jac_hess[i], {{ dims.np }});
+ {%- endif %}
}
{%- endif %}
{%- if cost.cost_type_e == "NONLINEAR_LS" %}
// nonlinear least square function
- capsule->cost_y_e_fun.casadi_fun = &{{ model.name }}_cost_y_e_fun;
- capsule->cost_y_e_fun.casadi_n_in = &{{ model.name }}_cost_y_e_fun_n_in;
- capsule->cost_y_e_fun.casadi_n_out = &{{ model.name }}_cost_y_e_fun_n_out;
- capsule->cost_y_e_fun.casadi_sparsity_in = &{{ model.name }}_cost_y_e_fun_sparsity_in;
- capsule->cost_y_e_fun.casadi_sparsity_out = &{{ model.name }}_cost_y_e_fun_sparsity_out;
- capsule->cost_y_e_fun.casadi_work = &{{ model.name }}_cost_y_e_fun_work;
- external_function_param_casadi_create(&capsule->cost_y_e_fun, {{ dims.np }});
-
- capsule->cost_y_e_fun_jac_ut_xt.casadi_fun = &{{ model.name }}_cost_y_e_fun_jac_ut_xt;
- capsule->cost_y_e_fun_jac_ut_xt.casadi_n_in = &{{ model.name }}_cost_y_e_fun_jac_ut_xt_n_in;
- capsule->cost_y_e_fun_jac_ut_xt.casadi_n_out = &{{ model.name }}_cost_y_e_fun_jac_ut_xt_n_out;
- capsule->cost_y_e_fun_jac_ut_xt.casadi_sparsity_in = &{{ model.name }}_cost_y_e_fun_jac_ut_xt_sparsity_in;
- capsule->cost_y_e_fun_jac_ut_xt.casadi_sparsity_out = &{{ model.name }}_cost_y_e_fun_jac_ut_xt_sparsity_out;
- capsule->cost_y_e_fun_jac_ut_xt.casadi_work = &{{ model.name }}_cost_y_e_fun_jac_ut_xt_work;
- external_function_param_casadi_create(&capsule->cost_y_e_fun_jac_ut_xt, {{ dims.np }});
-
- capsule->cost_y_e_hess.casadi_fun = &{{ model.name }}_cost_y_e_hess;
- capsule->cost_y_e_hess.casadi_n_in = &{{ model.name }}_cost_y_e_hess_n_in;
- capsule->cost_y_e_hess.casadi_n_out = &{{ model.name }}_cost_y_e_hess_n_out;
- capsule->cost_y_e_hess.casadi_sparsity_in = &{{ model.name }}_cost_y_e_hess_sparsity_in;
- capsule->cost_y_e_hess.casadi_sparsity_out = &{{ model.name }}_cost_y_e_hess_sparsity_out;
- capsule->cost_y_e_hess.casadi_work = &{{ model.name }}_cost_y_e_hess_work;
- external_function_param_casadi_create(&capsule->cost_y_e_hess, {{ dims.np }});
-
+ MAP_CASADI_FNC(cost_y_e_fun, {{ model.name }}_cost_y_e_fun);
+ MAP_CASADI_FNC(cost_y_e_fun_jac_ut_xt, {{ model.name }}_cost_y_e_fun_jac_ut_xt);
+ MAP_CASADI_FNC(cost_y_e_hess, {{ model.name }}_cost_y_e_hess);
{%- elif cost.cost_type_e == "EXTERNAL" %}
- // external cost
- {% if cost.cost_ext_fun_type_e == "casadi" %}
- capsule->ext_cost_e_fun.casadi_fun = &{{ model.name }}_cost_ext_cost_e_fun;
- capsule->ext_cost_e_fun.casadi_n_in = &{{ model.name }}_cost_ext_cost_e_fun_n_in;
- capsule->ext_cost_e_fun.casadi_n_out = &{{ model.name }}_cost_ext_cost_e_fun_n_out;
- capsule->ext_cost_e_fun.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_e_fun_sparsity_in;
- capsule->ext_cost_e_fun.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_e_fun_sparsity_out;
- capsule->ext_cost_e_fun.casadi_work = &{{ model.name }}_cost_ext_cost_e_fun_work;
+ // external cost - function
+ {%- if cost.cost_ext_fun_type_e == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_e_fun, {{ model.name }}_cost_ext_cost_e_fun);
{% else %}
capsule->ext_cost_e_fun.fun = &{{ cost.cost_function_ext_cost_e }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_e }}_create(&capsule->ext_cost_e_fun, {{ dims.np }});
+ {%- endif %}
- // external cost
- {% if cost.cost_ext_fun_type_e == "casadi" %}
- capsule->ext_cost_e_fun_jac.casadi_fun = &{{ model.name }}_cost_ext_cost_e_fun_jac;
- capsule->ext_cost_e_fun_jac.casadi_n_in = &{{ model.name }}_cost_ext_cost_e_fun_jac_n_in;
- capsule->ext_cost_e_fun_jac.casadi_n_out = &{{ model.name }}_cost_ext_cost_e_fun_jac_n_out;
- capsule->ext_cost_e_fun_jac.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_e_fun_jac_sparsity_in;
- capsule->ext_cost_e_fun_jac.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_e_fun_jac_sparsity_out;
- capsule->ext_cost_e_fun_jac.casadi_work = &{{ model.name }}_cost_ext_cost_e_fun_jac_work;
- {% else %}
+ // external cost - jacobian
+ {%- if cost.cost_ext_fun_type_e == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_e_fun_jac, {{ model.name }}_cost_ext_cost_e_fun_jac);
+ {%- else %}
capsule->ext_cost_e_fun_jac.fun = &{{ cost.cost_function_ext_cost_e }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_e }}_create(&capsule->ext_cost_e_fun_jac, {{ dims.np }});
+ {%- endif %}
- // external cost
- {% if cost.cost_ext_fun_type_e == "casadi" %}
- capsule->ext_cost_e_fun_jac_hess.casadi_fun = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess;
- capsule->ext_cost_e_fun_jac_hess.casadi_n_in = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess_n_in;
- capsule->ext_cost_e_fun_jac_hess.casadi_n_out = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess_n_out;
- capsule->ext_cost_e_fun_jac_hess.casadi_sparsity_in = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess_sparsity_in;
- capsule->ext_cost_e_fun_jac_hess.casadi_sparsity_out = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess_sparsity_out;
- capsule->ext_cost_e_fun_jac_hess.casadi_work = &{{ model.name }}_cost_ext_cost_e_fun_jac_hess_work;
- {% else %}
+ // external cost - hessian
+ {%- if cost.cost_ext_fun_type_e == "casadi" %}
+ MAP_CASADI_FNC(ext_cost_e_fun_jac_hess, {{ model.name }}_cost_ext_cost_e_fun_jac_hess);
+ {%- else %}
capsule->ext_cost_e_fun_jac_hess.fun = &{{ cost.cost_function_ext_cost_e }};
- {% endif %}
external_function_param_{{ cost.cost_ext_fun_type_e }}_create(&capsule->ext_cost_e_fun_jac_hess, {{ dims.np }});
+ {%- endif %}
{%- endif %}
+#undef MAP_CASADI_FNC
+}
+
+
+/**
+ * Internal function for {{ model.name }}_acados_create: step 4
+ */
+void {{ model.name }}_acados_create_4_set_default_parameters({{ model.name }}_solver_capsule* capsule) {
+{%- if dims.np > 0 %}
+ const int N = capsule->nlp_solver_plan->N;
+ // initialize parameters to nominal value
+ double* p = calloc(NP, sizeof(double));
+ {%- for item in parameter_values %}
+ {%- if item != 0 %}
+ p[{{ loop.index0 }}] = {{ item }};
+ {%- endif %}
+ {%- endfor %}
+
+ for (int i = 0; i <= N; i++) {
+ {{ model.name }}_acados_update_params(capsule, i, p, NP);
+ }
+ free(p);
+{%- else %}
+ // no parameters defined
+{%- endif %}{# if dims.np #}
+}
+
+
+/**
+ * Internal function for {{ model.name }}_acados_create: step 5
+ */
+void {{ model.name }}_acados_create_5_set_nlp_in({{ model.name }}_solver_capsule* capsule, const int N, double* new_time_steps)
+{
+ assert(N == capsule->nlp_solver_plan->N);
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
+ ocp_nlp_dims* nlp_dims = capsule->nlp_dims;
+
/************************************************
* nlp_in
************************************************/
- ocp_nlp_in * nlp_in = ocp_nlp_in_create(nlp_config, nlp_dims);
- capsule->nlp_in = nlp_in;
+// ocp_nlp_in * nlp_in = ocp_nlp_in_create(nlp_config, nlp_dims);
+// capsule->nlp_in = nlp_in;
+ ocp_nlp_in * nlp_in = capsule->nlp_in;
// set up time_steps
{% set all_equal = true -%}
@@ -978,11 +785,15 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i,
"impl_dae_fun_jac_x_xdot_u", &capsule->impl_dae_fun_jac_x_xdot_u[i]);
{% elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i, "phi_fun", &capsule->gnsf_phi_fun[i]);
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i, "phi_fun_jac_y", &capsule->gnsf_phi_fun_jac_y[i]);
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i, "phi_jac_y_uhat", &capsule->gnsf_phi_jac_y_uhat[i]);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i, "f_lo_jac_x1_x1dot_u_z",
&capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i]);
+ {%- endif %}
+ {%- endif %}
ocp_nlp_dynamics_model_set(nlp_config, nlp_dims, nlp_in, i, "gnsf_get_matrices_fun",
&capsule->gnsf_get_matrices_fun[i]);
{% elif solver_options.integrator_type == "DISCRETE" %}
@@ -996,10 +807,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endif %}
}
-
/**** Cost ****/
{%- if cost.cost_type_0 == "NONLINEAR_LS" or cost.cost_type_0 == "LINEAR_LS" %}
-{% if dims.ny_0 > 0 %}
+ {%- if dims.ny_0 > 0 %}
double* W_0 = calloc(NY0*NY0, sizeof(double));
// change only the non-zero elements:
{%- for j in range(end=dims.ny_0) %}
@@ -1021,14 +831,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "yref", yref_0);
free(yref_0);
-{% endif %}
-{% endif %}
+ {%- endif %}
+{%- endif %}
{%- if cost.cost_type == "NONLINEAR_LS" or cost.cost_type == "LINEAR_LS" %}
-{% if dims.ny > 0 %}
+ {%- if dims.ny > 0 %}
double* W = calloc(NY*NY, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny) %}
+ {%- for j in range(end=dims.ny) %}
{%- for k in range(end=dims.ny) %}
{%- if cost.W[j][k] != 0 %}
W[{{ j }}+(NY) * {{ k }}] = {{ cost.W[j][k] }};
@@ -1051,13 +861,13 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(W);
free(yref);
-{% endif %}
-{% endif %}
+ {%- endif %}
+{%- endif %}
{%- if cost.cost_type_0 == "LINEAR_LS" %}
double* Vx_0 = calloc(NY0*NX, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny_0) %}
+ {%- for j in range(end=dims.ny_0) %}
{%- for k in range(end=dims.nx) %}
{%- if cost.Vx_0[j][k] != 0 %}
Vx_0[{{ j }}+(NY0) * {{ k }}] = {{ cost.Vx_0[j][k] }};
@@ -1067,10 +877,10 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "Vx", Vx_0);
free(Vx_0);
-{% if dims.ny_0 > 0 and dims.nu > 0 %}
+ {%- if dims.ny_0 > 0 and dims.nu > 0 %}
double* Vu_0 = calloc(NY0*NU, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny_0) %}
+ {%- for j in range(end=dims.ny_0) %}
{%- for k in range(end=dims.nu) %}
{%- if cost.Vu_0[j][k] != 0 %}
Vu_0[{{ j }}+(NY0) * {{ k }}] = {{ cost.Vu_0[j][k] }};
@@ -1079,8 +889,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "Vu", Vu_0);
free(Vu_0);
-{% endif %}
-{% if dims.ny_0 > 0 and dims.nz > 0 %}
+ {%- endif %}
+
+ {%- if dims.ny_0 > 0 and dims.nz > 0 %}
double* Vz_0 = calloc(NY0*NZ, sizeof(double));
// change only the non-zero elements:
{% for j in range(end=dims.ny_0) %}
@@ -1092,14 +903,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "Vz", Vz_0);
free(Vz_0);
-{%- endif %}
+ {%- endif %}
{%- endif %}{# LINEAR LS #}
{%- if cost.cost_type == "LINEAR_LS" %}
double* Vx = calloc(NY*NX, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny) %}
+ {%- for j in range(end=dims.ny) %}
{%- for k in range(end=dims.nx) %}
{%- if cost.Vx[j][k] != 0 %}
Vx[{{ j }}+(NY) * {{ k }}] = {{ cost.Vx[j][k] }};
@@ -1112,7 +923,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(Vx);
-{% if dims.ny > 0 and dims.nu > 0 %}
+ {% if dims.ny > 0 and dims.nu > 0 %}
double* Vu = calloc(NY*NU, sizeof(double));
// change only the non-zero elements:
{% for j in range(end=dims.ny) %}
@@ -1128,9 +939,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, i, "Vu", Vu);
}
free(Vu);
-{% endif %}
+ {%- endif %}
-{% if dims.ny > 0 and dims.nz > 0 %}
+ {%- if dims.ny > 0 and dims.nz > 0 %}
double* Vz = calloc(NY*NZ, sizeof(double));
// change only the non-zero elements:
{% for j in range(end=dims.ny) %}
@@ -1146,7 +957,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, i, "Vz", Vz);
}
free(Vz);
-{%- endif %}
+ {%- endif %}
{%- endif %}{# LINEAR LS #}
@@ -1176,8 +987,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
{%- endif %}
-
-{% if dims.ns > 0 %}
+{%- if dims.ns > 0 %}
double* zlumem = calloc(4*NS, sizeof(double));
double* Zl = zlumem+NS*0;
double* Zu = zlumem+NS*1;
@@ -1216,14 +1026,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, i, "zu", zu);
}
free(zlumem);
-{% endif %}
+{%- endif %}
// terminal cost
-{% if cost.cost_type_e == "LINEAR_LS" or cost.cost_type_e == "NONLINEAR_LS" %}
-{% if dims.ny_e > 0 %}
+{%- if cost.cost_type_e == "LINEAR_LS" or cost.cost_type_e == "NONLINEAR_LS" %}
+ {%- if dims.ny_e > 0 %}
double* yref_e = calloc(NYN, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny_e) %}
+ {%- for j in range(end=dims.ny_e) %}
{%- if cost.yref_e[j] != 0 %}
yref_e[{{ j }}] = {{ cost.yref_e[j] }};
{%- endif %}
@@ -1233,7 +1043,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
double* W_e = calloc(NYN*NYN, sizeof(double));
// change only the non-zero elements:
- {% for j in range(end=dims.ny_e) %}
+ {%- for j in range(end=dims.ny_e) %}
{%- for k in range(end=dims.ny_e) %}
{%- if cost.W_e[j][k] != 0 %}
W_e[{{ j }}+(NYN) * {{ k }}] = {{ cost.W_e[j][k] }};
@@ -1243,7 +1053,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "W", W_e);
free(W_e);
- {%- if cost.cost_type_e == "LINEAR_LS" %}
+ {%- if cost.cost_type_e == "LINEAR_LS" %}
double* Vx_e = calloc(NYN*NX, sizeof(double));
// change only the non-zero elements:
{% for j in range(end=dims.ny_e) %}
@@ -1255,14 +1065,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "Vx", Vx_e);
free(Vx_e);
- {%- endif %}
+ {%- endif %}
- {%- if cost.cost_type_e == "NONLINEAR_LS" %}
+ {%- if cost.cost_type_e == "NONLINEAR_LS" %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "nls_y_fun", &capsule->cost_y_e_fun);
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "nls_y_fun_jac", &capsule->cost_y_e_fun_jac_ut_xt);
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "nls_y_hess", &capsule->cost_y_e_hess);
- {%- endif %}
-{%- endif %}{# ny_e > 0 #}
+ {%- endif %}
+ {%- endif %}{# ny_e > 0 #}
{%- elif cost.cost_type_e == "EXTERNAL" %}
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "ext_cost_fun", &capsule->ext_cost_e_fun);
@@ -1312,7 +1122,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
/**** Constraints ****/
// bounds for initial stage
-{% if dims.nbx_0 > 0 %}
+{%- if dims.nbx_0 > 0 %}
// x0
int* idxbx0 = malloc(NBX0 * sizeof(int));
{%- for i in range(end=dims.nbx_0) %}
@@ -1337,8 +1147,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "ubx", ubx0);
free(idxbx0);
free(lubx0);
-{% endif %}
-{% if dims.nbxe_0 > 0 %}
+{%- endif %}
+
+{%- if dims.nbxe_0 > 0 %}
// idxbxe_0
int* idxbxe_0 = malloc({{ dims.nbxe_0 }} * sizeof(int));
{% for i in range(end=dims.nbxe_0) %}
@@ -1346,7 +1157,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "idxbxe", idxbxe_0);
free(idxbxe_0);
-{% endif %}
+{%- endif %}
/* constraints that are the same for initial and intermediate */
{%- if dims.nsbx > 0 %}
@@ -1357,14 +1168,14 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
// soft bounds on x
int* idxsbx = malloc(NSBX * sizeof(int));
- {% for i in range(end=dims.nsbx) %}
+ {%- for i in range(end=dims.nsbx) %}
idxsbx[{{ i }}] = {{ constraints.idxsbx[i] }};
{%- endfor %}
double* lusbx = calloc(2*NSBX, sizeof(double));
double* lsbx = lusbx;
double* usbx = lusbx + NSBX;
- {% for i in range(end=dims.nsbx) %}
+ {%- for i in range(end=dims.nsbx) %}
{%- if constraints.lsbx[i] != 0 %}
lsbx[{{ i }}] = {{ constraints.lsbx[i] }};
{%- endif %}
@@ -1384,7 +1195,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endif %}
-{% if dims.nbu > 0 %}
+{%- if dims.nbu > 0 %}
// u
int* idxbu = malloc(NBU * sizeof(int));
{% for i in range(end=dims.nbu) %}
@@ -1410,9 +1221,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxbu);
free(lubu);
-{% endif %}
+{%- endif %}
-{% if dims.nsbu > 0 %}
+{%- if dims.nsbu > 0 %}
// set up soft bounds for u
int* idxsbu = malloc(NSBU * sizeof(int));
{% for i in range(end=dims.nsbu) %}
@@ -1437,7 +1248,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxsbu);
free(lusbu);
-{% endif %}
+{%- endif %}
{% if dims.nsg > 0 %}
// set up soft bounds for general linear constraints
@@ -1465,7 +1276,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxsg);
free(lusg);
-{% endif %}
+{%- endif %}
{% if dims.nsh > 0 %}
// set up soft bounds for nonlinear constraints
@@ -1493,7 +1304,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxsh);
free(lush);
-{% endif %}
+{%- endif %}
{% if dims.nsphi > 0 %}
// set up soft bounds for convex-over-nonlinear constraints
@@ -1521,7 +1332,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxsphi);
free(lusphi);
-{% endif %}
+{%- endif %}
{% if dims.nbx > 0 %}
// x
@@ -1549,7 +1360,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
free(idxbx);
free(lubx);
-{% endif %}
+{%- endif %}
{% if dims.ng > 0 %}
// set up general constraints for stage 0 to N-1
@@ -1597,7 +1408,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
free(D);
free(C);
free(lug);
-{% endif %}
+{%- endif %}
{% if dims.nh > 0 %}
// set up nonlinear constraints for stage 0 to N-1
@@ -1632,7 +1443,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, i, "uh", uh);
}
free(luh);
-{% endif %}
+{%- endif %}
{% if dims.nphi > 0 and constraints.constr_type == "BGP" %}
// set up convex-over-nonlinear constraints for stage 0 to N-1
@@ -1659,7 +1470,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, i, "uphi", uphi);
}
free(luphi);
-{% endif %}
+{%- endif %}
/* terminal constraints */
{% if dims.nbx_e > 0 %}
@@ -1866,42 +1677,62 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
"nl_constr_phi_o_r_fun_phi_jac_ux_z_phi_hess_r_jac_ux", &capsule->phi_e_constraint);
free(luphi_e);
{% endif %}
+}
+/**
+ * Internal function for {{ model.name }}_acados_create: step 6
+ */
+void {{ model.name }}_acados_create_6_set_opts({{ model.name }}_solver_capsule* capsule)
+{
+ const int N = capsule->nlp_solver_plan->N;
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
+ ocp_nlp_dims* nlp_dims = capsule->nlp_dims;
+ void *nlp_opts = capsule->nlp_opts;
+
/************************************************
* opts
************************************************/
- capsule->nlp_opts = ocp_nlp_solver_opts_create(nlp_config, nlp_dims);
-
{% if solver_options.hessian_approx == "EXACT" %}
bool nlp_solver_exact_hessian = true;
// TODO: this if should not be needed! however, calling the setter with false leads to weird behavior. Investigate!
if (nlp_solver_exact_hessian)
{
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "exact_hess", &nlp_solver_exact_hessian);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "exact_hess", &nlp_solver_exact_hessian);
}
int exact_hess_dyn = {{ solver_options.exact_hess_dyn }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "exact_hess_dyn", &exact_hess_dyn);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "exact_hess_dyn", &exact_hess_dyn);
int exact_hess_cost = {{ solver_options.exact_hess_cost }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "exact_hess_cost", &exact_hess_cost);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "exact_hess_cost", &exact_hess_cost);
int exact_hess_constr = {{ solver_options.exact_hess_constr }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "exact_hess_constr", &exact_hess_constr);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "exact_hess_constr", &exact_hess_constr);
{%- endif -%}
{%- if solver_options.globalization == "FIXED_STEP" %}
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "globalization", "fixed_step");
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "globalization", "fixed_step");
{%- elif solver_options.globalization == "MERIT_BACKTRACKING" %}
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "globalization", "merit_backtracking");
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "globalization", "merit_backtracking");
double alpha_min = {{ solver_options.alpha_min }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "alpha_min", &alpha_min);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "alpha_min", &alpha_min);
double alpha_reduction = {{ solver_options.alpha_reduction }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "alpha_reduction", &alpha_reduction);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "alpha_reduction", &alpha_reduction);
+
+ int line_search_use_sufficient_descent = {{ solver_options.line_search_use_sufficient_descent }};
+ ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "line_search_use_sufficient_descent", &line_search_use_sufficient_descent);
+
+ int globalization_use_SOC = {{ solver_options.globalization_use_SOC }};
+ ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "globalization_use_SOC", &globalization_use_SOC);
+
+ double eps_sufficient_descent = {{ solver_options.eps_sufficient_descent }};
+ ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "eps_sufficient_descent", &eps_sufficient_descent);
{%- endif -%}
+ int full_step_dual = {{ solver_options.full_step_dual }};
+ ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "full_step_dual", &full_step_dual);
{%- if dims.nz > 0 %}
// TODO: these options are lower level -> should be encapsulated! maybe through hessian approx option.
@@ -1909,9 +1740,9 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
bool sens_algebraic_val = true;
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_output_z", &output_z_val);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_output_z", &output_z_val);
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_sens_algebraic", &sens_algebraic_val);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_sens_algebraic", &sens_algebraic_val);
{%- endif %}
{%- if solver_options.integrator_type != "DISCRETE" %}
@@ -1919,7 +1750,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
// set collocation type (relevant for implicit integrators)
sim_collocation_type collocation_type = {{ solver_options.collocation_type }};
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_collocation_type", &collocation_type);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_collocation_type", &collocation_type);
// set up sim_method_num_steps
{%- set all_equal = true %}
@@ -1935,7 +1766,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
// all sim_method_num_steps are identical
int sim_method_num_steps = {{ solver_options.sim_method_num_steps[0] }};
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_num_steps", &sim_method_num_steps);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_num_steps", &sim_method_num_steps);
{%- else %}
// sim_method_num_steps are different
int* sim_method_num_steps = malloc(N*sizeof(int));
@@ -1944,7 +1775,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_num_steps", &sim_method_num_steps[i]);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_num_steps", &sim_method_num_steps[i]);
free(sim_method_num_steps);
{%- endif %}
@@ -1962,7 +1793,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
// all sim_method_num_stages are identical
int sim_method_num_stages = {{ solver_options.sim_method_num_stages[0] }};
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_num_stages", &sim_method_num_stages);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_num_stages", &sim_method_num_stages);
{%- else %}
int* sim_method_num_stages = malloc(N*sizeof(int));
{%- for j in range(end=dims.N) %}
@@ -1970,13 +1801,13 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_num_stages", &sim_method_num_stages[i]);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_num_stages", &sim_method_num_stages[i]);
free(sim_method_num_stages);
{%- endif %}
int newton_iter_val = {{ solver_options.sim_method_newton_iter }};
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_newton_iter", &newton_iter_val);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_newton_iter", &newton_iter_val);
// set up sim_method_jac_reuse
@@ -1991,7 +1822,7 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- if all_equal == true %}
bool tmp_bool = (bool) {{ solver_options.sim_method_jac_reuse[0] }};
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_jac_reuse", &tmp_bool);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_jac_reuse", &tmp_bool);
{%- else %}
bool* sim_method_jac_reuse = malloc(N*sizeof(bool));
{%- for j in range(end=dims.N) %}
@@ -1999,104 +1830,119 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
{%- endfor %}
for (int i = 0; i < N; i++)
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "dynamics_jac_reuse", &sim_method_jac_reuse[i]);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "dynamics_jac_reuse", &sim_method_jac_reuse[i]);
free(sim_method_jac_reuse);
{%- endif %}
{%- endif %}
double nlp_solver_step_length = {{ solver_options.nlp_solver_step_length }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "step_length", &nlp_solver_step_length);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "step_length", &nlp_solver_step_length);
{%- if solver_options.nlp_solver_warm_start_first_qp %}
int nlp_solver_warm_start_first_qp = {{ solver_options.nlp_solver_warm_start_first_qp }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "warm_start_first_qp", &nlp_solver_warm_start_first_qp);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "warm_start_first_qp", &nlp_solver_warm_start_first_qp);
{%- endif %}
double levenberg_marquardt = {{ solver_options.levenberg_marquardt }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "levenberg_marquardt", &levenberg_marquardt);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "levenberg_marquardt", &levenberg_marquardt);
/* options QP solver */
{%- if solver_options.qp_solver is starting_with("PARTIAL_CONDENSING") %}
int qp_solver_cond_N;
- {%- if solver_options.qp_solver_cond_N %}
- qp_solver_cond_N = {{ solver_options.qp_solver_cond_N }};
- {% else %}
+ {% if solver_options.qp_solver_cond_N -%}
+ const int qp_solver_cond_N_ori = {{ solver_options.qp_solver_cond_N }};
+ qp_solver_cond_N = N < qp_solver_cond_N_ori ? N : qp_solver_cond_N_ori; // use the minimum value here
+ {%- else %}
// NOTE: there is no condensing happening here!
qp_solver_cond_N = N;
{%- endif %}
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_cond_N", &qp_solver_cond_N);
-{% endif %}
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_cond_N", &qp_solver_cond_N);
+{%- endif %}
+
+
+{%- if solver_options.qp_solver is containing("HPIPM") %}
+ // set HPIPM mode: should be done before setting other QP solver options
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_hpipm_mode", "{{ solver_options.hpipm_mode }}");
+{%- endif %}
+
+{% if solver_options.nlp_solver_type == "SQP" %}
+ // set SQP specific options
+ double nlp_solver_tol_stat = {{ solver_options.nlp_solver_tol_stat }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "tol_stat", &nlp_solver_tol_stat);
+
+ double nlp_solver_tol_eq = {{ solver_options.nlp_solver_tol_eq }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "tol_eq", &nlp_solver_tol_eq);
+
+ double nlp_solver_tol_ineq = {{ solver_options.nlp_solver_tol_ineq }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "tol_ineq", &nlp_solver_tol_ineq);
+
+ double nlp_solver_tol_comp = {{ solver_options.nlp_solver_tol_comp }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "tol_comp", &nlp_solver_tol_comp);
+
+ int nlp_solver_max_iter = {{ solver_options.nlp_solver_max_iter }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "max_iter", &nlp_solver_max_iter);
+
+ int initialize_t_slacks = {{ solver_options.initialize_t_slacks }};
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "initialize_t_slacks", &initialize_t_slacks);
+{%- endif %}
int qp_solver_iter_max = {{ solver_options.qp_solver_iter_max }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_iter_max", &qp_solver_iter_max);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_iter_max", &qp_solver_iter_max);
+{# NOTE: qp_solver tolerances must be set after NLP ones, since the setter for NLP tolerances sets the QP tolerances to the sam values. #}
{%- if solver_options.qp_solver_tol_stat %}
double qp_solver_tol_stat = {{ solver_options.qp_solver_tol_stat }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_tol_stat", &qp_solver_tol_stat);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_tol_stat", &qp_solver_tol_stat);
{%- endif -%}
{%- if solver_options.qp_solver_tol_eq %}
double qp_solver_tol_eq = {{ solver_options.qp_solver_tol_eq }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_tol_eq", &qp_solver_tol_eq);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_tol_eq", &qp_solver_tol_eq);
{%- endif -%}
{%- if solver_options.qp_solver_tol_ineq %}
double qp_solver_tol_ineq = {{ solver_options.qp_solver_tol_ineq }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_tol_ineq", &qp_solver_tol_ineq);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_tol_ineq", &qp_solver_tol_ineq);
{%- endif -%}
{%- if solver_options.qp_solver_tol_comp %}
double qp_solver_tol_comp = {{ solver_options.qp_solver_tol_comp }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_tol_comp", &qp_solver_tol_comp);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_tol_comp", &qp_solver_tol_comp);
{%- endif -%}
{%- if solver_options.qp_solver_warm_start %}
int qp_solver_warm_start = {{ solver_options.qp_solver_warm_start }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "qp_warm_start", &qp_solver_warm_start);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "qp_warm_start", &qp_solver_warm_start);
{%- endif -%}
-
-{% if solver_options.nlp_solver_type == "SQP" %}
- // set SQP specific options
- double nlp_solver_tol_stat = {{ solver_options.nlp_solver_tol_stat }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "tol_stat", &nlp_solver_tol_stat);
-
- double nlp_solver_tol_eq = {{ solver_options.nlp_solver_tol_eq }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "tol_eq", &nlp_solver_tol_eq);
-
- double nlp_solver_tol_ineq = {{ solver_options.nlp_solver_tol_ineq }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "tol_ineq", &nlp_solver_tol_ineq);
-
- double nlp_solver_tol_comp = {{ solver_options.nlp_solver_tol_comp }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "tol_comp", &nlp_solver_tol_comp);
-
- int nlp_solver_max_iter = {{ solver_options.nlp_solver_max_iter }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "max_iter", &nlp_solver_max_iter);
-
- int initialize_t_slacks = {{ solver_options.initialize_t_slacks }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "initialize_t_slacks", &initialize_t_slacks);
-{%- endif %}
int print_level = {{ solver_options.print_level }};
- ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "print_level", &print_level);
+ ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "print_level", &print_level);
int ext_cost_num_hess = {{ solver_options.ext_cost_num_hess }};
{%- if cost.cost_type == "EXTERNAL" %}
for (int i = 0; i < N; i++)
{
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, i, "cost_numerical_hessian", &ext_cost_num_hess);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, i, "cost_numerical_hessian", &ext_cost_num_hess);
}
{%- endif %}
{%- if cost.cost_type_e == "EXTERNAL" %}
- ocp_nlp_solver_opts_set_at_stage(nlp_config, capsule->nlp_opts, N, "cost_numerical_hessian", &ext_cost_num_hess);
+ ocp_nlp_solver_opts_set_at_stage(nlp_config, nlp_opts, N, "cost_numerical_hessian", &ext_cost_num_hess);
{%- endif %}
+}
- /* out */
- ocp_nlp_out * nlp_out = ocp_nlp_out_create(nlp_config, nlp_dims);
- capsule->nlp_out = nlp_out;
+/**
+ * Internal function for {{ model.name }}_acados_create: step 7
+ */
+void {{ model.name }}_acados_create_7_set_nlp_out({{ model.name }}_solver_capsule* capsule)
+{
+ const int N = capsule->nlp_solver_plan->N;
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
+ ocp_nlp_dims* nlp_dims = capsule->nlp_dims;
+ ocp_nlp_out* nlp_out = capsule->nlp_out;
// initialize primal solution
double* xu0 = calloc(NX+NU, sizeof(double));
@@ -2123,39 +1969,171 @@ int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_c
}
ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, N, "x", x0);
free(xu0);
-
- capsule->nlp_solver = ocp_nlp_solver_create(nlp_config, nlp_dims, capsule->nlp_opts);
+}
-{% if dims.np > 0 %}
- // initialize parameters to nominal value
- double* p = calloc(NP, sizeof(double));
- {% for item in parameter_values %}
- {%- if item != 0 %}
- p[{{ loop.index0 }}] = {{ item }};
- {%- endif %}
- {%- endfor %}
+/**
+ * Internal function for {{ model.name }}_acados_create: step 8
+ */
+//void {{ model.name }}_acados_create_8_create_solver({{ model.name }}_solver_capsule* capsule)
+//{
+// capsule->nlp_solver = ocp_nlp_solver_create(capsule->nlp_config, capsule->nlp_dims, capsule->nlp_opts);
+//}
- for (int i = 0; i <= N; i++)
- {
- {{ model.name }}_acados_update_params(capsule, i, p, NP);
+/**
+ * Internal function for {{ model.name }}_acados_create: step 9
+ */
+int {{ model.name }}_acados_create_9_precompute({{ model.name }}_solver_capsule* capsule) {
+ int status = ocp_nlp_precompute(capsule->nlp_solver, capsule->nlp_in, capsule->nlp_out);
+
+ if (status != ACADOS_SUCCESS) {
+ printf("\nocp_nlp_precompute failed!\n\n");
+ exit(1);
}
- free(p);
-{%- endif %}{# if dims.np #}
- status = ocp_nlp_precompute(capsule->nlp_solver, nlp_in, nlp_out);
+ return status;
+}
- if (status != ACADOS_SUCCESS)
- {
- printf("\nocp_precompute failed!\n\n");
- exit(1);
+
+int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_capsule* capsule, int N, double* new_time_steps)
+{
+ // If N does not match the number of shooting intervals used for code generation, new_time_steps must be given.
+ if (N != {{ model.name | upper }}_N && !new_time_steps) {
+ fprintf(stderr, "{{ model.name }}_acados_create_with_discretization: new_time_steps is NULL " \
+ "but the number of shooting intervals (= %d) differs from the number of " \
+ "shooting intervals (= %d) during code generation! Please provide a new vector of time_stamps!\n", \
+ N, {{ model.name | upper }}_N);
+ return 1;
}
+ // number of expected runtime parameters
+ capsule->nlp_np = NP;
+
+ // 1) create and set nlp_solver_plan; create nlp_config
+ capsule->nlp_solver_plan = ocp_nlp_plan_create(N);
+ {{ model.name }}_acados_create_1_set_plan(capsule->nlp_solver_plan, N);
+ capsule->nlp_config = ocp_nlp_config_create(*capsule->nlp_solver_plan);
+
+ // 3) create and set dimensions
+ capsule->nlp_dims = {{ model.name }}_acados_create_2_create_and_set_dimensions(capsule);
+ {{ model.name }}_acados_create_3_create_and_set_functions(capsule);
+
+ // 4) set default parameters in functions
+ {{ model.name }}_acados_create_4_set_default_parameters(capsule);
+
+ // 5) create and set nlp_in
+ capsule->nlp_in = ocp_nlp_in_create(capsule->nlp_config, capsule->nlp_dims);
+ {{ model.name }}_acados_create_5_set_nlp_in(capsule, N, new_time_steps);
+
+ // 6) create and set nlp_opts
+ capsule->nlp_opts = ocp_nlp_solver_opts_create(capsule->nlp_config, capsule->nlp_dims);
+ {{ model.name }}_acados_create_6_set_opts(capsule);
+
+ // 7) create and set nlp_out
+ // 7.1) nlp_out
+ capsule->nlp_out = ocp_nlp_out_create(capsule->nlp_config, capsule->nlp_dims);
+ // 7.2) sens_out
+ capsule->sens_out = ocp_nlp_out_create(capsule->nlp_config, capsule->nlp_dims);
+ {{ model.name }}_acados_create_7_set_nlp_out(capsule);
+
+ // 8) create solver
+ capsule->nlp_solver = ocp_nlp_solver_create(capsule->nlp_config, capsule->nlp_dims, capsule->nlp_opts);
+ //{{ model.name }}_acados_create_8_create_solver(capsule);
+
+ // 9) do precomputations
+ int status = {{ model.name }}_acados_create_9_precompute(capsule);
+ return status;
+}
+
+/**
+ * This function is for updating an already initialized solver with a different number of qp_cond_N. It is useful for code reuse after code export.
+ */
+int {{ model.name }}_acados_update_qp_solver_cond_N({{ model.name }}_solver_capsule* capsule, int qp_solver_cond_N)
+{
+{%- if solver_options.qp_solver is starting_with("PARTIAL_CONDENSING") %}
+ // 1) destroy solver
+ ocp_nlp_solver_destroy(capsule->nlp_solver);
+
+ // 2) set new value for "qp_cond_N"
+ const int N = capsule->nlp_solver_plan->N;
+ if(qp_solver_cond_N > N)
+ printf("Warning: qp_solver_cond_N = %d > N = %d\n", qp_solver_cond_N, N);
+ ocp_nlp_solver_opts_set(capsule->nlp_config, capsule->nlp_opts, "qp_cond_N", &qp_solver_cond_N);
+
+ // 3) continue with the remaining steps from {{ model.name }}_acados_create_with_discretization(...):
+ // -> 8) create solver
+ capsule->nlp_solver = ocp_nlp_solver_create(capsule->nlp_config, capsule->nlp_dims, capsule->nlp_opts);
+
+ // -> 9) do precomputations
+ int status = {{ model.name }}_acados_create_9_precompute(capsule);
return status;
+{%- else %}
+ printf("\nacados_update_qp_solver_cond_N() failed, since no partial condensing solver is used!\n\n");
+ // Todo: what is an adequate behavior here?
+ exit(1);
+ return -1;
+{%- endif %}
}
-int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * capsule, int stage, double *p, int np)
+int {{ model.name }}_acados_reset({{ model.name }}_solver_capsule* capsule)
+{
+
+ // set initialization to all zeros
+{# TODO: use guess values / initial state value from json instead?! #}
+ const int N = capsule->nlp_solver_plan->N;
+ ocp_nlp_config* nlp_config = capsule->nlp_config;
+ ocp_nlp_dims* nlp_dims = capsule->nlp_dims;
+ ocp_nlp_out* nlp_out = capsule->nlp_out;
+ ocp_nlp_in* nlp_in = capsule->nlp_in;
+ ocp_nlp_solver* nlp_solver = capsule->nlp_solver;
+
+ int nx, nu, nv, ns, nz, ni, dim;
+
+ double* buffer = calloc(NX+NU+NZ+2*NS+2*NSN+NBX+NBU+NG+NH+NPHI+NBX0+NBXN+NHN+NPHIN+NGN, sizeof(double));
+
+ for(int i=0; i reset memory
+ int qp_status;
+ ocp_nlp_get(capsule->nlp_config, capsule->nlp_solver, "qp_status", &qp_status);
+ if (qp_status == 3)
+ {
+ // printf("\nin reset qp_status %d -> resetting QP memory\n", qp_status);
+ ocp_nlp_solver_reset_qp_memory(nlp_solver, nlp_in, nlp_out);
+ }
+{%- endif %}
+
+ free(buffer);
+ return 0;
+}
+
+
+
+
+int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule* capsule, int stage, double *p, int np)
{
int solver_status = 0;
@@ -2180,7 +2158,7 @@ int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * caps
{%- endif %}
{% elif solver_options.integrator_type == "LIFTED_IRK" %}
capsule->impl_dae_fun[stage].set_param(capsule->impl_dae_fun+stage, p);
- capsule->impl_dae_fun_jac_x_xdot_z[stage].set_param(capsule->impl_dae_fun_jac_x_xdot_z+stage, p);
+ capsule->impl_dae_fun_jac_x_xdot_u[stage].set_param(capsule->impl_dae_fun_jac_x_xdot_u+stage, p);
{% elif solver_options.integrator_type == "ERK" %}
capsule->forw_vde_casadi[stage].set_param(capsule->forw_vde_casadi+stage, p);
capsule->expl_ode_fun[stage].set_param(capsule->expl_ode_fun+stage, p);
@@ -2189,11 +2167,14 @@ int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * caps
capsule->hess_vde_casadi[stage].set_param(capsule->hess_vde_casadi+stage, p);
{%- endif %}
{% elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
capsule->gnsf_phi_fun[stage].set_param(capsule->gnsf_phi_fun+stage, p);
capsule->gnsf_phi_fun_jac_y[stage].set_param(capsule->gnsf_phi_fun_jac_y+stage, p);
capsule->gnsf_phi_jac_y_uhat[stage].set_param(capsule->gnsf_phi_jac_y_uhat+stage, p);
-
- capsule->gnsf_f_lo_jac_x1_x1dot_u_z[stage].set_param(capsule->gnsf_f_lo_jac_x1_x1dot_u_z+stage, p);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
+ capsule->gnsf_f_lo_jac_x1_x1dot_u_z[stage].set_param(capsule->gnsf_f_lo_jac_x1_x1dot_u_z+stage, p);
+ {%- endif %}
+ {%- endif %}
{% elif solver_options.integrator_type == "DISCRETE" %}
capsule->discr_dyn_phi_fun[stage].set_param(capsule->discr_dyn_phi_fun+stage, p);
capsule->discr_dyn_phi_fun_jac_ut_xt[stage].set_param(capsule->discr_dyn_phi_fun_jac_ut_xt+stage, p);
@@ -2271,7 +2252,7 @@ int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * caps
-int {{ model.name }}_acados_solve({{ model.name }}_solver_capsule * capsule)
+int {{ model.name }}_acados_solve({{ model.name }}_solver_capsule* capsule)
{
// solve NLP
int solver_status = ocp_nlp_solve(capsule->nlp_solver, capsule->nlp_in, capsule->nlp_out);
@@ -2280,7 +2261,7 @@ int {{ model.name }}_acados_solve({{ model.name }}_solver_capsule * capsule)
}
-int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule)
+int {{ model.name }}_acados_free({{ model.name }}_solver_capsule* capsule)
{
// before destroying, keep some info
const int N = capsule->nlp_solver_plan->N;
@@ -2288,6 +2269,7 @@ int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule)
ocp_nlp_solver_opts_destroy(capsule->nlp_opts);
ocp_nlp_in_destroy(capsule->nlp_in);
ocp_nlp_out_destroy(capsule->nlp_out);
+ ocp_nlp_out_destroy(capsule->sens_out);
ocp_nlp_solver_destroy(capsule->nlp_solver);
ocp_nlp_dims_destroy(capsule->nlp_dims);
ocp_nlp_config_destroy(capsule->nlp_config);
@@ -2339,16 +2321,24 @@ int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule)
{%- elif solver_options.integrator_type == "GNSF" %}
for (int i = 0; i < N; i++)
{
+ {% if model.gnsf.purely_linear != 1 %}
external_function_param_casadi_free(&capsule->gnsf_phi_fun[i]);
external_function_param_casadi_free(&capsule->gnsf_phi_fun_jac_y[i]);
external_function_param_casadi_free(&capsule->gnsf_phi_jac_y_uhat[i]);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
external_function_param_casadi_free(&capsule->gnsf_f_lo_jac_x1_x1dot_u_z[i]);
+ {%- endif %}
+ {%- endif %}
external_function_param_casadi_free(&capsule->gnsf_get_matrices_fun[i]);
}
+ {% if model.gnsf.purely_linear != 1 %}
free(capsule->gnsf_phi_fun);
free(capsule->gnsf_phi_fun_jac_y);
free(capsule->gnsf_phi_jac_y_uhat);
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
free(capsule->gnsf_f_lo_jac_x1_x1dot_u_z);
+ {%- endif %}
+ {%- endif %}
free(capsule->gnsf_get_matrices_fun);
{%- elif solver_options.integrator_type == "DISCRETE" %}
for (int i = 0; i < N; i++)
@@ -2448,34 +2438,36 @@ int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule)
return 0;
}
-ocp_nlp_in *{{ model.name }}_acados_get_nlp_in({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_in; }
-ocp_nlp_out *{{ model.name }}_acados_get_nlp_out({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_out; }
-ocp_nlp_solver *{{ model.name }}_acados_get_nlp_solver({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_solver; }
-ocp_nlp_config *{{ model.name }}_acados_get_nlp_config({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_config; }
-void *{{ model.name }}_acados_get_nlp_opts({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_opts; }
-ocp_nlp_dims *{{ model.name }}_acados_get_nlp_dims({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_dims; }
-ocp_nlp_plan *{{ model.name }}_acados_get_nlp_plan({{ model.name }}_solver_capsule * capsule) { return capsule->nlp_solver_plan; }
+ocp_nlp_in *{{ model.name }}_acados_get_nlp_in({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_in; }
+ocp_nlp_out *{{ model.name }}_acados_get_nlp_out({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_out; }
+ocp_nlp_out *{{ model.name }}_acados_get_sens_out({{ model.name }}_solver_capsule* capsule) { return capsule->sens_out; }
+ocp_nlp_solver *{{ model.name }}_acados_get_nlp_solver({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_solver; }
+ocp_nlp_config *{{ model.name }}_acados_get_nlp_config({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_config; }
+void *{{ model.name }}_acados_get_nlp_opts({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_opts; }
+ocp_nlp_dims *{{ model.name }}_acados_get_nlp_dims({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_dims; }
+ocp_nlp_plan_t *{{ model.name }}_acados_get_nlp_plan({{ model.name }}_solver_capsule* capsule) { return capsule->nlp_solver_plan; }
-void {{ model.name }}_acados_print_stats({{ model.name }}_solver_capsule * capsule)
+void {{ model.name }}_acados_print_stats({{ model.name }}_solver_capsule* capsule)
{
int sqp_iter, stat_m, stat_n, tmp_int;
ocp_nlp_get(capsule->nlp_config, capsule->nlp_solver, "sqp_iter", &sqp_iter);
ocp_nlp_get(capsule->nlp_config, capsule->nlp_solver, "stat_n", &stat_n);
ocp_nlp_get(capsule->nlp_config, capsule->nlp_solver, "stat_m", &stat_m);
- {% set stat_n_max = 10 %}
+ {% set stat_n_max = 12 %}
double stat[{{ solver_options.nlp_solver_max_iter * stat_n_max }}];
ocp_nlp_get(capsule->nlp_config, capsule->nlp_solver, "statistics", stat);
int nrow = sqp_iter+1 < stat_m ? sqp_iter+1 : stat_m;
- printf("iter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter\n");
+{%- if solver_options.nlp_solver_type == "SQP" %}
+ printf("iter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter\talpha\n");
for (int i = 0; i < nrow; i++)
{
for (int j = 0; j < stat_n + 1; j++)
{
- if (j == 0 || j > 4)
+ if (j == 0 || j == 5 || j == 6)
{
tmp_int = (int) stat[i + j * nrow];
printf("%d\t", tmp_int);
@@ -2487,5 +2479,17 @@ void {{ model.name }}_acados_print_stats({{ model.name }}_solver_capsule * capsu
}
printf("\n");
}
+{% else %}
+ printf("iter\tqp_stat\tqp_iter\n");
+ for (int i = 0; i < nrow; i++)
+ {
+ for (int j = 0; j < stat_n + 1; j++)
+ {
+ tmp_int = (int) stat[i + j * nrow];
+ printf("%d\t", tmp_int);
+ }
+ printf("\n");
+ }
+{%- endif %}
}
diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.h b/pyextra/acados_template/c_templates_tera/acados_solver.in.h
index e8c0a38ca3..1c82ef3ba0 100644
--- a/pyextra/acados_template/c_templates_tera/acados_solver.in.h
+++ b/pyextra/acados_template/c_templates_tera/acados_solver.in.h
@@ -34,6 +34,8 @@
#ifndef ACADOS_SOLVER_{{ model.name }}_H_
#define ACADOS_SOLVER_{{ model.name }}_H_
+#include "acados/utils/types.h"
+
#include "acados_c/ocp_nlp_interface.h"
#include "acados_c/external_function_interface.h"
@@ -78,9 +80,10 @@ typedef struct {{ model.name }}_solver_capsule
// acados objects
ocp_nlp_in *nlp_in;
ocp_nlp_out *nlp_out;
+ ocp_nlp_out *sens_out;
ocp_nlp_solver *nlp_solver;
void *nlp_opts;
- ocp_nlp_plan *nlp_solver_plan;
+ ocp_nlp_plan_t *nlp_solver_plan;
ocp_nlp_config *nlp_config;
ocp_nlp_dims *nlp_dims;
@@ -171,33 +174,41 @@ typedef struct {{ model.name }}_solver_capsule
} {{ model.name }}_solver_capsule;
-{{ model.name }}_solver_capsule * {{ model.name }}_acados_create_capsule(void);
-int {{ model.name }}_acados_free_capsule({{ model.name }}_solver_capsule *capsule);
+ACADOS_SYMBOL_EXPORT {{ model.name }}_solver_capsule * {{ model.name }}_acados_create_capsule(void);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_free_capsule({{ model.name }}_solver_capsule *capsule);
+
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_create({{ model.name }}_solver_capsule * capsule);
+
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_reset({{ model.name }}_solver_capsule* capsule);
-int {{ model.name }}_acados_create({{ model.name }}_solver_capsule * capsule);
/**
* Generic version of {{ model.name }}_acados_create which allows to use a different number of shooting intervals than
* the number used for code generation. If new_time_steps=NULL and n_time_steps matches the number used for code
* generation, the time-steps from code generation is used.
*/
-int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_capsule * capsule, int n_time_steps, double* new_time_steps);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_create_with_discretization({{ model.name }}_solver_capsule * capsule, int n_time_steps, double* new_time_steps);
/**
* Update the time step vector. Number N must be identical to the currently set number of shooting nodes in the
* nlp_solver_plan. Returns 0 if no error occurred and a otherwise a value other than 0.
*/
-int {{ model.name }}_acados_update_time_steps({{ model.name }}_solver_capsule * capsule, int N, double* new_time_steps);
-int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * capsule, int stage, double *value, int np);
-int {{ model.name }}_acados_solve({{ model.name }}_solver_capsule * capsule);
-int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule);
-void {{ model.name }}_acados_print_stats({{ model.name }}_solver_capsule * capsule);
-
-ocp_nlp_in *{{ model.name }}_acados_get_nlp_in({{ model.name }}_solver_capsule * capsule);
-ocp_nlp_out *{{ model.name }}_acados_get_nlp_out({{ model.name }}_solver_capsule * capsule);
-ocp_nlp_solver *{{ model.name }}_acados_get_nlp_solver({{ model.name }}_solver_capsule * capsule);
-ocp_nlp_config *{{ model.name }}_acados_get_nlp_config({{ model.name }}_solver_capsule * capsule);
-void *{{ model.name }}_acados_get_nlp_opts({{ model.name }}_solver_capsule * capsule);
-ocp_nlp_dims *{{ model.name }}_acados_get_nlp_dims({{ model.name }}_solver_capsule * capsule);
-ocp_nlp_plan *{{ model.name }}_acados_get_nlp_plan({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_update_time_steps({{ model.name }}_solver_capsule * capsule, int N, double* new_time_steps);
+/**
+ * This function is used for updating an already initialized solver with a different number of qp_cond_N.
+ */
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_update_qp_solver_cond_N({{ model.name }}_solver_capsule * capsule, int qp_solver_cond_N);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_update_params({{ model.name }}_solver_capsule * capsule, int stage, double *value, int np);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_solve({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT int {{ model.name }}_acados_free({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT void {{ model.name }}_acados_print_stats({{ model.name }}_solver_capsule * capsule);
+
+ACADOS_SYMBOL_EXPORT ocp_nlp_in *{{ model.name }}_acados_get_nlp_in({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_out *{{ model.name }}_acados_get_nlp_out({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_out *{{ model.name }}_acados_get_sens_out({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_solver *{{ model.name }}_acados_get_nlp_solver({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_config *{{ model.name }}_acados_get_nlp_config({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT void *{{ model.name }}_acados_get_nlp_opts({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_dims *{{ model.name }}_acados_get_nlp_dims({{ model.name }}_solver_capsule * capsule);
+ACADOS_SYMBOL_EXPORT ocp_nlp_plan_t *{{ model.name }}_acados_get_nlp_plan({{ model.name }}_solver_capsule * capsule);
#ifdef __cplusplus
} /* extern "C" */
diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd b/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd
index 8387980c24..09f8755cbe 100644
--- a/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd
+++ b/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd
@@ -1,3 +1,36 @@
+#
+# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
+# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
+# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
+# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
+#
+# This file is part of acados.
+#
+# The 2-Clause BSD License
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.;
+#
+
cimport acados_solver_common
cdef extern from "acados_solver_{{ model.name }}.h":
@@ -8,13 +41,20 @@ cdef extern from "acados_solver_{{ model.name }}.h":
int acados_free_capsule "{{ model.name }}_acados_free_capsule"(nlp_solver_capsule *capsule)
int acados_create "{{ model.name }}_acados_create"(nlp_solver_capsule * capsule)
+
+ int acados_create_with_discretization "{{ model.name }}_acados_create_with_discretization"(nlp_solver_capsule * capsule, int n_time_steps, double* new_time_steps)
+ int acados_update_time_steps "{{ model.name }}_acados_update_time_steps"(nlp_solver_capsule * capsule, int N, double* new_time_steps)
+ int acados_update_qp_solver_cond_N "{{ model.name }}_acados_update_qp_solver_cond_N"(nlp_solver_capsule * capsule, int qp_solver_cond_N)
+
int acados_update_params "{{ model.name }}_acados_update_params"(nlp_solver_capsule * capsule, int stage, double *value, int np_)
int acados_solve "{{ model.name }}_acados_solve"(nlp_solver_capsule * capsule)
+ int acados_reset "{{ model.name }}_acados_reset"(nlp_solver_capsule * capsule)
int acados_free "{{ model.name }}_acados_free"(nlp_solver_capsule * capsule)
void acados_print_stats "{{ model.name }}_acados_print_stats"(nlp_solver_capsule * capsule)
acados_solver_common.ocp_nlp_in *acados_get_nlp_in "{{ model.name }}_acados_get_nlp_in"(nlp_solver_capsule * capsule)
acados_solver_common.ocp_nlp_out *acados_get_nlp_out "{{ model.name }}_acados_get_nlp_out"(nlp_solver_capsule * capsule)
+ acados_solver_common.ocp_nlp_out *acados_get_sens_out "{{ model.name }}_acados_get_sens_out"(nlp_solver_capsule * capsule)
acados_solver_common.ocp_nlp_solver *acados_get_nlp_solver "{{ model.name }}_acados_get_nlp_solver"(nlp_solver_capsule * capsule)
acados_solver_common.ocp_nlp_config *acados_get_nlp_config "{{ model.name }}_acados_get_nlp_config"(nlp_solver_capsule * capsule)
void *acados_get_nlp_opts "{{ model.name }}_acados_get_nlp_opts"(nlp_solver_capsule * capsule)
diff --git a/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c b/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c
index a6cd1faa8c..96e0983de6 100644
--- a/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c
+++ b/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c
@@ -37,7 +37,7 @@
#define MDL_START
// acados
-#include "acados/utils/print.h"
+// #include "acados/utils/print.h"
#include "acados_c/sim_interface.h"
#include "acados_c/external_function_interface.h"
@@ -126,10 +126,14 @@ static void mdlInitializeSizes (SimStruct *S)
{%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %} {#- cost_W_0 #}
{%- set n_inputs = n_inputs + 1 %}
{%- endif -%}
- {%- if dims.ny > 0 and simulink_opts.inputs.cost_W -%} {#- cost_W #}
+ {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {#- cost_W #}
{%- set n_inputs = n_inputs + 1 %}
{%- endif -%}
- {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e -%} {#- cost_W_e #}
+ {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %} {#- cost_W_e #}
+ {%- set n_inputs = n_inputs + 1 -%}
+ {%- endif -%}
+
+ {%- if simulink_opts.inputs.reset_solver -%} {#- reset_solver #}
{%- set n_inputs = n_inputs + 1 -%}
{%- endif -%}
@@ -256,7 +260,7 @@ static void mdlInitializeSizes (SimStruct *S)
ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_0 * dims.ny_0 }});
{%- endif %}
- {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W %} {#- cost_W #}
+ {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {#- cost_W #}
{%- set i_input = i_input + 1 %}
// cost_W
ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny * dims.ny }});
@@ -268,6 +272,12 @@ static void mdlInitializeSizes (SimStruct *S)
ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_e * dims.ny_e }});
{%- endif %}
+ {%- if simulink_opts.inputs.reset_solver -%} {#- reset_solver #}
+ {%- set i_input = i_input + 1 %}
+ // reset_solver
+ ssSetInputPortVectorDimension(S, {{ i_input }}, 1);
+ {%- endif -%}
+
{%- if simulink_opts.inputs.x_init -%} {#- x_init #}
{%- set i_input = i_input + 1 %}
// x_init
@@ -406,13 +416,13 @@ static void mdlOutputs(SimStruct *S, int_T tid)
{%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_e)) %}
{%- endif %}
- {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %} {# cost_W_0 #}
+ {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %} {#- cost_W_0 #}
{%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_0 * dims.ny_0)) %}
{%- endif %}
- {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {# cost_W #}
+ {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {#- cost_W #}
{%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny * dims.ny)) %}
{%- endif %}
- {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %} {# cost_W_e #}
+ {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %} {#- cost_W_e #}
{%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_e * dims.ny_e)) %}
{%- endif %}
@@ -602,7 +612,7 @@ static void mdlOutputs(SimStruct *S, int_T tid)
ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "uh", buffer);
{%- endif -%}
- {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %} {# cost_W_0 #}
+ {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %} {#- cost_W_0 #}
// cost_W_0
{%- set i_input = i_input + 1 %}
in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
@@ -612,7 +622,7 @@ static void mdlOutputs(SimStruct *S, int_T tid)
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "W", buffer);
{%- endif %}
- {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {# cost_W #}
+ {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %} {#- cost_W #}
// cost_W
{%- set i_input = i_input + 1 %}
in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
@@ -633,6 +643,17 @@ static void mdlOutputs(SimStruct *S, int_T tid)
ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, {{ dims.N }}, "W", buffer);
{%- endif %}
+ {%- if simulink_opts.inputs.reset_solver %} {#- reset_solver #}
+ // reset_solver
+ {%- set i_input = i_input + 1 %}
+ in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
+ double reset = (double)(*in_sign[0]);
+ if (reset)
+ {
+ {{ model.name }}_acados_reset(capsule);
+ }
+ {%- endif %}
+
{%- if simulink_opts.inputs.x_init %} {#- x_init #}
// x_init
{%- set i_input = i_input + 1 %}
diff --git a/pyextra/acados_template/c_templates_tera/main.in.c b/pyextra/acados_template/c_templates_tera/main.in.c
index 3348eea5cb..99c4f13be1 100644
--- a/pyextra/acados_template/c_templates_tera/main.in.c
+++ b/pyextra/acados_template/c_templates_tera/main.in.c
@@ -156,11 +156,12 @@ int main()
for (int ii = 0; ii < NTIMINGS; ii++)
{
// initialize solution
- for (int i = 0; i <= nlp_dims->N; i++)
+ for (int i = 0; i < N; i++)
{
ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, i, "x", x_init);
ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, i, "u", u0);
}
+ ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, N, "x", x_init);
ocp_nlp_solver_opts_set(nlp_config, nlp_opts, "rti_phase", &rti_phase);
status = {{ model.name }}_acados_solve(acados_ocp_capsule);
ocp_nlp_get(nlp_config, nlp_solver, "time_tot", &elapsed_time);
diff --git a/pyextra/acados_template/c_templates_tera/make_sfun.in.m b/pyextra/acados_template/c_templates_tera/make_sfun.in.m
index 172da654ee..77603a78fa 100644
--- a/pyextra/acados_template/c_templates_tera/make_sfun.in.m
+++ b/pyextra/acados_template/c_templates_tera/make_sfun.in.m
@@ -46,10 +46,14 @@ SOURCES = { ...
'{{ model.name }}_model/{{ model.name }}_impl_dae_hess.c',...
{%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_fun.c',...
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_fun_jac_y.c',...
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_jac_y_uhat.c',...
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
'{{ model.name }}_model/{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.c',...
+ {%- endif %}
+ {%- endif %}
'{{ model.name }}_model/{{ model.name }}_gnsf_get_matrices_fun.c',...
{%- elif solver_options.integrator_type == "DISCRETE" %}
'{{ model.name }}_model/{{ model.name }}_dyn_disc_phi_fun.c',...
@@ -260,6 +264,11 @@ input_note = strcat(input_note, num2str(i_in), ') cost_W_e in column-major forma
i_in = i_in + 1;
{%- endif %}
+{%- if simulink_opts.inputs.reset_solver %} {#- reset_solver #}
+input_note = strcat(input_note, num2str(i_in), ') reset_solver determines if iterate is set to all zeros before other initializations (x_init, u_init) are set and before solver is called, size [1]\n ');
+i_in = i_in + 1;
+{%- endif %}
+
{%- if simulink_opts.inputs.x_init %} {#- x_init #}
input_note = strcat(input_note, num2str(i_in), ') initialization of x for all shooting nodes, size [{{ dims.nx * (dims.N+1) }}]\n ');
i_in = i_in + 1;
diff --git a/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m b/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m
index 1c5cf0b123..a0c503e41a 100644
--- a/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m
+++ b/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m
@@ -47,10 +47,14 @@ SOURCES = [ 'acados_sim_solver_sfunction_{{ model.name }}.c ', ...
'{{ model.name }}_model/{{ model.name }}_impl_dae_hess.c ',...
{%- endif %}
{%- elif solver_options.integrator_type == "GNSF" %}
+ {% if model.gnsf.purely_linear != 1 %}
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_fun.c '
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_fun_jac_y.c '
'{{ model.name }}_model/{{ model.name }}_gnsf_phi_jac_y_uhat.c '
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
'{{ model.name }}_model/{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz.c '
+ {%- endif %}
+ {%- endif %}
'{{ model.name }}_model/{{ model.name }}_gnsf_get_matrices_fun.c '
{%- endif %}
];
diff --git a/pyextra/acados_template/c_templates_tera/mex_solver.in.m b/pyextra/acados_template/c_templates_tera/mex_solver.in.m
index 728741a46e..278e0a605c 100644
--- a/pyextra/acados_template/c_templates_tera/mex_solver.in.m
+++ b/pyextra/acados_template/c_templates_tera/mex_solver.in.m
@@ -125,15 +125,15 @@ classdef {{ model.name }}_mex_solver < handle
if strcmp(field, 'stat')
stat = obj.get('stat');
{%- if solver_options.nlp_solver_type == "SQP" %}
- fprintf('\niter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter');
- if size(stat,2)>7
+ fprintf('\niter\tres_stat\tres_eq\t\tres_ineq\tres_comp\tqp_stat\tqp_iter\talpha');
+ if size(stat,2)>8
fprintf('\tqp_res_stat\tqp_res_eq\tqp_res_ineq\tqp_res_comp');
end
fprintf('\n');
for jj=1:size(stat,1)
- fprintf('%d\t%e\t%e\t%e\t%e\t%d\t%d', stat(jj,1), stat(jj,2), stat(jj,3), stat(jj,4), stat(jj,5), stat(jj,6), stat(jj,7));
- if size(stat,2)>7
- fprintf('\t%e\t%e\t%e\t%e', stat(jj,8), stat(jj,9), stat(jj,10), stat(jj,11));
+ fprintf('%d\t%e\t%e\t%e\t%e\t%d\t%d\t%e', stat(jj,1), stat(jj,2), stat(jj,3), stat(jj,4), stat(jj,5), stat(jj,6), stat(jj,7), stat(jj, 8));
+ if size(stat,2)>8
+ fprintf('\t%e\t%e\t%e\t%e', stat(jj,9), stat(jj,10), stat(jj,11), stat(jj,12));
end
fprintf('\n');
end
diff --git a/pyextra/acados_template/c_templates_tera/model.in.h b/pyextra/acados_template/c_templates_tera/model.in.h
index 661811232c..918e2bc29e 100644
--- a/pyextra/acados_template/c_templates_tera/model.in.h
+++ b/pyextra/acados_template/c_templates_tera/model.in.h
@@ -90,14 +90,7 @@ int {{ model.name }}_impl_dae_hess_n_out(void);
{% elif solver_options.integrator_type == "GNSF" %}
/* GNSF Functions */
-// used to import model matrices
-int {{ model.name }}_gnsf_get_matrices_fun(const double** arg, double** res, int* iw, double* w, void *mem);
-int {{ model.name }}_gnsf_get_matrices_fun_work(int *, int *, int *, int *);
-const int *{{ model.name }}_gnsf_get_matrices_fun_sparsity_in(int);
-const int *{{ model.name }}_gnsf_get_matrices_fun_sparsity_out(int);
-int {{ model.name }}_gnsf_get_matrices_fun_n_in(void);
-int {{ model.name }}_gnsf_get_matrices_fun_n_out(void);
-
+ {% if model.gnsf.purely_linear != 1 %}
// phi_fun
int {{ model.name }}_gnsf_phi_fun(const double** arg, double** res, int* iw, double* w, void *mem);
int {{ model.name }}_gnsf_phi_fun_work(int *, int *, int *, int *);
@@ -121,7 +114,7 @@ const int *{{ model.name }}_gnsf_phi_jac_y_uhat_sparsity_in(int);
const int *{{ model.name }}_gnsf_phi_jac_y_uhat_sparsity_out(int);
int {{ model.name }}_gnsf_phi_jac_y_uhat_n_in(void);
int {{ model.name }}_gnsf_phi_jac_y_uhat_n_out(void);
-
+ {% if model.gnsf.nontrivial_f_LO == 1 %}
// f_lo_fun_jac_x1k1uz
int {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz(const double** arg, double** res, int* iw, double* w, void *mem);
int {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_work(int *, int *, int *, int *);
@@ -129,6 +122,15 @@ const int *{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_sparsity_in(int);
const int *{{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_sparsity_out(int);
int {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_in(void);
int {{ model.name }}_gnsf_f_lo_fun_jac_x1k1uz_n_out(void);
+ {%- endif %}
+ {%- endif %}
+// used to import model matrices
+int {{ model.name }}_gnsf_get_matrices_fun(const double** arg, double** res, int* iw, double* w, void *mem);
+int {{ model.name }}_gnsf_get_matrices_fun_work(int *, int *, int *, int *);
+const int *{{ model.name }}_gnsf_get_matrices_fun_sparsity_in(int);
+const int *{{ model.name }}_gnsf_get_matrices_fun_sparsity_out(int);
+int {{ model.name }}_gnsf_get_matrices_fun_n_in(void);
+int {{ model.name }}_gnsf_get_matrices_fun_n_out(void);
{% elif solver_options.integrator_type == "ERK" %}
/* explicit ODE */
diff --git a/pyextra/acados_template/generate_c_code_discrete_dynamics.py b/pyextra/acados_template/generate_c_code_discrete_dynamics.py
index 334e18dab7..c6a245ff81 100644
--- a/pyextra/acados_template/generate_c_code_discrete_dynamics.py
+++ b/pyextra/acados_template/generate_c_code_discrete_dynamics.py
@@ -32,12 +32,12 @@
#
import os
-from casadi import *
+import casadi as ca
from .utils import ALLOWED_CASADI_VERSIONS, casadi_length, casadi_version_warning
def generate_c_code_discrete_dynamics( model, opts ):
- casadi_version = CasadiMeta.version()
+ casadi_version = ca.CasadiMeta.version()
casadi_opts = dict(mex=False, casadi_int='int', casadi_real='double')
if casadi_version not in (ALLOWED_CASADI_VERSIONS):
@@ -49,13 +49,12 @@ def generate_c_code_discrete_dynamics( model, opts ):
p = model.p
phi = model.disc_dyn_expr
model_name = model.name
- nx = x.size()[0]
+ nx = casadi_length(x)
-
- if isinstance(phi, casadi.MX):
- symbol = MX.sym
- elif isinstance(phi, casadi.SX):
- symbol = SX.sym
+ if isinstance(phi, ca.MX):
+ symbol = ca.MX.sym
+ elif isinstance(phi, ca.SX):
+ symbol = ca.SX.sym
else:
Exception("generate_c_code_disc_dyn: disc_dyn_expr must be a CasADi expression, you have type: {}".format(type(phi)))
@@ -63,12 +62,12 @@ def generate_c_code_discrete_dynamics( model, opts ):
lam = symbol('lam', nx, 1)
# generate jacobians
- ux = vertcat(u,x)
- jac_ux = jacobian(phi, ux)
+ ux = ca.vertcat(u,x)
+ jac_ux = ca.jacobian(phi, ux)
# generate adjoint
- adj_ux = jtimes(phi, ux, lam, True)
+ adj_ux = ca.jtimes(phi, ux, lam, True)
# generate hessian
- hess_ux = jacobian(adj_ux, ux)
+ hess_ux = ca.jacobian(adj_ux, ux)
## change directory
code_export_dir = opts["code_export_directory"]
@@ -85,15 +84,15 @@ def generate_c_code_discrete_dynamics( model, opts ):
# set up & generate Functions
fun_name = model_name + '_dyn_disc_phi_fun'
- phi_fun = Function(fun_name, [x, u, p], [phi])
+ phi_fun = ca.Function(fun_name, [x, u, p], [phi])
phi_fun.generate(fun_name, casadi_opts)
fun_name = model_name + '_dyn_disc_phi_fun_jac'
- phi_fun_jac_ut_xt = Function(fun_name, [x, u, p], [phi, jac_ux.T])
+ phi_fun_jac_ut_xt = ca.Function(fun_name, [x, u, p], [phi, jac_ux.T])
phi_fun_jac_ut_xt.generate(fun_name, casadi_opts)
fun_name = model_name + '_dyn_disc_phi_fun_jac_hess'
- phi_fun_jac_ut_xt_hess = Function(fun_name, [x, u, lam, p], [phi, jac_ux.T, hess_ux])
+ phi_fun_jac_ut_xt_hess = ca.Function(fun_name, [x, u, lam, p], [phi, jac_ux.T, hess_ux])
phi_fun_jac_ut_xt_hess.generate(fun_name, casadi_opts)
os.chdir(cwd)
diff --git a/pyextra/acados_template/simulink_default_opts.json b/pyextra/acados_template/simulink_default_opts.json
index 258a224cb2..70c939cb88 100644
--- a/pyextra/acados_template/simulink_default_opts.json
+++ b/pyextra/acados_template/simulink_default_opts.json
@@ -32,6 +32,7 @@
"cost_W_0": 0,
"cost_W": 0,
"cost_W_e": 0,
+ "reset_solver": 0,
"x_init": 0,
"u_init": 0
},
diff --git a/pyextra/acados_template/utils.py b/pyextra/acados_template/utils.py
index bf8ae4d5db..8f44f61e7e 100644
--- a/pyextra/acados_template/utils.py
+++ b/pyextra/acados_template/utils.py
@@ -1,3 +1,4 @@
+# -*- coding: future_fstrings -*-
#
# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
@@ -49,7 +50,7 @@ def get_acados_path():
ACADOS_PATH = os.path.realpath(acados_path)
msg = 'Warning: Did not find environment variable ACADOS_SOURCE_DIR, '
msg += 'guessed ACADOS_PATH to be {}.\n'.format(ACADOS_PATH)
- msg += 'Please export ACADOS_SOURCE_DIR to not avoid this warning.'
+ msg += 'Please export ACADOS_SOURCE_DIR to avoid this warning.'
print(msg)
return ACADOS_PATH
@@ -74,7 +75,7 @@ def get_tera_exec_path():
platform2tera = {
"linux": "linux",
"darwin": "osx",
- "win32": "window.exe"
+ "win32": "windows"
}
@@ -212,16 +213,14 @@ def render_template(in_file, out_file, template_dir, json_path):
template_glob = os.path.join(acados_path, 'c_templates_tera', '*')
# call tera as system cmd
- os_cmd = "{tera_path} '{template_glob}' '{in_file}' '{json_path}' '{out_file}'".format(
- tera_path=tera_path,
- template_glob=template_glob,
- json_path=json_path,
- in_file=in_file,
- out_file=out_file
- )
+ os_cmd = f"{tera_path} '{template_glob}' '{in_file}' '{json_path}' '{out_file}'"
+ # Windows cmd.exe can not cope with '...', so use "..." instead:
+ if os.name == 'nt':
+ os_cmd = os_cmd.replace('\'', '\"')
+
status = os.system(os_cmd)
if (status != 0):
- raise Exception('Rendering of {} failed!\n\nAttempted to execute OS command:\n{}\n\nExiting.\n'.format(in_file, os_cmd))
+ raise Exception(f'Rendering of {in_file} failed!\n\nAttempted to execute OS command:\n{os_cmd}\n\nExiting.\n')
os.chdir(cwd)
@@ -235,9 +234,7 @@ def np_array_to_list(np_array):
elif isinstance(np_array, (DM)):
return np_array.full()
else:
- raise(Exception(
- "Cannot convert to list type {}".format(type(np_array))
- ))
+ raise(Exception(f"Cannot convert to list type {type(np_array)}"))
def format_class_dict(d):
@@ -254,22 +251,6 @@ def format_class_dict(d):
return out
-def acados_class2dict(class_instance):
- """
- removes the __ artifact from class to dict conversion
- """
-
- d = dict(class_instance.__dict__)
- out = {}
- for k, v in d.items():
- if isinstance(v, dict):
- v = format_class_dict(v)
-
- out_key = k.split('__', 1)[-1]
- out[k.replace(k, out_key)] = v
- return out
-
-
def get_ocp_nlp_layout():
python_interface_path = get_python_interface_path()
abs_path = os.path.join(python_interface_path, 'acados_layout.json')
@@ -433,6 +414,13 @@ def set_up_imported_gnsf_model(acados_formulation):
acados_formulation.model.phi_jac_y_uhat = phi_jac_y_uhat
acados_formulation.model.get_matrices_fun = get_matrices_fun
+ # get_matrices_fun = Function([model_name,'_gnsf_get_matrices_fun'], {dummy},...
+ # {A, B, C, E, L_x, L_xdot, L_z, L_u, A_LO, c, E_LO, B_LO,...
+ # nontrivial_f_LO, purely_linear, ipiv_x, ipiv_z, c_LO});
+ get_matrices_out = get_matrices_fun(0)
+ acados_formulation.model.gnsf['nontrivial_f_LO'] = int(get_matrices_out[12])
+ acados_formulation.model.gnsf['purely_linear'] = int(get_matrices_out[13])
+
if "f_lo_fun_jac_x1k1uz" in gnsf:
f_lo_fun_jac_x1k1uz = Function.deserialize(gnsf['f_lo_fun_jac_x1k1uz'])
acados_formulation.model.f_lo_fun_jac_x1k1uz = f_lo_fun_jac_x1k1uz
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000..d26fc7ff1d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,180 @@
+[tool.poetry]
+name = "openpilot"
+version = "0.1.0"
+description = "an open source driver assistance system"
+authors = ["Vehicle Researcher "]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/commaai/openpilot"
+documentation = "https://docs.comma.ai"
+
+
+[tool.poetry.dependencies]
+python = "~3.8"
+atomicwrites = "^1.4.0"
+casadi = { version = "==3.5.5", markers = "platform_system != 'Darwin'" }
+cffi = "^1.15.1"
+crcmod = "^1.7"
+cryptography = "^37.0.4"
+Cython = "^0.29.30"
+flake8 = "^4.0.1"
+Flask = "^2.1.2"
+future-fstrings = "^1.2.0" # for acados
+gunicorn = "^20.1.0"
+hatanaka = "==2.4"
+hexdump = "^3.3"
+Jinja2 = "^3.1.2"
+json-rpc = "^1.13.0"
+libusb1 = "^3.0.0"
+nose = "^1.3.7"
+numpy = "^1.23.0"
+onnx = "^1.12.0"
+onnxruntime-gpu = { version = "^1.11.1", markers = "platform_system != 'Darwin'" }
+pillow = "^9.2.0"
+poetry = "==1.2.2"
+protobuf = "==3.20.1"
+psutil = "^5.9.1"
+pycapnp = "==1.1.0"
+pycryptodome = "^3.15.0"
+PyJWT = "^2.5.0"
+pylint = "^2.15.4"
+pyopencl = "^2022.2.4"
+pyserial = "^3.5"
+python-dateutil = "^2.8.2"
+PyYAML = "^6.0"
+pyzmq = "^23.2.0"
+requests = "^2.28.1"
+scons = "^4.3.0"
+sentry-sdk = "^1.6.0"
+setproctitle = "^1.2.3"
+six = "^1.16.0"
+smbus2 = "^0.4.2"
+sounddevice = "^0.4.5"
+sympy = "^1.10.1"
+timezonefinder = "^6.0.1"
+tqdm = "^4.64.0"
+urllib3 = "^1.26.10"
+utm = "^0.7.0"
+websocket_client = "^1.3.3"
+spidev = "^3.6"
+
+
+[tool.poetry.group.dev.dependencies]
+av = "^9.2.0"
+azure-storage-blob = "~2.1"
+breathe = "^4.34.0"
+carla = "==0.9.13"
+control = "^0.9.2"
+coverage = "^6.4.1"
+dictdiffer = "^0.9.0"
+fastcluster = "^1.2.6"
+ft4222 = "^1.4.1"
+hexdump = "^3.3"
+hypothesis = "==6.46.7"
+inputs = "^0.5"
+lru-dict = "^1.1.7"
+lxml = "^4.9.1"
+markdown-it-py = "^2.1.0"
+matplotlib = "^3.5.2"
+mpld3 = "^0.5.8"
+mypy = "^0.961"
+myst-parser = "^0.18.0"
+natsort = "^8.1.0"
+numpy = "^1.23.0"
+opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" }
+pandas = "^1.4.3"
+parameterized = "^0.8.1"
+paramiko = "^2.11.0"
+pprofile = "^2.1.0"
+pre-commit = "^2.19.0"
+pycurl = "^7.45.1"
+pygame = "^2.1.2"
+pyprof2calltree = "^1.4.5"
+pytest = "^7.1.2"
+pytest-xdist = "^2.5.0"
+reverse_geocoder = "^1.5.1"
+scipy = "^1.8.1"
+sphinx = "^5.0.2"
+sphinx-rtd-theme = "^1.0.0"
+sphinx-sitemap = "^2.2.0"
+subprocess32 = "^3.5.4"
+tabulate = "^0.8.10"
+tenacity = "^8.0.1"
+types-atomicwrites = "^1.4.5"
+types-certifi = "^2021.10.8"
+types-pycurl = "^7.45.1"
+types-PyYAML = "^6.0"
+types-requests = "^2.28.11"
+
+
+[tool.poetry.group.xx]
+optional = true
+
+[tool.poetry.group.xx.dependencies]
+aenum = "^3.1.11"
+aiohttp = "^3.8.1"
+albumentations = "^1.2.1"
+apex = { url = "https://github.com/commaai/apex/releases/download/pytorch1.10.0%2Bcu11.1/apex-0.1-cp38-cp38-linux_x86_64.whl" }
+azure-cli-core = "^2.38.0"
+azure-common = "^1.1.28"
+azure-core = "^1.24.2"
+azure-nspkg = "~3.0"
+azure-storage-blob = "~2.1"
+azure-storage-common = "~2.1"
+azure-storage-nspkg = "~3.1"
+blosc = "==1.9.2"
+cloudpickle = "^2.1.0"
+configargparse = "^1.5.3"
+cupy-cuda113 = "^10.6.0"
+datadog = "^0.44.0"
+dotmap = "^1.3.30"
+einops = "^0.5.0"
+elasticsearch = "^8.3.1"
+Flask-Cors = "^3.0.10"
+Flask-SocketIO = "^5.2.0"
+GeoAlchemy2 = "^0.12.1"
+imageio = "^2.19.5"
+influxdb-client = "^1.30.0"
+ipykernel = "^6.15.1"
+ipython = "^8.4.0"
+joblib = "^1.1.0"
+json-logging-py = "^0.2"
+jupyter = "^1.0.0"
+jupyterlab = "^3.4.4"
+jupyterlab-vim = "^0.15.1"
+Markdown = "^3.4.1"
+mpld3 = "^0.5.8"
+msgpack-python = "^0.5.6"
+networkx = "~2.3"
+nvidia-ml-py3 = "^7.352.0"
+onnx2torch = "^1.5.4"
+onnxoptimizer = "^0.3.1"
+opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" }
+osmium = "^3.3.0"
+pandas = "^1.4.3"
+pillow-avif-plugin = "^1.2.2"
+pipenv = "==2022.10.12"
+plotly = "^5.9.0"
+pycuda = "^2022.1"
+Pygments = "^2.12.0"
+PyMySQL = "~0.9"
+pyproj = "^3.3.1"
+python-logstash = "^0.4.8"
+redis = "^4.3.4"
+s2sphere = "^0.2.5"
+scikit-image = "^0.19.3"
+scikit-learn = "^1.1.1"
+segmentation-models-pytorch = "==0.2.1"
+simplejson = "^3.17.6"
+SQLAlchemy = "^1.4.39"
+torch = { url = "https://download.pytorch.org/whl/cu113/torch-1.11.0%2Bcu113-cp38-cp38-linux_x86_64.whl" }
+torchsummary = "^1.5.1"
+torchvision = { url = "https://download.pytorch.org/whl/cu113/torchvision-0.12.0%2Bcu113-cp38-cp38-linux_x86_64.whl" }
+triton = "^1.1.1"
+Werkzeug = "^2.1.2"
+zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" }
+
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/rednose_repo b/rednose_repo
index 5b526a8e00..3b6bd703b7 160000
--- a/rednose_repo
+++ b/rednose_repo
@@ -1 +1 @@
-Subproject commit 5b526a8e00bdc1c3922be470af1602cf9dc72dde
+Subproject commit 3b6bd703b7a7667e4f82d0b81ef9a454819b94bd
diff --git a/release/build_devel.sh b/release/build_devel.sh
index db8c69bd7a..668ac0de19 100755
--- a/release/build_devel.sh
+++ b/release/build_devel.sh
@@ -1,33 +1,36 @@
-#!/usr/bin/bash -e
+#!/usr/bin/bash
+
+set -ex
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
-TARGET_DIR=/data/openpilot
-SOURCE_DIR="$(git rev-parse --show-toplevel)"
+SOURCE_DIR="$(git -C $DIR rev-parse --show-toplevel)"
+if [ -z "$TARGET_DIR" ]; then
+ TARGET_DIR="$(mktemp -d)"
+fi
# set git identity
source $DIR/identity.sh
-echo "[-] Setting up repo T=$SECONDS"
-if [ ! -d "$TARGET_DIR" ]; then
- mkdir -p $TARGET_DIR
- cd $TARGET_DIR
- git init
- git remote add origin git@github.com:commaai/openpilot.git
-fi
+echo "[-] Setting up target repo T=$SECONDS"
+
+rm -rf $TARGET_DIR
+mkdir -p $TARGET_DIR
+cd $TARGET_DIR
+cp -r $SOURCE_DIR/.git $TARGET_DIR
+pre-commit uninstall || true
echo "[-] bringing master-ci and devel in sync T=$SECONDS"
cd $TARGET_DIR
-git prune || true
-git remote prune origin || true
-git fetch origin master-ci
-git fetch origin devel
+git fetch --depth 1 origin master-ci
+git fetch --depth 1 origin devel
git checkout -f --track origin/master-ci
git reset --hard master-ci
git checkout master-ci
git reset --hard origin/devel
-git clean -xdf
+git clean -xdff
+git lfs uninstall
# remove everything except .git
echo "[-] erasing old openpilot T=$SECONDS"
@@ -35,36 +38,46 @@ find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -
# reset source tree
cd $SOURCE_DIR
-git clean -xdf
+git clean -xdff
# do the files copy
echo "[-] copying files T=$SECONDS"
cd $SOURCE_DIR
-cp -pR --parents $(cat release/files_common) $TARGET_DIR/
-cp -pR --parents $(cat release/files_tici) $TARGET_DIR/
+cp -pR --parents $(cat release/files_*) $TARGET_DIR/
if [ ! -z "$EXTRA_FILES" ]; then
cp -pR --parents $EXTRA_FILES $TARGET_DIR/
fi
-# append source commit hash and build date to version
-GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse --short HEAD)
-DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
-VERSION=$(cat selfdrive/common/version.h | awk -F\" '{print $2}')
-echo "#define COMMA_VERSION \"$VERSION-$GIT_HASH-$DATETIME\"" > selfdrive/common/version.h
-
# in the directory
cd $TARGET_DIR
rm -f panda/board/obj/panda.bin.signed
+# include source commit hash and build date in commit
+GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
+DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
+VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
+
echo "[-] committing version $VERSION T=$SECONDS"
git add -f .
git status
-git commit -a -m "openpilot v$VERSION release"
+git commit -a -m "openpilot v$VERSION release
+
+date: $DATETIME
+master commit: $GIT_HASH
+"
+
+# ensure files are within GitHub's limit
+BIG_FILES="$(find . -type f -not -path './.git/*' -size +95M)"
+if [ ! -z "$BIG_FILES" ]; then
+ printf '\n\n\n'
+ echo "Found files exceeding GitHub's 100MB limit:"
+ echo "$BIG_FILES"
+ exit 1
+fi
-if [ ! -z "$PUSH" ]; then
- echo "[-] Pushing to $PUSH T=$SECONDS"
- git remote set-url origin git@github.com:commaai/openpilot.git
- git push -f origin master-ci:$PUSH
+if [ ! -z "$BRANCH" ]; then
+ echo "[-] Pushing to $BRANCH T=$SECONDS"
+ git push -f origin master-ci:$BRANCH
fi
-echo "[-] done T=$SECONDS"
+echo "[-] done T=$SECONDS, ready at $TARGET_DIR"
diff --git a/release/build_release.sh b/release/build_release.sh
index 95fcfea1a1..80106eefb2 100755
--- a/release/build_release.sh
+++ b/release/build_release.sh
@@ -13,16 +13,13 @@ if [ -f /TICI ]; then
FILES_SRC="release/files_tici"
RELEASE_BRANCH=release3-staging
DASHCAM_BRANCH=dashcam3-staging
-elif [ -f /EON ]; then
- FILES_SRC="release/files_eon"
- RELEASE_BRANCH=release2-staging
- DASHCAM_BRANCH=dashcam-staging
else
exit 0
fi
# set git identity
source $DIR/identity.sh
+export GIT_SSH_COMMAND="ssh -i /data/gitkey"
echo "[-] Setting up repo T=$SECONDS"
rm -rf $BUILD_DIR
@@ -43,9 +40,10 @@ cp -pR --parents $(cat $FILES_SRC) $BUILD_DIR/
cd $BUILD_DIR
rm -f panda/board/obj/panda.bin.signed
+rm -f panda/board/obj/panda_h7.bin.signed
-VERSION=$(cat selfdrive/common/version.h | awk -F[\"-] '{print $2}')
-echo "#define COMMA_VERSION \"$VERSION-release\"" > selfdrive/common/version.h
+VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
+echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h
echo "[-] committing version $VERSION T=$SECONDS"
git add -f .
@@ -56,6 +54,7 @@ git branch --set-upstream-to=origin/$RELEASE_BRANCH
pushd panda/
CERT=/data/pandaextra/certs/release RELEASE=1 scons -u .
mv board/obj/panda.bin.signed /tmp/panda.bin.signed
+mv board/obj/panda_h7.bin.signed /tmp/panda_h7.bin.signed
popd
# Build
@@ -79,11 +78,12 @@ find . -name 'moc_*' -delete
find . -name '__pycache__' -delete
rm -rf panda/board panda/certs panda/crypto
rm -rf .sconsign.dblite Jenkinsfile release/
-rm models/supercombo.dlc
+rm selfdrive/modeld/models/supercombo.onnx
# Move back signed panda fw
mkdir -p panda/board/obj
mv /tmp/panda.bin.signed panda/board/obj/panda.bin.signed
+mv /tmp/panda_h7.bin.signed panda/board/obj/panda_h7.bin.signed
# Restore third_party
git checkout third_party/
diff --git a/release/check-dirty.sh b/release/check-dirty.sh
new file mode 100755
index 0000000000..9c6389f380
--- /dev/null
+++ b/release/check-dirty.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/bash
+set -e
+
+DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd $DIR
+
+if [ ! -z "$(git status --porcelain)" ]; then
+ echo "Dirty working tree after build:"
+ git status --porcelain
+ exit 1
+fi
diff --git a/release/files_common b/release/files_common
index cfc8150e3b..297a7a808e 100644
--- a/release/files_common
+++ b/release/files_common
@@ -17,6 +17,7 @@ site_scons/site_tools/cython.py
common/.gitignore
common/__init__.py
+common/conversions.py
common/gpio.py
common/realtime.py
common/clock.pyx
@@ -25,10 +26,8 @@ common/ffi_wrapper.py
common/file_helpers.py
common/logging_extra.py
common/numpy_fast.py
-common/markdown.py
common/params.py
common/params_pyx.pyx
-common/xattr.py
common/profiler.py
common/basedir.py
common/dict_helpers.py
@@ -36,7 +35,6 @@ common/filter_simple.py
common/stat_live.py
common/spinner.py
common/text_window.py
-common/SConscript
common/kalman/.gitignore
common/kalman/*
@@ -57,29 +55,26 @@ common/transformations/transformations.pyx
common/api/__init__.py
-models/supercombo.dlc
-models/big_supercombo.dlc
-models/dmonitoring_model_q.dlc
-
release/*
+tools/__init__.py
tools/lib/*
-
-installer/updater/updater
-
-selfdrive/version.py
+tools/joystick/*
+tools/replay/*.cc
+tools/replay/*.h
selfdrive/__init__.py
-selfdrive/config.py
selfdrive/sentry.py
-selfdrive/swaglog.py
-selfdrive/logmessaged.py
selfdrive/tombstoned.py
-selfdrive/pandad.py
selfdrive/updated.py
selfdrive/rtshield.py
selfdrive/statsd.py
+system/logmessaged.py
+system/micd.py
+system/swaglog.py
+system/version.py
+
selfdrive/athena/__init__.py
selfdrive/athena/athenad.py
selfdrive/athena/manage_athenad.py
@@ -96,135 +91,83 @@ selfdrive/boardd/boardd_api_impl.pyx
selfdrive/boardd/can_list_to_can_capnp.cc
selfdrive/boardd/panda.cc
selfdrive/boardd/panda.h
-selfdrive/boardd/pigeon.cc
-selfdrive/boardd/pigeon.h
+selfdrive/boardd/spi.cc
+selfdrive/boardd/panda_comms.h
+selfdrive/boardd/panda_comms.cc
selfdrive/boardd/set_time.py
+selfdrive/boardd/pandad.py
+selfdrive/boardd/tests/test_boardd_loopback.py
selfdrive/car/__init__.py
+selfdrive/car/docs_definitions.py
selfdrive/car/car_helpers.py
selfdrive/car/fingerprints.py
selfdrive/car/interfaces.py
selfdrive/car/vin.py
selfdrive/car/disable_ecu.py
selfdrive/car/fw_versions.py
+selfdrive/car/fw_query_definitions.py
+selfdrive/car/ecu_addrs.py
selfdrive/car/isotp_parallel_query.py
selfdrive/car/tests/__init__.py
selfdrive/car/tests/test_car_interfaces.py
-selfdrive/car/chrysler/__init__.py
-selfdrive/car/chrysler/carstate.py
-selfdrive/car/chrysler/interface.py
-selfdrive/car/chrysler/radar_interface.py
-selfdrive/car/chrysler/values.py
-selfdrive/car/chrysler/carcontroller.py
-selfdrive/car/chrysler/chryslercan.py
-selfdrive/car/honda/__init__.py
-selfdrive/car/honda/carstate.py
-selfdrive/car/honda/interface.py
-selfdrive/car/honda/radar_interface.py
-selfdrive/car/honda/values.py
-selfdrive/car/honda/carcontroller.py
-selfdrive/car/honda/hondacan.py
-selfdrive/car/hyundai/__init__.py
-selfdrive/car/hyundai/carstate.py
-selfdrive/car/hyundai/interface.py
-selfdrive/car/hyundai/radar_interface.py
-selfdrive/car/hyundai/values.py
-selfdrive/car/hyundai/carcontroller.py
-selfdrive/car/hyundai/hyundaican.py
-selfdrive/car/toyota/__init__.py
-selfdrive/car/toyota/carstate.py
-selfdrive/car/toyota/tunes.py
-selfdrive/car/toyota/interface.py
-selfdrive/car/toyota/radar_interface.py
-selfdrive/car/toyota/values.py
-selfdrive/car/toyota/carcontroller.py
-selfdrive/car/toyota/toyotacan.py
-selfdrive/car/nissan/__init__.py
-selfdrive/car/nissan/carcontroller.py
-selfdrive/car/nissan/carstate.py
-selfdrive/car/nissan/interface.py
-selfdrive/car/nissan/nissancan.py
-selfdrive/car/nissan/radar_interface.py
-selfdrive/car/nissan/values.py
-selfdrive/car/volkswagen/__init__.py
-selfdrive/car/volkswagen/carstate.py
-selfdrive/car/volkswagen/interface.py
-selfdrive/car/volkswagen/radar_interface.py
-selfdrive/car/volkswagen/values.py
-selfdrive/car/volkswagen/carcontroller.py
-selfdrive/car/volkswagen/volkswagencan.py
-selfdrive/car/gm/__init__.py
-selfdrive/car/gm/carstate.py
-selfdrive/car/gm/interface.py
-selfdrive/car/gm/radar_interface.py
-selfdrive/car/gm/values.py
-selfdrive/car/gm/carcontroller.py
-selfdrive/car/gm/gmcan.py
-selfdrive/car/ford/__init__.py
-selfdrive/car/ford/carstate.py
-selfdrive/car/ford/interface.py
-selfdrive/car/ford/radar_interface.py
-selfdrive/car/ford/values.py
-selfdrive/car/ford/carcontroller.py
-selfdrive/car/ford/fordcan.py
-selfdrive/car/subaru/__init__.py
-selfdrive/car/subaru/carstate.py
-selfdrive/car/subaru/interface.py
-selfdrive/car/subaru/radar_interface.py
-selfdrive/car/subaru/values.py
-selfdrive/car/subaru/carcontroller.py
-selfdrive/car/subaru/subarucan.py
-selfdrive/car/mazda/__init__.py
-selfdrive/car/mazda/carstate.py
-selfdrive/car/mazda/interface.py
-selfdrive/car/mazda/radar_interface.py
-selfdrive/car/mazda/values.py
-selfdrive/car/mazda/carcontroller.py
-selfdrive/car/mazda/mazdacan.py
-selfdrive/car/tesla/__init__.py
-selfdrive/car/tesla/teslacan.py
-selfdrive/car/tesla/carcontroller.py
-selfdrive/car/tesla/radar_interface.py
-selfdrive/car/tesla/values.py
-selfdrive/car/tesla/carstate.py
-selfdrive/car/tesla/interface.py
+selfdrive/car/torque_data/params.yaml
+selfdrive/car/torque_data/substitute.yaml
+selfdrive/car/torque_data/override.yaml
+
+selfdrive/car/body/*.py
+selfdrive/car/chrysler/*.py
+selfdrive/car/ford/*.py
+selfdrive/car/gm/*.py
+selfdrive/car/honda/*.py
+selfdrive/car/hyundai/*.py
+selfdrive/car/mazda/*.py
selfdrive/car/mock/*.py
+selfdrive/car/nissan/*.py
+selfdrive/car/subaru/*.py
+selfdrive/car/tesla/*.py
+selfdrive/car/toyota/*.py
+selfdrive/car/volkswagen/*.py
+
+system/clocksd/.gitignore
+system/clocksd/SConscript
+system/clocksd/clocksd.cc
+
+selfdrive/debug/can_printer.py
+selfdrive/debug/check_freq.py
+selfdrive/debug/dump.py
+selfdrive/debug/filter_log_message.py
+selfdrive/debug/get_fingerprint.py
+selfdrive/debug/uiview.py
+
+selfdrive/debug/hyundai_enable_radar_points.py
+selfdrive/debug/vw_mqb_config.py
-selfdrive/clocksd/.gitignore
-selfdrive/clocksd/SConscript
-selfdrive/clocksd/clocksd.cc
-
-selfdrive/debug/*.py
-
-selfdrive/common/SConscript
-selfdrive/common/version.h
-
-selfdrive/common/swaglog.h
-selfdrive/common/swaglog.cc
-selfdrive/common/statlog.h
-selfdrive/common/statlog.cc
-selfdrive/common/util.cc
-selfdrive/common/util.h
-selfdrive/common/queue.h
-selfdrive/common/clutil.cc
-selfdrive/common/clutil.h
-selfdrive/common/params.h
-selfdrive/common/params.cc
-selfdrive/common/watchdog.cc
-selfdrive/common/watchdog.h
-
-selfdrive/common/modeldata.h
-selfdrive/common/mat.h
-selfdrive/common/timing.h
-
-selfdrive/common/visionimg.cc
-selfdrive/common/visionimg.h
-
-selfdrive/common/gpio.cc
-selfdrive/common/gpio.h
-selfdrive/common/i2c.cc
-selfdrive/common/i2c.h
-
+common/SConscript
+common/version.h
+
+common/swaglog.h
+common/swaglog.cc
+common/statlog.h
+common/statlog.cc
+common/util.cc
+common/util.h
+common/queue.h
+common/clutil.cc
+common/clutil.h
+common/params.h
+common/params.cc
+common/watchdog.cc
+common/watchdog.h
+
+common/modeldata.h
+common/mat.h
+common/timing.h
+
+common/gpio.cc
+common/gpio.h
+common/i2c.cc
+common/i2c.h
selfdrive/controls/__init__.py
selfdrive/controls/controlsd.py
@@ -236,10 +179,9 @@ selfdrive/controls/lib/alerts_offroad.json
selfdrive/controls/lib/desire_helper.py
selfdrive/controls/lib/drive_helpers.py
selfdrive/controls/lib/events.py
-selfdrive/controls/lib/lane_planner.py
selfdrive/controls/lib/latcontrol_angle.py
selfdrive/controls/lib/latcontrol_indi.py
-selfdrive/controls/lib/latcontrol_lqr.py
+selfdrive/controls/lib/latcontrol_torque.py
selfdrive/controls/lib/latcontrol_pid.py
selfdrive/controls/lib/latcontrol.py
selfdrive/controls/lib/lateral_planner.py
@@ -256,24 +198,27 @@ selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore
selfdrive/controls/lib/lateral_mpc_lib/*
selfdrive/controls/lib/longitudinal_mpc_lib/*
-selfdrive/hardware/__init__.py
-selfdrive/hardware/base.h
-selfdrive/hardware/base.py
-selfdrive/hardware/hw.h
-selfdrive/hardware/eon/__init__.py
-selfdrive/hardware/eon/androidd.py
-selfdrive/hardware/eon/shutdownd.py
-selfdrive/hardware/eon/hardware.h
-selfdrive/hardware/eon/hardware.py
-selfdrive/hardware/eon/neos.py
-selfdrive/hardware/eon/neos.json
-selfdrive/hardware/eon/updater
-selfdrive/hardware/tici/__init__.py
-selfdrive/hardware/tici/hardware.py
-selfdrive/hardware/tici/amplifier.py
-selfdrive/hardware/tici/iwlist.py
-selfdrive/hardware/pc/__init__.py
-selfdrive/hardware/pc/hardware.py
+selfdrive/hardware
+
+system/__init__.py
+
+system/hardware/__init__.py
+system/hardware/base.h
+system/hardware/base.py
+system/hardware/hw.h
+system/hardware/tici/__init__.py
+system/hardware/tici/hardware.h
+system/hardware/tici/hardware.py
+system/hardware/tici/pins.py
+system/hardware/tici/agnos.py
+system/hardware/tici/casync.py
+system/hardware/tici/agnos.json
+system/hardware/tici/amplifier.py
+system/hardware/tici/updater
+system/hardware/tici/iwlist.py
+system/hardware/pc/__init__.py
+system/hardware/pc/hardware.h
+system/hardware/pc/hardware.py
selfdrive/locationd/__init__.py
selfdrive/locationd/.gitignore
@@ -286,40 +231,49 @@ selfdrive/locationd/generated/ubx.h
selfdrive/locationd/generated/gps.cpp
selfdrive/locationd/generated/gps.h
+selfdrive/locationd/laikad.py
+selfdrive/locationd/laikad_helpers.py
selfdrive/locationd/locationd.h
selfdrive/locationd/locationd.cc
selfdrive/locationd/paramsd.py
+selfdrive/locationd/models/__init__.py
selfdrive/locationd/models/.gitignore
-selfdrive/locationd/models/live_kf.py
selfdrive/locationd/models/car_kf.py
-selfdrive/locationd/models/constants.py
+selfdrive/locationd/models/gnss_kf.py
+selfdrive/locationd/models/live_kf.py
selfdrive/locationd/models/live_kf.h
selfdrive/locationd/models/live_kf.cc
+selfdrive/locationd/models/constants.py
+selfdrive/locationd/models/gnss_helpers.py
+selfdrive/locationd/torqued.py
selfdrive/locationd/calibrationd.py
-selfdrive/logcatd/SConscript
-selfdrive/logcatd/logcatd_android.cc
-selfdrive/logcatd/logcatd_systemd.cc
+system/logcatd/.gitignore
+system/logcatd/SConscript
+system/logcatd/logcatd_systemd.cc
-selfdrive/proclogd/SConscript
-selfdrive/proclogd/main.cc
-selfdrive/proclogd/proclog.cc
-selfdrive/proclogd/proclog.h
+system/proclogd/SConscript
+system/proclogd/main.cc
+system/proclogd/proclog.cc
+system/proclogd/proclog.h
+selfdrive/loggerd/.gitignore
selfdrive/loggerd/SConscript
-selfdrive/loggerd/encoder.h
-selfdrive/loggerd/omx_encoder.cc
-selfdrive/loggerd/omx_encoder.h
+selfdrive/loggerd/encoder/encoder.cc
+selfdrive/loggerd/encoder/encoder.h
+selfdrive/loggerd/encoder/v4l_encoder.cc
+selfdrive/loggerd/encoder/v4l_encoder.h
+selfdrive/loggerd/video_writer.cc
+selfdrive/loggerd/video_writer.h
selfdrive/loggerd/logger.cc
selfdrive/loggerd/logger.h
selfdrive/loggerd/loggerd.cc
selfdrive/loggerd/loggerd.h
-selfdrive/loggerd/main.cc
+selfdrive/loggerd/encoderd.cc
selfdrive/loggerd/bootlog.cc
-selfdrive/loggerd/raw_logger.cc
-selfdrive/loggerd/raw_logger.h
-selfdrive/loggerd/include/msm_media_info.h
+selfdrive/loggerd/encoder/ffmpeg_encoder.cc
+selfdrive/loggerd/encoder/ffmpeg_encoder.h
selfdrive/loggerd/__init__.py
selfdrive/loggerd/config.py
@@ -329,19 +283,19 @@ selfdrive/loggerd/xattr_cache.py
selfdrive/sensord/SConscript
selfdrive/sensord/libdiag.h
-selfdrive/sensord/sensors_qcom.cc
selfdrive/sensord/sensors_qcom2.cc
selfdrive/sensord/sensors/*.cc
selfdrive/sensord/sensors/*.h
selfdrive/sensord/sensord
+selfdrive/sensord/pigeond.py
selfdrive/thermald/thermald.py
selfdrive/thermald/power_monitoring.py
+selfdrive/thermald/fan_controller.py
selfdrive/test/__init__.py
selfdrive/test/helpers.py
selfdrive/test/setup_device_ci.sh
-selfdrive/test/test_fingerprints.py
selfdrive/test/test_onroad.py
selfdrive/ui/.gitignore
@@ -355,6 +309,10 @@ selfdrive/ui/soundd/*.cc
selfdrive/ui/soundd/*.h
selfdrive/ui/soundd/soundd
selfdrive/ui/soundd/.gitignore
+selfdrive/ui/translations/*.ts
+selfdrive/ui/translations/languages.json
+selfdrive/ui/update_translations.py
+selfdrive/ui/tests/test_translations.py
selfdrive/ui/qt/*.cc
selfdrive/ui/qt/*.h
@@ -363,36 +321,22 @@ selfdrive/ui/qt/offroad/*.h
selfdrive/ui/qt/offroad/*.qml
selfdrive/ui/qt/widgets/*.cc
selfdrive/ui/qt/widgets/*.h
-selfdrive/ui/qt/spinner_aarch64
-selfdrive/ui/qt/text_aarch64
-
-selfdrive/ui/replay/*.cc
-selfdrive/ui/replay/*.h
-
-selfdrive/camerad/SConscript
-selfdrive/camerad/main.cc
-
-selfdrive/camerad/snapshot/*
-selfdrive/camerad/include/*
-selfdrive/camerad/cameras/camera_common.h
-selfdrive/camerad/cameras/camera_common.cc
-selfdrive/camerad/cameras/camera_qcom.cc
-selfdrive/camerad/cameras/camera_qcom.h
-selfdrive/camerad/cameras/camera_replay.cc
-selfdrive/camerad/cameras/camera_replay.h
-selfdrive/camerad/cameras/debayer.cl
-selfdrive/camerad/cameras/sensor_i2c.h
-selfdrive/camerad/cameras/sensor2_i2c.h
-
-selfdrive/camerad/transforms/rgb_to_yuv.cc
-selfdrive/camerad/transforms/rgb_to_yuv.h
-selfdrive/camerad/transforms/rgb_to_yuv.cl
-selfdrive/camerad/transforms/rgb_to_yuv_test.cc
-
-selfdrive/camerad/imgproc/conv.cl
-selfdrive/camerad/imgproc/pool.cl
-selfdrive/camerad/imgproc/utils.cc
-selfdrive/camerad/imgproc/utils.h
+selfdrive/ui/qt/maps/*.cc
+selfdrive/ui/qt/maps/*.h
+
+system/camerad/SConscript
+system/camerad/main.cc
+
+system/camerad/snapshot/*
+system/camerad/include/*
+system/camerad/cameras/camera_common.h
+system/camerad/cameras/camera_common.cc
+system/camerad/cameras/sensor2_i2c.h
+
+system/camerad/imgproc/conv.cl
+system/camerad/imgproc/pool.cl
+system/camerad/imgproc/utils.cc
+system/camerad/imgproc/utils.h
selfdrive/manager/__init__.py
selfdrive/manager/build.py
@@ -403,19 +347,30 @@ selfdrive/manager/process.py
selfdrive/manager/test/__init__.py
selfdrive/manager/test/test_manager.py
+selfdrive/modeld/__init__.py
selfdrive/modeld/SConscript
selfdrive/modeld/modeld.cc
+selfdrive/modeld/navmodeld.cc
selfdrive/modeld/dmonitoringmodeld.cc
selfdrive/modeld/constants.py
selfdrive/modeld/modeld
+selfdrive/modeld/navmodeld
selfdrive/modeld/dmonitoringmodeld
selfdrive/modeld/models/commonmodel.cc
selfdrive/modeld/models/commonmodel.h
+
selfdrive/modeld/models/driving.cc
selfdrive/modeld/models/driving.h
+selfdrive/modeld/models/supercombo.onnx
+
selfdrive/modeld/models/dmonitoring.cc
selfdrive/modeld/models/dmonitoring.h
+selfdrive/modeld/models/dmonitoring_model_q.dlc
+
+selfdrive/modeld/models/nav.cc
+selfdrive/modeld/models/nav.h
+selfdrive/modeld/models/navmodel_q.dlc
selfdrive/modeld/transforms/loadyuv.cc
selfdrive/modeld/transforms/loadyuv.h
@@ -424,12 +379,12 @@ selfdrive/modeld/transforms/transform.cc
selfdrive/modeld/transforms/transform.h
selfdrive/modeld/transforms/transform.cl
-selfdrive/modeld/thneed/thneed.*
+selfdrive/modeld/thneed/*.py
+selfdrive/modeld/thneed/thneed.h
+selfdrive/modeld/thneed/thneed_common.cc
+selfdrive/modeld/thneed/thneed_qcom2.cc
selfdrive/modeld/thneed/serialize.cc
-selfdrive/modeld/thneed/compile.cc
-selfdrive/modeld/thneed/optimizer.cc
selfdrive/modeld/thneed/include/*
-selfdrive/modeld/thneed/kernels/*.cl
selfdrive/modeld/runners/snpemodel.cc
selfdrive/modeld/runners/snpemodel.h
@@ -441,10 +396,15 @@ selfdrive/modeld/runners/run.h
selfdrive/monitoring/dmonitoringd.py
selfdrive/monitoring/driver_monitor.py
+selfdrive/navd/__init__.py
+selfdrive/navd/navd.py
+selfdrive/navd/helpers.py
+
selfdrive/assets/.gitignore
selfdrive/assets/assets.qrc
selfdrive/assets/*.png
selfdrive/assets/*.svg
+selfdrive/assets/body/*
selfdrive/assets/fonts/*.ttf
selfdrive/assets/icons/*
selfdrive/assets/images/*
@@ -454,12 +414,8 @@ selfdrive/assets/training/*
third_party/SConscript
-third_party/libgralloc/**
third_party/linux/**
third_party/opencl/**
-third_party/zlib/*
-third_party/bzip2/*
-third_party/openmax/**
third_party/json11/json11.cpp
third_party/json11/json11.hpp
@@ -475,18 +431,13 @@ third_party/libyuv/lib/**
third_party/libyuv/larch64/**
third_party/snpe/include/**
-third_party/snpe/aarch64**
-third_party/snpe/larch64**
third_party/snpe/dsp**
third_party/acados/x86_64/**
-third_party/acados/aarch64/**
third_party/acados/larch64/**
third_party/acados/include/**
-third_party/android_frameworks_native/**
-third_party/android_hardware_libhardware/**
-third_party/android_system_core/**
+third_party/qt5/larch64/bin/**
scripts/update_now.sh
scripts/stop_updater.sh
@@ -494,7 +445,22 @@ scripts/stop_updater.sh
pyextra/.gitignore
pyextra/acados_template/**
+rednose/.gitignore
rednose/**
+laika/**
+
+body/.gitignore
+body/board/SConscript
+body/board/*.h
+body/board/*.c
+body/board/*.s
+body/board/*.ld
+body/board/inc/**
+body/board/obj/
+body/board/bldc/**
+body/board/drivers/**
+body/certs/**
+body/crypto/**
cereal/.gitignore
cereal/__init__.py
@@ -528,6 +494,7 @@ cereal/visionipc/*.pxd
panda/.gitignore
panda/__init__.py
+panda/SConscript
panda/board/**
panda/certs/**
panda/crypto/**
@@ -544,27 +511,27 @@ opendbc/can/common.h
opendbc/can/common.pxd
opendbc/can/common_dbc.h
opendbc/can/dbc.cc
-opendbc/can/dbc.py
-opendbc/can/dbc_template.cc
opendbc/can/packer.cc
opendbc/can/packer.py
opendbc/can/packer_pyx.pyx
opendbc/can/parser.cc
opendbc/can/parser.py
opendbc/can/parser_pyx.pyx
-opendbc/can/process_dbc.py
-opendbc/can/dbc_out/.gitkeep
-opendbc/can/dbc_out/.gitignore
-opendbc/chrysler_pacifica_2017_hybrid.dbc
+opendbc/comma_body.dbc
+
+opendbc/chrysler_ram_hd_generated.dbc
+opendbc/chrysler_ram_dt_generated.dbc
+opendbc/chrysler_pacifica_2017_hybrid_generated.dbc
opendbc/chrysler_pacifica_2017_hybrid_private_fusion.dbc
opendbc/gm_global_a_powertrain_generated.dbc
opendbc/gm_global_a_object.dbc
opendbc/gm_global_a_chassis.dbc
-opendbc/ford_fusion_2018_pt.dbc
+opendbc/FORD_CADS.dbc
opendbc/ford_fusion_2018_adas.dbc
+opendbc/ford_lincoln_base_pt.dbc
opendbc/honda_accord_2018_can_generated.dbc
opendbc/acura_ilx_2016_can_generated.dbc
@@ -581,9 +548,11 @@ opendbc/honda_odyssey_exl_2018_generated.dbc
opendbc/honda_odyssey_extreme_edition_2018_china_can_generated.dbc
opendbc/honda_insight_ex_2019_can_generated.dbc
opendbc/acura_ilx_2016_nidec.dbc
+opendbc/honda_civic_ex_2022_can_generated.dbc
+opendbc/hyundai_canfd.dbc
opendbc/hyundai_kia_generic.dbc
-opendbc/hyundai_kia_mando_front_radar.dbc
+opendbc/hyundai_kia_mando_front_radar_generated.dbc
opendbc/mazda_2017.dbc
@@ -601,8 +570,24 @@ opendbc/toyota_nodsu_pt_generated.dbc
opendbc/toyota_adas.dbc
opendbc/toyota_tss2_adas.dbc
+opendbc/vw_golf_mk4.dbc
opendbc/vw_mqb_2010.dbc
opendbc/tesla_can.dbc
opendbc/tesla_radar.dbc
opendbc/tesla_powertrain.dbc
+
+tinygrad_repo/openpilot/compile.py
+tinygrad_repo/accel/opencl/*
+tinygrad_repo/extra/onnx.py
+tinygrad_repo/extra/thneed.py
+tinygrad_repo/extra/utils.py
+tinygrad_repo/tinygrad/llops/ops_gpu.py
+tinygrad_repo/tinygrad/llops/ops_opencl.py
+tinygrad_repo/tinygrad/helpers.py
+tinygrad_repo/tinygrad/mlops.py
+tinygrad_repo/tinygrad/ops.py
+tinygrad_repo/tinygrad/shapetracker.py
+tinygrad_repo/tinygrad/tensor.py
+tinygrad_repo/tinygrad/nn/__init__.py
+tinygrad_repo/tinygrad/nn/optim.py
diff --git a/release/files_eon b/release/files_eon
deleted file mode 100644
index b43bf86b50..0000000000
--- a/release/files_eon
+++ /dev/null
@@ -1 +0,0 @@
-README.md
diff --git a/release/files_pc b/release/files_pc
index d00de7e475..01ecae4327 100644
--- a/release/files_pc
+++ b/release/files_pc
@@ -1,5 +1,7 @@
-selfdrive/ui/replay/*
+selfdrive/modeld/runners/onnx*
-third_party/mapbox-gl-native-qt/x86_64/**
+third_party/mapbox-gl-native-qt/x86_64/*.so
-third_party/qt-plugins/x86_64/**
+third_party/libyuv/x64/**
+third_party/snpe/x86_64/**
+third_party/snpe/x86_64-linux-clang/**
diff --git a/release/files_tici b/release/files_tici
index 59cc41918f..c8abd720d5 100644
--- a/release/files_tici
+++ b/release/files_tici
@@ -1,30 +1,19 @@
+third_party/snpe/larch64**
+third_party/snpe/aarch64-ubuntu-gcc7.5/*
third_party/mapbox-gl-native-qt/include/*
-selfdrive/timezoned.py
+system/timezoned.py
selfdrive/assets/navigation/*
selfdrive/assets/training_wide/*
-selfdrive/camerad/cameras/camera_qcom2.cc
-selfdrive/camerad/cameras/camera_qcom2.h
-selfdrive/camerad/cameras/real_debayer.cl
+system/camerad/cameras/camera_qcom2.cc
+system/camerad/cameras/camera_qcom2.h
+system/camerad/cameras/camera_util.cc
+system/camerad/cameras/camera_util.h
+system/camerad/cameras/real_debayer.cl
-selfdrive/hardware/tici/__init__.py
-selfdrive/hardware/tici/hardware.h
-selfdrive/hardware/tici/hardware.py
-selfdrive/hardware/tici/pins.py
-selfdrive/hardware/tici/agnos.py
-selfdrive/hardware/tici/agnos.json
-selfdrive/hardware/tici/amplifier.py
-selfdrive/hardware/tici/updater
+selfdrive/sensord/rawgps/*
selfdrive/ui/qt/spinner_larch64
selfdrive/ui/qt/text_larch64
-selfdrive/ui/qt/maps/*.cc
-selfdrive/ui/qt/maps/*.h
-
-selfdrive/ui/navd/*.cc
-selfdrive/ui/navd/*.h
-selfdrive/ui/navd/navd
-selfdrive/ui/navd/.gitignore
-
diff --git a/release/identity.sh b/release/identity.sh
index b90372d825..c699c94650 100644
--- a/release/identity.sh
+++ b/release/identity.sh
@@ -2,4 +2,3 @@ export GIT_COMMITTER_NAME="Vehicle Researcher"
export GIT_COMMITTER_EMAIL="user@comma.ai"
export GIT_AUTHOR_NAME="Vehicle Researcher"
export GIT_AUTHOR_EMAIL="user@comma.ai"
-export GIT_SSH_COMMAND="ssh -i /data/gitkey"
diff --git a/release/verify.sh b/release/verify.sh
index 2ebd50a29d..56f21183f1 100755
--- a/release/verify.sh
+++ b/release/verify.sh
@@ -6,7 +6,7 @@ RED="\033[0;31m"
GREEN="\033[0;32m"
CLEAR="\033[0m"
-BRANCHES="devel dashcam dashcam3 release2 release3"
+BRANCHES="devel dashcam3 release3"
for b in $BRANCHES; do
if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then
printf "%-10s $GREEN ok $CLEAR\n" "$b"
diff --git a/scripts/cell.sh b/scripts/cell.sh
new file mode 100755
index 0000000000..cae701eccc
--- /dev/null
+++ b/scripts/cell.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/bash
+
+nmcli connection modify --temporary lte ipv4.route-metric 1
+nmcli connection modify --temporary lte ipv6.route-metric 1
+nmcli con up lte
diff --git a/scripts/clwaste b/scripts/clwaste
deleted file mode 100755
index 51770f0e37..0000000000
Binary files a/scripts/clwaste and /dev/null differ
diff --git a/scripts/count_cars.py b/scripts/count_cars.py
index b01f97570e..25bad2c9b4 100755
--- a/scripts/count_cars.py
+++ b/scripts/count_cars.py
@@ -1,15 +1,11 @@
#!/usr/bin/env python3
-import os
from collections import Counter
from pprint import pprint
-from common.basedir import BASEDIR
+from selfdrive.car.docs import get_all_car_info
-with open(os.path.join(BASEDIR, "docs/CARS.md")) as f:
- lines = f.readlines()
- cars = [l for l in lines if l.strip().startswith("|") and l.strip().endswith("|") and
- "Make" not in l and any(c.isalpha() for c in l)]
-
- make_count = Counter(l.split('|')[1].split('|')[0].strip() for l in cars)
- print("\n", "*"*20, len(cars), "total", "*"*20, "\n")
+if __name__ == "__main__":
+ cars = get_all_car_info()
+ make_count = Counter(l.make for l in cars)
+ print("\n", "*" * 20, len(cars), "total", "*" * 20, "\n")
pprint(make_count)
diff --git a/scripts/disable-powersave.py b/scripts/disable-powersave.py
index f651bc87f1..93688504f3 100755
--- a/scripts/disable-powersave.py
+++ b/scripts/disable-powersave.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-from selfdrive.hardware import HARDWARE
+from system.hardware import HARDWARE
if __name__ == "__main__":
HARDWARE.set_power_save(False)
diff --git a/scripts/launch_corolla.sh b/scripts/launch_corolla.sh
new file mode 100755
index 0000000000..146fbacf0a
--- /dev/null
+++ b/scripts/launch_corolla.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/bash
+
+DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+
+export FINGERPRINT="TOYOTA COROLLA TSS2 2019"
+export SKIP_FW_QUERY="1"
+$DIR/../launch_openpilot.sh
diff --git a/scripts/restart_modem.sh b/scripts/restart_modem.sh
deleted file mode 100755
index fac54b32ff..0000000000
--- a/scripts/restart_modem.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/bash
-echo "restart" > /sys/kernel/debug/msm_subsys/modem
diff --git a/scripts/switch_to_master.sh b/scripts/switch_to_master.sh
deleted file mode 100755
index cad51eb549..0000000000
--- a/scripts/switch_to_master.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/bash
-
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-cd $DIR/..
-
-git clean -xdf .
-git rm -r --cached .
-
-git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
-git fetch origin master
-git checkout master
-git reset --hard
-git submodule update --init
-
-printf '\n\n'
-echo "master checked out. reboot to start building openpilot master"
diff --git a/scripts/throttling.sh b/scripts/throttling.sh
deleted file mode 100755
index d100c5f5ab..0000000000
--- a/scripts/throttling.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/data/data/com.termux/files/usr/bin/bash
-watch -n1 '
- cat /sys/kernel/debug/clk/pwrcl_clk/measure
- cat /sys/kernel/debug/clk/perfcl_clk/measure
- cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
- cat /sys/class/kgsl/kgsl-3d0/gpuclk
- echo
- echo -n "CPU0 " ; cat /sys/devices/virtual/thermal/thermal_zone5/temp
- echo -n "CPU1 " ; cat /sys/devices/virtual/thermal/thermal_zone7/temp
- echo -n "CPU2 " ; cat /sys/devices/virtual/thermal/thermal_zone10/temp
- echo -n "CPU3 " ; cat /sys/devices/virtual/thermal/thermal_zone12/temp
- echo -n "MEM " ; cat /sys/devices/virtual/thermal/thermal_zone2/temp
- echo -n "GPU " ; cat /sys/devices/virtual/thermal/thermal_zone16/temp
- echo -n "BAT " ; cat /sys/devices/virtual/thermal/thermal_zone29/temp
-'
-
diff --git a/scripts/waste b/scripts/waste
deleted file mode 100755
index e3154ab01f..0000000000
Binary files a/scripts/waste and /dev/null differ
diff --git a/scripts/waste.c b/scripts/waste.c
index 62233b7fc4..2e492916a7 100644
--- a/scripts/waste.c
+++ b/scripts/waste.c
@@ -11,7 +11,7 @@
#include
#include
#include
-#include "../selfdrive/common/timing.h"
+#include "../common/timing.h"
int get_nprocs(void);
double *ttime, *oout;
diff --git a/scripts/waste.py b/scripts/waste.py
index f2820ea670..d3c96bf198 100755
--- a/scripts/waste.py
+++ b/scripts/waste.py
@@ -6,7 +6,7 @@ from multiprocessing import Process
from setproctitle import setproctitle # pylint: disable=no-name-in-module
def waste(core):
- os.sched_setaffinity(0, [core,])
+ os.sched_setaffinity(0, [core,]) # pylint: disable=no-member
m1 = np.zeros((200, 200)) + 0.8
m2 = np.zeros((200, 200)) + 1.2
diff --git a/selfdrive/assets/body/awake.gif b/selfdrive/assets/body/awake.gif
new file mode 100644
index 0000000000..7ec67055dd
Binary files /dev/null and b/selfdrive/assets/body/awake.gif differ
diff --git a/selfdrive/assets/body/sleep.gif b/selfdrive/assets/body/sleep.gif
new file mode 100644
index 0000000000..469cc80338
Binary files /dev/null and b/selfdrive/assets/body/sleep.gif differ
diff --git a/selfdrive/assets/compress-images.sh b/selfdrive/assets/compress-images.sh
index 607b27c2a8..8601b2d61b 100755
--- a/selfdrive/assets/compress-images.sh
+++ b/selfdrive/assets/compress-images.sh
@@ -2,3 +2,6 @@
echo "compressing training guide images"
optipng -o7 -strip all training/* training_wide/*
+
+# This can sometimes provide smaller images
+# mogrify -quality 100 -format jpg training_wide/* training/*
diff --git a/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf
new file mode 100644
index 0000000000..a6ba5529af
Binary files /dev/null and b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf differ
diff --git a/selfdrive/assets/fonts/opensans_bold.ttf b/selfdrive/assets/fonts/opensans_bold.ttf
deleted file mode 100644
index 7b52945603..0000000000
Binary files a/selfdrive/assets/fonts/opensans_bold.ttf and /dev/null differ
diff --git a/selfdrive/assets/fonts/opensans_regular.ttf b/selfdrive/assets/fonts/opensans_regular.ttf
deleted file mode 100644
index 2e31d02424..0000000000
Binary files a/selfdrive/assets/fonts/opensans_regular.ttf and /dev/null differ
diff --git a/selfdrive/assets/fonts/opensans_semibold.ttf b/selfdrive/assets/fonts/opensans_semibold.ttf
deleted file mode 100644
index 99db86aa02..0000000000
Binary files a/selfdrive/assets/fonts/opensans_semibold.ttf and /dev/null differ
diff --git a/selfdrive/assets/images/button_flag.png b/selfdrive/assets/images/button_flag.png
new file mode 100644
index 0000000000..cac4db6d4c
Binary files /dev/null and b/selfdrive/assets/images/button_flag.png differ
diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg
new file mode 100644
index 0000000000..2e86012809
--- /dev/null
+++ b/selfdrive/assets/img_couch.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg
new file mode 100644
index 0000000000..0eaec3b3cd
--- /dev/null
+++ b/selfdrive/assets/img_experimental.svg
@@ -0,0 +1,10 @@
+
diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg
new file mode 100644
index 0000000000..dc87105ac5
--- /dev/null
+++ b/selfdrive/assets/img_experimental_grey.svg
@@ -0,0 +1,4 @@
+
diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg
new file mode 100644
index 0000000000..ae4f18fde2
--- /dev/null
+++ b/selfdrive/assets/img_experimental_white.svg
@@ -0,0 +1,4 @@
+
diff --git a/selfdrive/assets/navigation/direction_turn_left_inactive.png b/selfdrive/assets/navigation/direction_turn_left_inactive.png
new file mode 100644
index 0000000000..2946984acd
Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_left_inactive.png differ
diff --git a/selfdrive/assets/navigation/direction_turn_right_inactive.png b/selfdrive/assets/navigation/direction_turn_right_inactive.png
new file mode 100644
index 0000000000..7d327766af
Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_right_inactive.png differ
diff --git a/selfdrive/assets/navigation/direction_turn_straight_inactive.png b/selfdrive/assets/navigation/direction_turn_straight_inactive.png
new file mode 100644
index 0000000000..4c567966ee
Binary files /dev/null and b/selfdrive/assets/navigation/direction_turn_straight_inactive.png differ
diff --git a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
new file mode 100644
index 0000000000..0175e672c6
--- /dev/null
+++ b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/selfdrive/assets/offroad/icon_openpilot_mirrored.png b/selfdrive/assets/offroad/icon_openpilot_mirrored.png
deleted file mode 100644
index 23a7d5a552..0000000000
Binary files a/selfdrive/assets/offroad/icon_openpilot_mirrored.png and /dev/null differ
diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav
index 420e9fabee..1ae77051eb 100644
Binary files a/selfdrive/assets/sounds/prompt.wav and b/selfdrive/assets/sounds/prompt.wav differ
diff --git a/selfdrive/assets/training/step0.png b/selfdrive/assets/training/step0.png
index 8ea90610d1..b942703b5d 100644
Binary files a/selfdrive/assets/training/step0.png and b/selfdrive/assets/training/step0.png differ
diff --git a/selfdrive/assets/training/step1.png b/selfdrive/assets/training/step1.png
index 22a629beaa..e2c9f9f60e 100644
Binary files a/selfdrive/assets/training/step1.png and b/selfdrive/assets/training/step1.png differ
diff --git a/selfdrive/assets/training/step10.png b/selfdrive/assets/training/step10.png
index 96390aa324..c5ed8fd624 100644
Binary files a/selfdrive/assets/training/step10.png and b/selfdrive/assets/training/step10.png differ
diff --git a/selfdrive/assets/training/step11.png b/selfdrive/assets/training/step11.png
index 94342a4b5b..4776593922 100644
Binary files a/selfdrive/assets/training/step11.png and b/selfdrive/assets/training/step11.png differ
diff --git a/selfdrive/assets/training/step12.png b/selfdrive/assets/training/step12.png
index 8b2b01af21..497170c978 100644
Binary files a/selfdrive/assets/training/step12.png and b/selfdrive/assets/training/step12.png differ
diff --git a/selfdrive/assets/training/step13.png b/selfdrive/assets/training/step13.png
index 6593b93d79..228d7549d4 100644
Binary files a/selfdrive/assets/training/step13.png and b/selfdrive/assets/training/step13.png differ
diff --git a/selfdrive/assets/training/step14.png b/selfdrive/assets/training/step14.png
index 1cb8fb9921..7f8da0552b 100644
Binary files a/selfdrive/assets/training/step14.png and b/selfdrive/assets/training/step14.png differ
diff --git a/selfdrive/assets/training/step15.png b/selfdrive/assets/training/step15.png
index b4c0841b71..9aa861c9fa 100644
Binary files a/selfdrive/assets/training/step15.png and b/selfdrive/assets/training/step15.png differ
diff --git a/selfdrive/assets/training/step16.png b/selfdrive/assets/training/step16.png
index d8518b10b5..e0b36b0337 100644
Binary files a/selfdrive/assets/training/step16.png and b/selfdrive/assets/training/step16.png differ
diff --git a/selfdrive/assets/training/step17.png b/selfdrive/assets/training/step17.png
index a9ab912697..c6b33c237e 100644
Binary files a/selfdrive/assets/training/step17.png and b/selfdrive/assets/training/step17.png differ
diff --git a/selfdrive/assets/training/step18.png b/selfdrive/assets/training/step18.png
index a5d8833a32..bd062d4cc0 100644
Binary files a/selfdrive/assets/training/step18.png and b/selfdrive/assets/training/step18.png differ
diff --git a/selfdrive/assets/training/step2.png b/selfdrive/assets/training/step2.png
index 03a45f8de9..97c2eb0f4b 100644
Binary files a/selfdrive/assets/training/step2.png and b/selfdrive/assets/training/step2.png differ
diff --git a/selfdrive/assets/training/step3.png b/selfdrive/assets/training/step3.png
index 0712678ac9..7489722316 100644
Binary files a/selfdrive/assets/training/step3.png and b/selfdrive/assets/training/step3.png differ
diff --git a/selfdrive/assets/training/step4.png b/selfdrive/assets/training/step4.png
index 60a99310fd..8139349ff7 100644
Binary files a/selfdrive/assets/training/step4.png and b/selfdrive/assets/training/step4.png differ
diff --git a/selfdrive/assets/training/step5.png b/selfdrive/assets/training/step5.png
index 54aa049dd1..714162ae1f 100644
Binary files a/selfdrive/assets/training/step5.png and b/selfdrive/assets/training/step5.png differ
diff --git a/selfdrive/assets/training/step6.png b/selfdrive/assets/training/step6.png
index 80cbb0a558..356d76a3e8 100644
Binary files a/selfdrive/assets/training/step6.png and b/selfdrive/assets/training/step6.png differ
diff --git a/selfdrive/assets/training/step7.png b/selfdrive/assets/training/step7.png
index e5e403df22..ac09faffe8 100644
Binary files a/selfdrive/assets/training/step7.png and b/selfdrive/assets/training/step7.png differ
diff --git a/selfdrive/assets/training/step8.png b/selfdrive/assets/training/step8.png
index d5193ae333..f081ac6e45 100644
Binary files a/selfdrive/assets/training/step8.png and b/selfdrive/assets/training/step8.png differ
diff --git a/selfdrive/assets/training/step9.png b/selfdrive/assets/training/step9.png
index 10dadc2ae0..540dafe787 100644
Binary files a/selfdrive/assets/training/step9.png and b/selfdrive/assets/training/step9.png differ
diff --git a/selfdrive/assets/training_wide/step0.png b/selfdrive/assets/training_wide/step0.png
index cff6d4f88b..3c2c5c72a0 100644
Binary files a/selfdrive/assets/training_wide/step0.png and b/selfdrive/assets/training_wide/step0.png differ
diff --git a/selfdrive/assets/training_wide/step1.png b/selfdrive/assets/training_wide/step1.png
index e81e2ce801..0857893118 100644
Binary files a/selfdrive/assets/training_wide/step1.png and b/selfdrive/assets/training_wide/step1.png differ
diff --git a/selfdrive/assets/training_wide/step10.png b/selfdrive/assets/training_wide/step10.png
index c595f602a1..2941316d17 100644
Binary files a/selfdrive/assets/training_wide/step10.png and b/selfdrive/assets/training_wide/step10.png differ
diff --git a/selfdrive/assets/training_wide/step11.png b/selfdrive/assets/training_wide/step11.png
index 26680b9021..7a7c72e3df 100644
Binary files a/selfdrive/assets/training_wide/step11.png and b/selfdrive/assets/training_wide/step11.png differ
diff --git a/selfdrive/assets/training_wide/step12.png b/selfdrive/assets/training_wide/step12.png
index c93ea6b251..0d6f64eb84 100644
Binary files a/selfdrive/assets/training_wide/step12.png and b/selfdrive/assets/training_wide/step12.png differ
diff --git a/selfdrive/assets/training_wide/step13.png b/selfdrive/assets/training_wide/step13.png
index 103b6db974..565e02fa3f 100644
Binary files a/selfdrive/assets/training_wide/step13.png and b/selfdrive/assets/training_wide/step13.png differ
diff --git a/selfdrive/assets/training_wide/step14.png b/selfdrive/assets/training_wide/step14.png
index cf4f35b724..225231cbaa 100644
Binary files a/selfdrive/assets/training_wide/step14.png and b/selfdrive/assets/training_wide/step14.png differ
diff --git a/selfdrive/assets/training_wide/step15.png b/selfdrive/assets/training_wide/step15.png
index 29999f0ada..929c759b26 100644
Binary files a/selfdrive/assets/training_wide/step15.png and b/selfdrive/assets/training_wide/step15.png differ
diff --git a/selfdrive/assets/training_wide/step16.png b/selfdrive/assets/training_wide/step16.png
index 601ff4eb03..161af863aa 100644
Binary files a/selfdrive/assets/training_wide/step16.png and b/selfdrive/assets/training_wide/step16.png differ
diff --git a/selfdrive/assets/training_wide/step17.png b/selfdrive/assets/training_wide/step17.png
index d110451af2..1b0cdb6fbc 100644
Binary files a/selfdrive/assets/training_wide/step17.png and b/selfdrive/assets/training_wide/step17.png differ
diff --git a/selfdrive/assets/training_wide/step18.png b/selfdrive/assets/training_wide/step18.png
index c1ce4ec1e3..0e3b64bab5 100644
Binary files a/selfdrive/assets/training_wide/step18.png and b/selfdrive/assets/training_wide/step18.png differ
diff --git a/selfdrive/assets/training_wide/step2.png b/selfdrive/assets/training_wide/step2.png
index ac03677ef5..55814b8ef9 100644
Binary files a/selfdrive/assets/training_wide/step2.png and b/selfdrive/assets/training_wide/step2.png differ
diff --git a/selfdrive/assets/training_wide/step3.png b/selfdrive/assets/training_wide/step3.png
index 70649b82b9..831095b0ae 100644
Binary files a/selfdrive/assets/training_wide/step3.png and b/selfdrive/assets/training_wide/step3.png differ
diff --git a/selfdrive/assets/training_wide/step4.png b/selfdrive/assets/training_wide/step4.png
index 3f393ca797..5433034939 100644
Binary files a/selfdrive/assets/training_wide/step4.png and b/selfdrive/assets/training_wide/step4.png differ
diff --git a/selfdrive/assets/training_wide/step5.png b/selfdrive/assets/training_wide/step5.png
index a26ecbf1eb..7191b63a0c 100644
Binary files a/selfdrive/assets/training_wide/step5.png and b/selfdrive/assets/training_wide/step5.png differ
diff --git a/selfdrive/assets/training_wide/step6.png b/selfdrive/assets/training_wide/step6.png
index bb1b116022..8eafd4a198 100644
Binary files a/selfdrive/assets/training_wide/step6.png and b/selfdrive/assets/training_wide/step6.png differ
diff --git a/selfdrive/assets/training_wide/step7.png b/selfdrive/assets/training_wide/step7.png
index 8a3d930811..502f5f1b2e 100644
Binary files a/selfdrive/assets/training_wide/step7.png and b/selfdrive/assets/training_wide/step7.png differ
diff --git a/selfdrive/assets/training_wide/step8.png b/selfdrive/assets/training_wide/step8.png
index c559d6a6c1..c4e8668332 100644
Binary files a/selfdrive/assets/training_wide/step8.png and b/selfdrive/assets/training_wide/step8.png differ
diff --git a/selfdrive/assets/training_wide/step9.png b/selfdrive/assets/training_wide/step9.png
index 19b2d483d7..84eae3a066 100644
Binary files a/selfdrive/assets/training_wide/step9.png and b/selfdrive/assets/training_wide/step9.png differ
diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py
index 7250754d54..5b351ca0f5 100755
--- a/selfdrive/athena/athenad.py
+++ b/selfdrive/athena/athenad.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import base64
+import bz2
import hashlib
import io
import json
@@ -30,13 +31,13 @@ from common.api import Api
from common.basedir import PERSIST
from common.file_helpers import CallbackReader
from common.params import Params
-from common.realtime import sec_since_boot
-from selfdrive.hardware import HARDWARE, PC, TICI
+from common.realtime import sec_since_boot, set_core_affinity
+from system.hardware import HARDWARE, PC, AGNOS
from selfdrive.loggerd.config import ROOT
from selfdrive.loggerd.xattr_cache import getxattr, setxattr
from selfdrive.statsd import STATS_DIR
-from selfdrive.swaglog import SWAGLOG_DIR, cloudlog
-from selfdrive.version import get_commit, get_origin, get_short_branch, get_version
+from system.swaglog import SWAGLOG_DIR, cloudlog
+from system.version import get_commit, get_origin, get_short_branch, get_version
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai')
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
@@ -64,6 +65,13 @@ UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', '
cur_upload_items: Dict[int, Any] = {}
+
+def strip_bz2_extension(fn):
+ if fn.endswith('.bz2'):
+ return fn[:-4]
+ return fn
+
+
class AbortTransferException(Exception):
pass
@@ -163,8 +171,6 @@ def upload_handler(end_event: threading.Event) -> None:
sm = messaging.SubMaster(['deviceState'])
tid = threading.get_ident()
- cellular_unmetered = Params().get_bool("CellularUnmetered")
-
while not end_event.is_set():
cur_upload_items[tid] = None
@@ -181,46 +187,45 @@ def upload_handler(end_event: threading.Event) -> None:
cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True)
continue
- # Check if uploading over cell is allowed
+ # Check if uploading over metered connection is allowed
sm.update(0)
- cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
- if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
+ metered = sm['deviceState'].networkMetered
+ network_type = sm['deviceState'].networkType.raw
+ if metered and (not cur_upload_items[tid].allow_cellular):
retry_upload(tid, end_event, False)
continue
try:
def cb(sz, cur):
- # Abort transfer if connection changed to cell after starting upload
+ # Abort transfer if connection changed to metered after starting upload
sm.update(0)
- cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
- if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
+ metered = sm['deviceState'].networkMetered
+ if metered and (not cur_upload_items[tid].allow_cellular):
raise AbortTransferException
cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1)
-
- network_type = sm['deviceState'].networkType.raw
fn = cur_upload_items[tid].path
try:
sz = os.path.getsize(fn)
except OSError:
sz = -1
- cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type)
+ cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=cur_upload_items[tid].retry_count)
response = _do_upload(cur_upload_items[tid], cb)
- if response.status_code not in (200, 201, 403, 412):
- cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type)
+ if response.status_code not in (200, 201, 401, 403, 412):
+ cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
retry_upload(tid, end_event)
else:
- cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type)
+ cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
UploadQueueCache.cache(upload_queue)
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError):
- cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type)
+ cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type, metered=metered)
retry_upload(tid, end_event)
except AbortTransferException:
- cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type)
+ cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type, metered=metered)
retry_upload(tid, end_event, False)
except queue.Empty:
@@ -230,14 +235,29 @@ def upload_handler(end_event: threading.Event) -> None:
def _do_upload(upload_item, callback=None):
- with open(upload_item.path, "rb") as f:
- size = os.fstat(f.fileno()).st_size
+ path = upload_item.path
+ compress = False
+
+ # If file does not exist, but does exist without the .bz2 extension we will compress on the fly
+ if not os.path.exists(path) and os.path.exists(strip_bz2_extension(path)):
+ path = strip_bz2_extension(path)
+ compress = True
+
+ with open(path, "rb") as f:
+ if compress:
+ cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path)
+ data = bz2.compress(f.read())
+ size = len(data)
+ data = io.BytesIO(data)
+ else:
+ size = os.fstat(f.fileno()).st_size
+ data = f
if callback:
- f = CallbackReader(f, callback, size)
+ data = CallbackReader(data, callback, size)
return requests.put(upload_item.url,
- data=f,
+ data=data,
headers={**upload_item.headers, 'Content-Length': str(size)},
timeout=30)
@@ -258,12 +278,12 @@ def getMessage(service=None, timeout=1000):
@dispatcher.add_method
-def getVersion():
+def getVersion() -> Dict[str, str]:
return {
"version": get_version(),
- "remote": get_origin(),
- "branch": get_short_branch(),
- "commit": get_commit(),
+ "remote": get_origin(''),
+ "branch": get_short_branch(''),
+ "commit": get_commit(default=''),
}
@@ -338,11 +358,17 @@ def uploadFilesToUrls(files_data):
if len(fn) == 0 or fn[0] == '/' or '..' in fn or 'url' not in file:
failed.append(fn)
continue
+
path = os.path.join(ROOT, fn)
- if not os.path.exists(path):
+ if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)):
failed.append(fn)
continue
+ # Skip item if already in queue
+ url = file['url'].split('?')[0]
+ if any(url == item['url'].split('?')[0] for item in listUploadQueue()):
+ continue
+
item = UploadItem(
path=path,
url=file['url'],
@@ -392,8 +418,8 @@ def primeActivated(activated):
@dispatcher.add_method
def setBandwithLimit(upload_speed_kbps, download_speed_kbps):
- if not TICI:
- return {"success": 0, "error": "only supported on comma three"}
+ if not AGNOS:
+ return {"success": 0, "error": "only supported on AGNOS"}
try:
HARDWARE.set_bandwidth_limit(upload_speed_kbps, download_speed_kbps)
@@ -418,7 +444,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port):
ssock, csock = socket.socketpair()
local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
local_sock.connect(('127.0.0.1', local_port))
- local_sock.setblocking(0)
+ local_sock.setblocking(False)
proxy_end_event = threading.Event()
threads = [
@@ -459,6 +485,12 @@ def getNetworkType():
return HARDWARE.get_network_type()
+@dispatcher.add_method
+def getNetworkMetered():
+ network_type = HARDWARE.get_network_type()
+ return HARDWARE.get_network_metered(network_type)
+
+
@dispatcher.add_method
def getNetworks():
return HARDWARE.get_networks()
@@ -466,7 +498,7 @@ def getNetworks():
@dispatcher.add_method
def takeSnapshot():
- from selfdrive.camerad.snapshot.snapshot import jpeg_write, snapshot
+ from system.camerad.snapshot.snapshot import jpeg_write, snapshot
ret = snapshot()
if ret is not None:
def b64jpeg(x):
@@ -677,6 +709,11 @@ def backoff(retries):
def main():
+ try:
+ set_core_affinity([0, 1, 2, 3])
+ except Exception:
+ cloudlog.exception("failed to set core affinity")
+
params = Params()
dongle_id = params.get("DongleId", encoding='utf-8')
UploadQueueCache.initialize(upload_queue)
@@ -693,7 +730,6 @@ def main():
enable_multithread=True,
timeout=30.0)
cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri)
- params.delete("PrimeRedirected")
conn_retries = 0
cur_upload_items.clear()
@@ -703,23 +739,14 @@ def main():
break
except (ConnectionError, TimeoutError, WebSocketException):
conn_retries += 1
- params.delete("PrimeRedirected")
- params.delete("LastAthenaPingTime")
+ params.remove("LastAthenaPingTime")
except socket.timeout:
- try:
- r = requests.get("http://api.commadotai.com/v1/me", allow_redirects=False,
- headers={"User-Agent": f"openpilot-{get_version()}"}, timeout=15.0)
- if r.status_code == 302 and r.headers['Location'].startswith("http://u.web2go.com"):
- params.put_bool("PrimeRedirected", True)
- except Exception:
- cloudlog.exception("athenad.socket_timeout.exception")
- params.delete("LastAthenaPingTime")
+ params.remove("LastAthenaPingTime")
except Exception:
cloudlog.exception("athenad.main.exception")
conn_retries += 1
- params.delete("PrimeRedirected")
- params.delete("LastAthenaPingTime")
+ params.remove("LastAthenaPingTime")
time.sleep(backoff(conn_retries))
diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py
index 58ad58310f..59ca2430ce 100755
--- a/selfdrive/athena/manage_athenad.py
+++ b/selfdrive/athena/manage_athenad.py
@@ -5,8 +5,8 @@ from multiprocessing import Process
from common.params import Params
from selfdrive.manager.process import launcher
-from selfdrive.swaglog import cloudlog
-from selfdrive.version import get_version, is_dirty
+from system.swaglog import cloudlog
+from system.version import get_version, is_dirty
ATHENA_MGR_PID_PARAM = "AthenadPid"
@@ -27,7 +27,7 @@ def main():
except Exception:
cloudlog.exception("manage_athenad.exception")
finally:
- params.delete(ATHENA_MGR_PID_PARAM)
+ params.remove(ATHENA_MGR_PID_PARAM)
if __name__ == '__main__':
diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py
index 10f5d46f77..32bc92059c 100755
--- a/selfdrive/athena/registration.py
+++ b/selfdrive/athena/registration.py
@@ -3,6 +3,7 @@ import time
import json
import jwt
from pathlib import Path
+from typing import Optional
from datetime import datetime, timedelta
from common.api import api_get
@@ -10,8 +11,8 @@ from common.params import Params
from common.spinner import Spinner
from common.basedir import PERSIST
from selfdrive.controls.lib.alertmanager import set_offroad_alert
-from selfdrive.hardware import HARDWARE, PC
-from selfdrive.swaglog import cloudlog
+from system.hardware import HARDWARE, PC
+from system.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
@@ -22,13 +23,13 @@ def is_registered_device() -> bool:
return dongle not in (None, UNREGISTERED_DONGLE_ID)
-def register(show_spinner=False) -> str:
+def register(show_spinner=False) -> Optional[str]:
params = Params()
params.put("SubscriberInfo", HARDWARE.get_subscriber_info())
IMEI = params.get("IMEI", encoding='utf8')
HardwareSerial = params.get("HardwareSerial", encoding='utf8')
- dongle_id = params.get("DongleId", encoding='utf8')
+ dongle_id: Optional[str] = params.get("DongleId", encoding='utf8')
needs_registration = None in (IMEI, HardwareSerial, dongle_id)
pubkey = Path(PERSIST+"/comma/id_rsa.pub")
@@ -48,7 +49,8 @@ def register(show_spinner=False) -> str:
# Block until we get the imei
serial = HARDWARE.get_serial()
start_time = time.monotonic()
- imei1, imei2 = None, None
+ imei1: Optional[str] = None
+ imei2: Optional[str] = None
while imei1 is None and imei2 is None:
try:
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py
index bb5fcd1063..a43527c260 100644
--- a/selfdrive/athena/tests/helpers.py
+++ b/selfdrive/athena/tests/helpers.py
@@ -53,8 +53,8 @@ class MockParams():
default_params = {
"DongleId": b"0000000000000000",
"GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501
+ "GsmMetered": True,
"AthenadUploadQueue": '[]',
- "CellularUnmetered": False,
}
params = default_params.copy()
@@ -116,7 +116,7 @@ def with_http_server(func):
with Timeout(2, 'HTTP Server seeding failed'):
while True:
try:
- requests.put(f'http://{host}:{port}/qlog.bz2', data='')
+ requests.put(f'http://{host}:{port}/qlog.bz2', data='', timeout=10)
break
except requests.exceptions.ConnectionError:
time.sleep(0.1)
diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py
index 1742ed4347..5e86a2e821 100755
--- a/selfdrive/athena/tests/test_athenad.py
+++ b/selfdrive/athena/tests/test_athenad.py
@@ -9,6 +9,7 @@ import threading
import queue
import unittest
from datetime import datetime, timedelta
+from typing import Optional
from multiprocessing import Process
from pathlib import Path
@@ -16,7 +17,7 @@ from unittest import mock
from websocket import ABNF
from websocket._exceptions import WebSocketConnectionClosedException
-from selfdrive import swaglog
+from system import swaglog
from selfdrive.athena import athenad
from selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher
from selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server
@@ -46,12 +47,26 @@ class TestAthenadMethods(unittest.TestCase):
else:
os.unlink(p)
- def wait_for_upload(self):
+
+ # *** test helpers ***
+
+ @staticmethod
+ def _wait_for_upload():
now = time.time()
while time.time() - now < 5:
if athenad.upload_queue.qsize() == 0:
break
+ @staticmethod
+ def _create_file(file: str, parent: Optional[str] = None) -> str:
+ fn = os.path.join(athenad.ROOT if parent is None else parent, file)
+ os.makedirs(os.path.dirname(fn), exist_ok=True)
+ Path(fn).touch()
+ return fn
+
+
+ # *** test cases ***
+
def test_echo(self):
assert dispatcher["echo"]("bob") == "bob"
@@ -81,12 +96,11 @@ class TestAthenadMethods(unittest.TestCase):
def test_listDataDirectory(self):
route = '2021-03-29--13-32-47'
segments = [0, 1, 2, 3, 11]
- filenames = ['qlog.bz2', 'qcamera.ts', 'rlog.bz2', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc']
+
+ filenames = ['qlog', 'qcamera.ts', 'rlog', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc']
files = [f'{route}--{s}/{f}' for s in segments for f in filenames]
for file in files:
- fn = os.path.join(athenad.ROOT, file)
- os.makedirs(os.path.dirname(fn), exist_ok=True)
- Path(fn).touch()
+ self._create_file(file)
resp = dispatcher["listDataDirectory"]()
self.assertTrue(resp, 'list empty!')
@@ -119,10 +133,15 @@ class TestAthenadMethods(unittest.TestCase):
self.assertTrue(resp, 'list empty!')
self.assertCountEqual(resp, expected)
+ def test_strip_bz2_extension(self):
+ fn = self._create_file('qlog.bz2')
+ if fn.endswith('.bz2'):
+ self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4])
+
+
@with_http_server
def test_do_upload(self, host):
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='')
with self.assertRaises(requests.exceptions.ConnectionError):
@@ -134,11 +153,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_uploadFileToUrl(self, host):
- not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {})
- self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']})
-
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {})
self.assertEqual(resp['enqueued'], 1)
@@ -147,10 +162,26 @@ class TestAthenadMethods(unittest.TestCase):
self.assertIsNotNone(resp['items'][0].get('id'))
self.assertEqual(athenad.upload_queue.qsize(), 1)
+ @with_http_server
+ def test_uploadFileToUrl_duplicate(self, host):
+ self._create_file('qlog.bz2')
+
+ url1 = f"{host}/qlog.bz2?sig=sig1"
+ dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {})
+
+ # Upload same file again, but with different signature
+ url2 = f"{host}/qlog.bz2?sig=sig2"
+ resp = dispatcher["uploadFileToUrl"]("qlog.bz2", url2, {})
+ self.assertEqual(resp, {'enqueued': 0, 'items': []})
+
+ @with_http_server
+ def test_uploadFileToUrl_does_not_exist(self, host):
+ not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {})
+ self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']})
+
@with_http_server
def test_upload_handler(self, host):
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@@ -159,7 +190,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item)
try:
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
# TODO: verify that upload actually succeeded
@@ -172,8 +203,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_retry(self, host, mock_put):
for status, retry in ((500, True), (412, False)):
mock_put.return_value.status_code = status
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@@ -182,7 +212,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item)
try:
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0)
@@ -194,8 +224,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_timeout(self):
"""When an upload times out or fails to connect it should be placed back in the queue"""
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT)
@@ -205,14 +234,14 @@ class TestAthenadMethods(unittest.TestCase):
try:
athenad.upload_queue.put_nowait(item_no_retry)
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
# Check that upload with retry count exceeded is not put back
self.assertEqual(athenad.upload_queue.qsize(), 0)
athenad.upload_queue.put_nowait(item)
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
# Check that upload item was put back in the queue with incremented retry count
@@ -233,7 +262,7 @@ class TestAthenadMethods(unittest.TestCase):
thread = threading.Thread(target=athenad.upload_handler, args=(end_event,))
thread.start()
try:
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0)
@@ -246,8 +275,7 @@ class TestAthenadMethods(unittest.TestCase):
ts = int(t_future.strftime("%s")) * 1000
# Item that would time out if actually uploaded
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True)
@@ -256,7 +284,7 @@ class TestAthenadMethods(unittest.TestCase):
thread.start()
try:
athenad.upload_queue.put_nowait(item)
- self.wait_for_upload()
+ self._wait_for_upload()
time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0)
@@ -269,8 +297,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server
def test_listUploadQueueCurrent(self, host):
- fn = os.path.join(athenad.ROOT, 'qlog.bz2')
- Path(fn).touch()
+ fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event()
@@ -279,7 +306,7 @@ class TestAthenadMethods(unittest.TestCase):
try:
athenad.upload_queue.put_nowait(item)
- self.wait_for_upload()
+ self._wait_for_upload()
items = dispatcher["listUploadQueue"]()
self.assertEqual(len(items), 1)
@@ -382,9 +409,9 @@ class TestAthenadMethods(unittest.TestCase):
def test_get_logs_to_send_sorted(self):
fl = list()
for i in range(10):
- fn = os.path.join(swaglog.SWAGLOG_DIR, f'swaglog.{i:010}')
- Path(fn).touch()
- fl.append(os.path.basename(fn))
+ file = f'swaglog.{i:010}'
+ self._create_file(file, athenad.SWAGLOG_DIR)
+ fl.append(file)
# ensure the list is all logs except most recent
sl = athenad.get_logs_to_send_sorted()
diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript
index 922107509a..d99e67a9f0 100644
--- a/selfdrive/boardd/SConscript
+++ b/selfdrive/boardd/SConscript
@@ -1,9 +1,9 @@
Import('env', 'envCython', 'common', 'cereal', 'messaging')
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']
-env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=libs)
+env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc'])
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
if GetOption('test'):
- env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs)
+ env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)
diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc
index e8ace77a1d..1f1249194d 100644
--- a/selfdrive/boardd/boardd.cc
+++ b/selfdrive/boardd/boardd.cc
@@ -19,24 +19,20 @@
#include
#include
-#include
-
#include "cereal/gen/cpp/car.capnp.h"
#include "cereal/messaging/messaging.h"
-#include "selfdrive/common/params.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/timing.h"
-#include "selfdrive/common/util.h"
-#include "selfdrive/hardware/hw.h"
-
-#include "selfdrive/boardd/pigeon.h"
+#include "common/params.h"
+#include "common/swaglog.h"
+#include "common/timing.h"
+#include "common/util.h"
+#include "system/hardware/hw.h"
// -- Multi-panda conventions --
// Ordering:
// - The internal panda will always be the first panda
// - Consecutive pandas will be sorted based on panda type, and then serial number
// Connecting:
-// - If a panda connection is dropped, boardd wil reconnect to all pandas
+// - If a panda connection is dropped, boardd will reconnect to all pandas
// - If a panda is added, we will only reconnect when we are offroad
// CAN buses:
// - Each panda will have it's block of 4 buses. E.g.: the second panda will use
@@ -46,7 +42,7 @@
// Safety:
// - SafetyConfig is a list, which is mapped to the connected pandas
// - If there are more pandas connected than there are SafetyConfigs,
-// the excess pandas will remain in "silent" ot "noOutput" mode
+// the excess pandas will remain in "silent" or "noOutput" mode
// Ignition:
// - If any of the ignition sources in any panda is high, ignition is high
@@ -58,7 +54,6 @@
using namespace std::chrono_literals;
std::atomic ignition(false);
-std::atomic pigeon_active(false);
ExitHandler do_exit;
@@ -70,7 +65,7 @@ static std::string get_time_str(const struct tm &time) {
bool check_all_connected(const std::vector &pandas) {
for (const auto& panda : pandas) {
- if (!panda->connected) {
+ if (!panda->connected()) {
do_exit = true;
return false;
}
@@ -115,11 +110,15 @@ bool safety_setter_thread(std::vector pandas) {
return false;
}
- pandas[0]->set_safety_model(cereal::CarParams::SafetyModel::ELM327);
+ // set to ELM327 for fingerprinting
+ for (int i = 0; i < pandas.size(); i++) {
+ const uint16_t safety_param = (i > 0) ? 1U : 0U;
+ pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param);
+ }
Params p = Params();
- // switch to SILENT when CarVin param is read
+ // wait for VIN to be read
while (true) {
if (do_exit || !check_all_connected(pandas) || !ignition) {
return false;
@@ -135,15 +134,16 @@ bool safety_setter_thread(std::vector pandas) {
util::sleep_for(20);
}
- pandas[0]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1);
+ // set to ELM327 for ECU knockouts
+ for (Panda *panda : pandas) {
+ panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U);
+ }
std::string params;
LOGW("waiting for params to set safety model");
while (true) {
- for (const auto& panda : pandas) {
- if (do_exit || !panda->connected || !ignition) {
- return false;
- }
+ if (do_exit || !check_all_connected(pandas) || !ignition) {
+ return false;
}
if (p.getBool("ControlsReady")) {
@@ -158,10 +158,10 @@ bool safety_setter_thread(std::vector pandas) {
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(params.data(), params.size()));
cereal::CarParams::Reader car_params = cmsg.getRoot();
cereal::CarParams::SafetyModel safety_model;
- int safety_param;
+ uint16_t safety_param;
auto safety_configs = car_params.getSafetyConfigs();
- uint16_t unsafe_mode = car_params.getUnsafeMode();
+ uint16_t alternative_experience = car_params.getAlternativeExperience();
for (uint32_t i = 0; i < pandas.size(); i++) {
auto panda = pandas[i];
@@ -171,18 +171,18 @@ bool safety_setter_thread(std::vector pandas) {
} else {
// If no safety mode is specified, default to silent
safety_model = cereal::CarParams::SafetyModel::SILENT;
- safety_param = 0;
+ safety_param = 0U;
}
- LOGW("panda %d: setting safety model: %d, param: %d, unsafe mode: %d", i, (int)safety_model, safety_param, unsafe_mode);
- panda->set_unsafe_mode(unsafe_mode);
+ LOGW("panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience);
+ panda->set_alternative_experience(alternative_experience);
panda->set_safety_model(safety_model, safety_param);
}
return true;
}
-Panda *usb_connect(std::string serial="", uint32_t index=0) {
+Panda *connect(std::string serial="", uint32_t index=0) {
std::unique_ptr panda;
try {
panda = std::make_unique(serial, (index * PANDA_BUS_CNT));
@@ -190,15 +190,11 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) {
return nullptr;
}
+ // common panda config
if (getenv("BOARDD_LOOPBACK")) {
panda->set_loopback(true);
}
-
- // power on charging, only the first time. Panda can also change mode and it causes a brief disconneciton
-#ifndef __x86_64__
- static std::once_flag connected_once;
- std::call_once(connected_once, &Panda::set_usb_power_mode, panda, cereal::PeripheralState::UsbPowerMode::CDP);
-#endif
+ //panda->enable_deepsleep();
sync_time(panda.get(), SyncTimeDir::FROM_PANDA);
return panda.release();
@@ -229,7 +225,9 @@ void can_send_thread(std::vector pandas, bool fake_send) {
//Dont send if older than 1 second
if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) {
for (const auto& panda : pandas) {
+ LOGT("sending sendcan to panda: %s", (panda->hw_serial).c_str());
panda->can_send(event.getSendcan());
+ LOGT("sendcan sent to panda: %s", (panda->hw_serial).c_str());
}
}
}
@@ -296,13 +294,19 @@ void send_empty_panda_state(PubMaster *pm) {
std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool spoofing_started) {
bool ignition_local = false;
+ const uint32_t pandas_cnt = pandas.size();
// build msg
MessageBuilder msg;
auto evt = msg.initEvent();
- auto pss = evt.initPandaStates(pandas.size());
+ auto pss = evt.initPandaStates(pandas_cnt);
std::vector pandaStates;
+ pandaStates.reserve(pandas_cnt);
+
+ std::vector> pandaCanStates;
+ pandaCanStates.reserve(pandas_cnt);
+
for (const auto& panda : pandas){
auto health_opt = panda->get_state();
if (!health_opt) {
@@ -311,6 +315,16 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
health_t health = *health_opt;
+ std::array can_health{};
+ for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) {
+ auto can_health_opt = panda->get_can_state(i);
+ if (!can_health_opt) {
+ return std::nullopt;
+ }
+ can_health[i] = *can_health_opt;
+ }
+ pandaCanStates.push_back(can_health);
+
if (spoofing_started) {
health.ignition_line_pkt = 1;
}
@@ -320,7 +334,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
pandaStates.push_back(health);
}
- for (uint32_t i = 0; i < pandas.size(); i++) {
+ for (uint32_t i = 0; i < pandas_cnt; i++) {
auto panda = pandas[i];
const auto &health = pandaStates[i];
@@ -330,7 +344,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
}
#ifndef __x86_64__
- bool power_save_desired = !ignition_local && !pigeon_active;
+ bool power_save_desired = !ignition_local;
if (health.power_save_enabled_pkt != power_save_desired) {
panda->set_power_saving(power_save_desired);
}
@@ -341,20 +355,20 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
}
#endif
- if (!panda->comms_healthy) {
+ if (!panda->comms_healthy()) {
evt.setValid(false);
}
auto ps = pss[i];
ps.setUptime(health.uptime_pkt);
- ps.setBlockedCnt(health.blocked_msg_cnt_pkt);
+ ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt);
+ ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt);
ps.setIgnitionLine(health.ignition_line_pkt);
ps.setIgnitionCan(health.ignition_can_pkt);
ps.setControlsAllowed(health.controls_allowed_pkt);
ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt);
- ps.setCanRxErrs(health.can_rx_errs_pkt);
- ps.setCanSendErrs(health.can_send_errs_pkt);
- ps.setCanFwdErrs(health.can_fwd_errs_pkt);
+ ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
+ ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
ps.setGmlanSendErrs(health.gmlan_send_errs_pkt);
ps.setPandaType(panda->hw_type);
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt));
@@ -362,8 +376,38 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
ps.setFaultStatus(cereal::PandaState::FaultStatus(health.fault_status_pkt));
ps.setPowerSaveEnabled((bool)(health.power_save_enabled_pkt));
ps.setHeartbeatLost((bool)(health.heartbeat_lost_pkt));
- ps.setUnsafeMode(health.unsafe_mode_pkt);
+ ps.setAlternativeExperience(health.alternative_experience_pkt);
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
+ ps.setInterruptLoad(health.interrupt_load);
+ ps.setFanPower(health.fan_power);
+ ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid));
+
+ std::array cs = {ps.initCanState0(), ps.initCanState1(), ps.initCanState2()};
+
+ for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) {
+ const auto &can_health = pandaCanStates[i][j];
+ cs[j].setBusOff((bool)can_health.bus_off);
+ cs[j].setBusOffCnt(can_health.bus_off_cnt);
+ cs[j].setErrorWarning((bool)can_health.error_warning);
+ cs[j].setErrorPassive((bool)can_health.error_passive);
+ cs[j].setLastError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_error));
+ cs[j].setLastStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_stored_error));
+ cs[j].setLastDataError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_error));
+ cs[j].setLastDataStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_stored_error));
+ cs[j].setReceiveErrorCnt(can_health.receive_error_cnt);
+ cs[j].setTransmitErrorCnt(can_health.transmit_error_cnt);
+ cs[j].setTotalErrorCnt(can_health.total_error_cnt);
+ cs[j].setTotalTxLostCnt(can_health.total_tx_lost_cnt);
+ cs[j].setTotalRxLostCnt(can_health.total_rx_lost_cnt);
+ cs[j].setTotalTxCnt(can_health.total_tx_cnt);
+ cs[j].setTotalRxCnt(can_health.total_rx_cnt);
+ cs[j].setTotalFwdCnt(can_health.total_fwd_cnt);
+ cs[j].setCanSpeed(can_health.can_speed);
+ cs[j].setCanDataSpeed(can_health.can_data_speed);
+ cs[j].setCanfdEnabled(can_health.canfd_enabled);
+ cs[j].setBrsEnabled(can_health.brs_enabled);
+ cs[j].setCanfdNonIso(can_health.canfd_non_iso);
+ }
// Convert faults bitset to capnp list
std::bitset fault_bits(health.faults_pkt);
@@ -371,7 +415,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
size_t j = 0;
for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION);
- f <= size_t(cereal::PandaState::FaultType::INTERRUPT_RATE_TICK); f++) {
+ f <= size_t(cereal::PandaState::FaultType::INTERRUPT_RATE_EXTI); f++) {
if (fault_bits.test(f)) {
faults.set(j, cereal::PandaState::FaultType(f));
j++;
@@ -384,36 +428,23 @@ std::optional send_panda_states(PubMaster *pm, const std::vector
}
void send_peripheral_state(PubMaster *pm, Panda *panda) {
- auto pandaState_opt = panda->get_state();
- if (!pandaState_opt) {
- return;
- }
-
- health_t pandaState = *pandaState_opt;
-
// build msg
MessageBuilder msg;
auto evt = msg.initEvent();
- evt.setValid(panda->comms_healthy);
+ evt.setValid(panda->comms_healthy());
auto ps = evt.initPeripheralState();
ps.setPandaType(panda->hw_type);
- if (Hardware::TICI()) {
- double read_time = millis_since_boot();
- ps.setVoltage(std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()));
- ps.setCurrent(std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()));
- read_time = millis_since_boot() - read_time;
- if (read_time > 50) {
- LOGW("reading hwmon took %lfms", read_time);
- }
- } else {
- ps.setVoltage(pandaState.voltage_pkt);
- ps.setCurrent(pandaState.current_pkt);
+ double read_time = millis_since_boot();
+ ps.setVoltage(Hardware::get_voltage());
+ ps.setCurrent(Hardware::get_current());
+ read_time = millis_since_boot() - read_time;
+ if (read_time > 50) {
+ LOGW("reading hwmon took %lfms", read_time);
}
uint16_t fan_speed_rpm = panda->get_fan_speed();
- ps.setUsbPowerMode(cereal::PeripheralState::UsbPowerMode(pandaState.usb_power_mode_pkt));
ps.setFanSpeedRpm(fan_speed_rpm);
pm->send("peripheralState", msg);
@@ -480,7 +511,7 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin
}
-void peripheral_control_thread(Panda *panda) {
+void peripheral_control_thread(Panda *panda, bool no_fan_control) {
util::set_thread_name("boardd_peripheral_control");
SubMaster sm({"deviceState", "driverCameraState"});
@@ -489,33 +520,15 @@ void peripheral_control_thread(Panda *panda) {
uint16_t prev_fan_speed = 999;
uint16_t ir_pwr = 0;
uint16_t prev_ir_pwr = 999;
- bool prev_charging_disabled = false;
unsigned int cnt = 0;
FirstOrderFilter integ_lines_filter(0, 30.0, 0.05);
- while (!do_exit && panda->connected) {
+ while (!do_exit && panda->connected()) {
cnt++;
sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update?
- if (!Hardware::PC() && sm.updated("deviceState")) {
- // Charging mode
- bool charging_disabled = sm["deviceState"].getDeviceState().getChargingDisabled();
- if (charging_disabled != prev_charging_disabled) {
- if (charging_disabled) {
- panda->set_usb_power_mode(cereal::PeripheralState::UsbPowerMode::CLIENT);
- LOGW("TURN OFF CHARGING!\n");
- } else {
- panda->set_usb_power_mode(cereal::PeripheralState::UsbPowerMode::CDP);
- LOGW("TURN ON CHARGING!\n");
- }
- prev_charging_disabled = charging_disabled;
- }
- }
-
- // Other pandas don't have fan/IR to control
- if (panda->hw_type != cereal::PandaState::PandaType::UNO && panda->hw_type != cereal::PandaState::PandaType::DOS) continue;
- if (sm.updated("deviceState")) {
+ if (sm.updated("deviceState") && !no_fan_control) {
// Fan speed
uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired();
if (fan_speed != prev_fan_speed || cnt % 100 == 0) {
@@ -523,14 +536,13 @@ void peripheral_control_thread(Panda *panda) {
prev_fan_speed = fan_speed;
}
}
+
if (sm.updated("driverCameraState")) {
auto event = sm["driverCameraState"];
int cur_integ_lines = event.getDriverCameraState().getIntegLines();
float cur_gain = event.getDriverCameraState().getGain();
- if (Hardware::TICI()) {
- cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain);
- }
+ cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain);
last_front_frame_t = event.getLogMonoTime();
if (cur_integ_lines <= CUTOFF_IL) {
@@ -541,6 +553,7 @@ void peripheral_control_thread(Panda *panda) {
ir_pwr = 100.0 * (MIN_IR_POWER + ((cur_integ_lines - CUTOFF_IL) * (MAX_IR_POWER - MIN_IR_POWER) / (SATURATE_IL - CUTOFF_IL)));
}
}
+
// Disable ir_pwr on front frame timeout
uint64_t cur_t = nanos_since_boot();
if (cur_t - last_front_frame_t > 1e9) {
@@ -559,66 +572,25 @@ void peripheral_control_thread(Panda *panda) {
}
}
-static void pigeon_publish_raw(PubMaster &pm, const std::string &dat) {
- // create message
- MessageBuilder msg;
- msg.initEvent().setUbloxRaw(capnp::Data::Reader((uint8_t*)dat.data(), dat.length()));
- pm.send("ubloxRaw", msg);
-}
-
-void pigeon_thread(Panda *panda) {
- util::set_thread_name("boardd_pigeon");
-
- PubMaster pm({"ubloxRaw"});
- bool ignition_last = false;
-
- std::unique_ptr pigeon(Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda));
-
- while (!do_exit && panda->connected) {
- bool need_reset = false;
- bool ignition_local = ignition;
- std::string recv = pigeon->receive();
-
- // Check based on null bytes
- if (ignition_local && recv.length() > 0 && recv[0] == (char)0x00) {
- need_reset = true;
- LOGW("received invalid ublox message while onroad, resetting panda GPS");
- }
+void boardd_main_thread(std::vector serials) {
+ PubMaster pm({"pandaStates", "peripheralState"});
+ LOGW("attempting to connect");
- if (recv.length() > 0) {
- pigeon_publish_raw(pm, recv);
- }
+ if (serials.size() == 0) {
+ // connect to all
+ serials = Panda::list();
- // init pigeon on rising ignition edge
- // since it was turned off in low power mode
- if((ignition_local && !ignition_last) || need_reset) {
- pigeon_active = true;
- pigeon->init();
- } else if (!ignition_local && ignition_last) {
- // power off on falling edge of ignition
- LOGD("powering off pigeon\n");
- pigeon->stop();
- pigeon->set_power(false);
- pigeon_active = false;
+ // exit if no pandas are connected
+ if (serials.size() == 0) {
+ LOGW("no pandas found, exiting");
+ return;
}
-
- ignition_last = ignition_local;
-
- // 10ms - 100 Hz
- util::sleep_for(10);
}
-}
-void boardd_main_thread(std::vector serials) {
- PubMaster pm({"pandaStates", "peripheralState"});
- LOGW("attempting to connect");
-
- if (serials.size() == 0) serials = Panda::list();
-
// connect to all provided serials
std::vector pandas;
for (int i = 0; i < serials.size() && !do_exit; /**/) {
- Panda *p = usb_connect(serials[i], i);
+ Panda *p = connect(serials[i], i);
if (!p) {
// send empty pandaState & peripheralState and try again
send_empty_panda_state(&pm);
@@ -636,11 +608,8 @@ void boardd_main_thread(std::vector serials) {
Panda *peripheral_panda = pandas[0];
std::vector threads;
- Params().put("LastPeripheralPandaType", std::to_string((int) peripheral_panda->get_hw_type()));
-
threads.emplace_back(panda_state_thread, &pm, pandas, getenv("STARTED") != nullptr);
- threads.emplace_back(peripheral_control_thread, peripheral_panda);
- threads.emplace_back(pigeon_thread, peripheral_panda);
+ threads.emplace_back(peripheral_control_thread, peripheral_panda, getenv("NO_FAN_CONTROL") != nullptr);
threads.emplace_back(can_send_thread, pandas, getenv("FAKESEND") != nullptr);
threads.emplace_back(can_recv_thread, pandas);
diff --git a/selfdrive/boardd/main.cc b/selfdrive/boardd/main.cc
index d802e42f86..cb17a584bd 100644
--- a/selfdrive/boardd/main.cc
+++ b/selfdrive/boardd/main.cc
@@ -1,9 +1,9 @@
#include
#include "selfdrive/boardd/boardd.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/util.h"
-#include "selfdrive/hardware/hw.h"
+#include "common/swaglog.h"
+#include "common/util.h"
+#include "system/hardware/hw.h"
int main(int argc, char *argv[]) {
LOGW("starting boardd");
@@ -12,7 +12,7 @@ int main(int argc, char *argv[]) {
int err;
err = util::set_realtime_priority(54);
assert(err == 0);
- err = util::set_core_affinity({Hardware::TICI() ? 4 : 3});
+ err = util::set_core_affinity({4});
assert(err == 0);
}
diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc
index 5e621b12cd..3e447c830e 100644
--- a/selfdrive/boardd/panda.cc
+++ b/selfdrive/boardd/panda.cc
@@ -4,273 +4,67 @@
#include
#include
-#include
#include "cereal/messaging/messaging.h"
-#include "panda/board/dlc_to_len.h"
-#include "selfdrive/common/gpio.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/util.h"
-
-static int init_usb_ctx(libusb_context **context) {
- assert(context != nullptr);
-
- int err = libusb_init(context);
- if (err != 0) {
- LOGE("libusb initialization error");
- return err;
- }
-
-#if LIBUSB_API_VERSION >= 0x01000106
- libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
-#else
- libusb_set_debug(*context, 3);
-#endif
-
- return err;
-}
-
+#include "common/swaglog.h"
+#include "common/util.h"
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
- // init libusb
- ssize_t num_devices;
- libusb_device **dev_list = NULL;
- int err = init_usb_ctx(&ctx);
- if (err != 0) { goto fail; }
-
- // connect by serial
- num_devices = libusb_get_device_list(ctx, &dev_list);
- if (num_devices < 0) { goto fail; }
- for (size_t i = 0; i < num_devices; ++i) {
- libusb_device_descriptor desc;
- libusb_get_device_descriptor(dev_list[i], &desc);
- if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
- int ret = libusb_open(dev_list[i], &dev_handle);
- if (dev_handle == NULL || ret < 0) { goto fail; }
-
- unsigned char desc_serial[26] = { 0 };
- ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
- if (ret < 0) { goto fail; }
-
- usb_serial = std::string((char *)desc_serial, ret).c_str();
- if (serial.empty() || serial == usb_serial) {
- break;
- }
- libusb_close(dev_handle);
- dev_handle = NULL;
- }
- }
- if (dev_handle == NULL) goto fail;
- libusb_free_device_list(dev_list, 1);
- dev_list = nullptr;
-
- if (libusb_kernel_driver_active(dev_handle, 0) == 1) {
- libusb_detach_kernel_driver(dev_handle, 0);
+ // TODO: support SPI here one day...
+ if (serial.find("spi") != std::string::npos) {
+ handle = std::make_unique(serial);
+ } else {
+ handle = std::make_unique(serial);
}
- err = libusb_set_configuration(dev_handle, 1);
- if (err != 0) { goto fail; }
-
- err = libusb_claim_interface(dev_handle, 0);
- if (err != 0) { goto fail; }
-
hw_type = get_hw_type();
assert((hw_type != cereal::PandaState::PandaType::WHITE_PANDA) &&
(hw_type != cereal::PandaState::PandaType::GREY_PANDA));
has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) ||
- (hw_type == cereal::PandaState::PandaType::DOS);
+ (hw_type == cereal::PandaState::PandaType::DOS) ||
+ (hw_type == cereal::PandaState::PandaType::TRES);
return;
-
-fail:
- if (dev_list != NULL) {
- libusb_free_device_list(dev_list, 1);
- }
- cleanup();
- throw std::runtime_error("Error connecting to panda");
}
-Panda::~Panda() {
- std::lock_guard lk(usb_lock);
- cleanup();
- connected = false;
+bool Panda::connected() {
+ return handle->connected;
}
-void Panda::cleanup() {
- if (dev_handle) {
- libusb_release_interface(dev_handle, 0);
- libusb_close(dev_handle);
- }
-
- if (ctx) {
- libusb_exit(ctx);
- }
+bool Panda::comms_healthy() {
+ return handle->comms_healthy;
}
std::vector Panda::list() {
- // init libusb
- ssize_t num_devices;
- libusb_context *context = NULL;
- libusb_device **dev_list = NULL;
- std::vector serials;
-
- int err = init_usb_ctx(&context);
- if (err != 0) { return serials; }
-
- num_devices = libusb_get_device_list(context, &dev_list);
- if (num_devices < 0) {
- LOGE("libusb can't get device list");
- goto finish;
- }
- for (size_t i = 0; i < num_devices; ++i) {
- libusb_device *device = dev_list[i];
- libusb_device_descriptor desc;
- libusb_get_device_descriptor(device, &desc);
- if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
- libusb_device_handle *handle = NULL;
- int ret = libusb_open(device, &handle);
- if (ret < 0) { goto finish; }
-
- unsigned char desc_serial[26] = { 0 };
- ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
- libusb_close(handle);
- if (ret < 0) { goto finish; }
-
- serials.push_back(std::string((char *)desc_serial, ret).c_str());
- }
- }
-
-finish:
- if (dev_list != NULL) {
- libusb_free_device_list(dev_list, 1);
- }
- if (context) {
- libusb_exit(context);
- }
- return serials;
-}
-
-void Panda::handle_usb_issue(int err, const char func[]) {
- LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func);
- if (err == LIBUSB_ERROR_NO_DEVICE) {
- LOGE("lost connection");
- connected = false;
- }
- // TODO: check other errors, is simply retrying okay?
-}
-
-int Panda::usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) {
- int err;
- const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
-
- if (!connected) {
- return LIBUSB_ERROR_NO_DEVICE;
- }
-
- std::lock_guard lk(usb_lock);
- do {
- err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout);
- if (err < 0) handle_usb_issue(err, __func__);
- } while (err < 0 && connected);
-
- return err;
-}
-
-int Panda::usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) {
- int err;
- const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
-
- if (!connected) {
- return LIBUSB_ERROR_NO_DEVICE;
- }
-
- std::lock_guard lk(usb_lock);
- do {
- err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout);
- if (err < 0) handle_usb_issue(err, __func__);
- } while (err < 0 && connected);
-
- return err;
-}
-
-int Panda::usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
- int err;
- int transferred = 0;
-
- if (!connected) {
- return 0;
- }
-
- std::lock_guard lk(usb_lock);
- do {
- // Try sending can messages. If the receive buffer on the panda is full it will NAK
- // and libusb will try again. After 5ms, it will time out. We will drop the messages.
- err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
-
- if (err == LIBUSB_ERROR_TIMEOUT) {
- LOGW("Transmit buffer full");
- break;
- } else if (err != 0 || length != transferred) {
- handle_usb_issue(err, __func__);
- }
- } while(err != 0 && connected);
-
- return transferred;
+ return PandaUsbHandle::list();
}
-int Panda::usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
- int err;
- int transferred = 0;
-
- if (!connected) {
- return 0;
- }
-
- std::lock_guard lk(usb_lock);
-
- do {
- err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
-
- if (err == LIBUSB_ERROR_TIMEOUT) {
- break; // timeout is okay to exit, recv still happened
- } else if (err == LIBUSB_ERROR_OVERFLOW) {
- comms_healthy = false;
- LOGE_100("overflow got 0x%x", transferred);
- } else if (err != 0) {
- handle_usb_issue(err, __func__);
- }
-
- } while(err != 0 && connected);
-
- return transferred;
+void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) {
+ handle->control_write(0xdc, (uint16_t)safety_model, safety_param);
}
-void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, int safety_param) {
- usb_write(0xdc, (uint16_t)safety_model, safety_param);
-}
-
-void Panda::set_unsafe_mode(uint16_t unsafe_mode) {
- usb_write(0xdf, unsafe_mode, 0);
+void Panda::set_alternative_experience(uint16_t alternative_experience) {
+ handle->control_write(0xdf, alternative_experience, 0);
}
cereal::PandaState::PandaType Panda::get_hw_type() {
unsigned char hw_query[1] = {0};
- usb_read(0xc1, 0, 0, hw_query, 1);
+ handle->control_read(0xc1, 0, 0, hw_query, 1);
return (cereal::PandaState::PandaType)(hw_query[0]);
}
void Panda::set_rtc(struct tm sys_time) {
// tm struct has year defined as years since 1900
- usb_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0);
- usb_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0);
- usb_write(0xa3, (uint16_t)sys_time.tm_mday, 0);
- // usb_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0);
- usb_write(0xa5, (uint16_t)sys_time.tm_hour, 0);
- usb_write(0xa6, (uint16_t)sys_time.tm_min, 0);
- usb_write(0xa7, (uint16_t)sys_time.tm_sec, 0);
+ handle->control_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0);
+ handle->control_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0);
+ handle->control_write(0xa3, (uint16_t)sys_time.tm_mday, 0);
+ // handle->control_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0);
+ handle->control_write(0xa5, (uint16_t)sys_time.tm_hour, 0);
+ handle->control_write(0xa6, (uint16_t)sys_time.tm_min, 0);
+ handle->control_write(0xa7, (uint16_t)sys_time.tm_sec, 0);
}
struct tm Panda::get_rtc() {
@@ -284,7 +78,7 @@ struct tm Panda::get_rtc() {
uint8_t second;
} rtc_time = {0};
- usb_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time));
+ handle->control_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time));
struct tm new_time = { 0 };
new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900
@@ -298,60 +92,70 @@ struct tm Panda::get_rtc() {
}
void Panda::set_fan_speed(uint16_t fan_speed) {
- usb_write(0xb1, fan_speed, 0);
+ handle->control_write(0xb1, fan_speed, 0);
}
uint16_t Panda::get_fan_speed() {
uint16_t fan_speed_rpm = 0;
- usb_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm));
+ handle->control_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm));
return fan_speed_rpm;
}
void Panda::set_ir_pwr(uint16_t ir_pwr) {
- usb_write(0xb0, ir_pwr, 0);
+ handle->control_write(0xb0, ir_pwr, 0);
}
std::optional Panda::get_state() {
health_t health {0};
- int err = usb_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health));
+ int err = handle->control_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health));
return err >= 0 ? std::make_optional(health) : std::nullopt;
}
+std::optional Panda::get_can_state(uint16_t can_number) {
+ can_health_t can_health {0};
+ int err = handle->control_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health));
+ return err >= 0 ? std::make_optional(can_health) : std::nullopt;
+}
+
void Panda::set_loopback(bool loopback) {
- usb_write(0xe5, loopback, 0);
+ handle->control_write(0xe5, loopback, 0);
}
std::optional> Panda::get_firmware_version() {
std::vector fw_sig_buf(128);
- int read_1 = usb_read(0xd3, 0, 0, &fw_sig_buf[0], 64);
- int read_2 = usb_read(0xd4, 0, 0, &fw_sig_buf[64], 64);
+ int read_1 = handle->control_read(0xd3, 0, 0, &fw_sig_buf[0], 64);
+ int read_2 = handle->control_read(0xd4, 0, 0, &fw_sig_buf[64], 64);
return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt;
}
std::optional Panda::get_serial() {
char serial_buf[17] = {'\0'};
- int err = usb_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16);
+ int err = handle->control_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16);
return err >= 0 ? std::make_optional(serial_buf) : std::nullopt;
}
void Panda::set_power_saving(bool power_saving) {
- usb_write(0xe7, power_saving, 0);
+ handle->control_write(0xe7, power_saving, 0);
}
-void Panda::set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode) {
- usb_write(0xe6, (uint16_t)power_mode, 0);
+void Panda::enable_deepsleep() {
+ handle->control_write(0xfb, 0, 0);
}
void Panda::send_heartbeat(bool engaged) {
- usb_write(0xf3, engaged, 0);
+ handle->control_write(0xf3, engaged, 0);
}
void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) {
- usb_write(0xde, bus, (speed * 10));
+ handle->control_write(0xde, bus, (speed * 10));
}
void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) {
- usb_write(0xf9, bus, (speed * 10));
+ handle->control_write(0xf9, bus, (speed * 10));
+}
+
+void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) {
+ handle->control_write(0xfc, bus, non_iso);
}
static uint8_t len_to_dlc(uint8_t len) {
@@ -365,22 +169,15 @@ static uint8_t len_to_dlc(uint8_t len) {
}
}
-static void write_packet(uint8_t *dest, int *write_pos, const uint8_t *src, size_t size) {
- for (int i = 0, &pos = *write_pos; i < size; ++i, ++pos) {
- // Insert counter every 64 bytes (first byte of 64 bytes USB packet)
- if (pos % USBPACKET_MAX_SIZE == 0) {
- dest[pos] = pos / USBPACKET_MAX_SIZE;
- pos++;
- }
- dest[pos] = src[i];
- }
-}
-
void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list,
std::function write_func) {
int32_t pos = 0;
uint8_t send_buf[2 * USB_TX_SOFT_LIMIT];
+ uint32_t magic = CAN_TRANSACTION_MAGIC;
+ memcpy(&send_buf[0], &magic, sizeof(uint32_t));
+ pos += sizeof(uint32_t);
+
for (auto cmsg : can_data_list) {
// check if the message is intended for this panda
uint8_t bus = cmsg.getSrc();
@@ -389,7 +186,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data
}
auto can_data = cmsg.getDat();
uint8_t data_len_code = len_to_dlc(can_data.size());
- assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8));
+ assert(can_data.size() <= 64);
assert(can_data.size() == dlc_to_len[data_len_code]);
can_header header;
@@ -398,28 +195,31 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data
header.data_len_code = data_len_code;
header.bus = bus - bus_offset;
- write_packet(send_buf, &pos, (uint8_t *)&header, sizeof(can_header));
- write_packet(send_buf, &pos, (uint8_t *)can_data.begin(), can_data.size());
+ memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header));
+ pos += sizeof(can_header);
+ memcpy(&send_buf[pos], (uint8_t *)can_data.begin(), can_data.size());
+ pos += can_data.size();
+
if (pos >= USB_TX_SOFT_LIMIT) {
write_func(send_buf, pos);
- pos = 0;
+ pos = sizeof(uint32_t);
}
}
// send remaining packets
- if (pos > 0) write_func(send_buf, pos);
+ if (pos > sizeof(uint32_t)) write_func(send_buf, pos);
}
void Panda::can_send(capnp::List::Reader can_data_list) {
pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) {
- usb_bulk_write(3, data, size, 5);
+ handle->bulk_write(3, data, size, 5);
});
}
bool Panda::can_receive(std::vector& out_vec) {
uint8_t data[RECV_SIZE];
- int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE);
- if (!comms_healthy) {
+ int recv = handle->bulk_read(0x81, (uint8_t*)data, RECV_SIZE);
+ if (!comms_healthy()) {
return false;
}
if (recv == RECV_SIZE) {
@@ -430,33 +230,38 @@ bool Panda::can_receive(std::vector& out_vec) {
}
bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) {
- recv_buf.clear();
- for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) {
- if (data[i] != i / USBPACKET_MAX_SIZE) {
- LOGE("CAN: MALFORMED USB RECV PACKET");
- comms_healthy = false;
- return false;
- }
- int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i));
- recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]);
+ if (size < sizeof(uint32_t)) {
+ return true;
+ }
+
+ uint32_t magic;
+ memcpy(&magic, &data[0], sizeof(uint32_t));
+ if (magic != CAN_TRANSACTION_MAGIC) {
+ LOGE("CAN recv: buffer didn't start with magic");
+ handle->comms_healthy = false;
+ return false;
}
- int pos = 0;
- while (pos < recv_buf.size()) {
+ int pos = sizeof(uint32_t);
+ while (pos < size) {
can_header header;
- memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE);
+ memcpy(&header, &data[pos], sizeof(can_header));
can_frame &canData = out_vec.emplace_back();
canData.busTime = 0;
canData.address = header.addr;
canData.src = header.bus + bus_offset;
- if (header.rejected) { canData.src += CANPACKET_REJECTED; }
- if (header.returned) { canData.src += CANPACKET_RETURNED; }
+ if (header.rejected) {
+ canData.src += CAN_REJECTED_BUS_OFFSET;
+ }
+ if (header.returned) {
+ canData.src += CAN_RETURNED_BUS_OFFSET;
+ }
const uint8_t data_len = dlc_to_len[header.data_len_code];
- canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len);
+ canData.dat.assign((char *)&data[pos + sizeof(can_header)], data_len);
- pos += CANPACKET_HEAD_SIZE + data_len;
+ pos += sizeof(can_header) + data_len;
}
return true;
}
diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h
index dbd866adf4..75fec57a3e 100644
--- a/selfdrive/boardd/panda.h
+++ b/selfdrive/boardd/panda.h
@@ -1,29 +1,26 @@
#pragma once
-#include
#include
#include
#include
#include
-#include
+#include
#include
#include
-#include
-
#include "cereal/gen/cpp/car.capnp.h"
#include "cereal/gen/cpp/log.capnp.h"
#include "panda/board/health.h"
+#include "panda/board/can_definitions.h"
+#include "selfdrive/boardd/panda_comms.h"
-#define TIMEOUT 0
-#define PANDA_BUS_CNT 4
-#define RECV_SIZE (0x4000U)
#define USB_TX_SOFT_LIMIT (0x100U)
#define USBPACKET_MAX_SIZE (0x40)
-#define CANPACKET_HEAD_SIZE 5U
-#define CANPACKET_MAX_SIZE 72U
-#define CANPACKET_REJECTED (0xC0U)
-#define CANPACKET_RETURNED (0x80U)
+
+#define RECV_SIZE (0x4000U)
+
+#define CAN_REJECTED_BUS_OFFSET 0xC0U
+#define CAN_RETURNED_BUS_OFFSET 0x80U
struct __attribute__((packed)) can_header {
uint8_t reserved : 1;
@@ -36,59 +33,51 @@ struct __attribute__((packed)) can_header {
};
struct can_frame {
- long address;
- std::string dat;
- long busTime;
- long src;
+ long address;
+ std::string dat;
+ long busTime;
+ long src;
};
+
class Panda {
- private:
- libusb_context *ctx = NULL;
- libusb_device_handle *dev_handle = NULL;
- std::mutex usb_lock;
- std::vector recv_buf;
- void handle_usb_issue(int err, const char func[]);
- void cleanup();
+private:
+ std::unique_ptr handle;
- public:
+public:
Panda(std::string serial="", uint32_t bus_offset=0);
- ~Panda();
- std::string usb_serial;
- std::atomic connected = true;
- std::atomic comms_healthy = true;
+ std::string hw_serial;
cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN;
bool has_rtc = false;
const uint32_t bus_offset;
+ bool connected();
+ bool comms_healthy();
+
// Static functions
static std::vector list();
- // HW communication
- int usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout=TIMEOUT);
- int usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout=TIMEOUT);
- int usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
- int usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
-
// Panda functionality
cereal::PandaState::PandaType get_hw_type();
- void set_safety_model(cereal::CarParams::SafetyModel safety_model, int safety_param=0);
- void set_unsafe_mode(uint16_t unsafe_mode);
+ void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U);
+ void set_alternative_experience(uint16_t alternative_experience);
void set_rtc(struct tm sys_time);
struct tm get_rtc();
void set_fan_speed(uint16_t fan_speed);
uint16_t get_fan_speed();
void set_ir_pwr(uint16_t ir_pwr);
std::optional get_state();
+ std::optional get_can_state(uint16_t can_number);
void set_loopback(bool loopback);
std::optional> get_firmware_version();
std::optional get_serial();
void set_power_saving(bool power_saving);
- void set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode);
+ void enable_deepsleep();
void send_heartbeat(bool engaged);
void set_can_speed_kbps(uint16_t bus, uint16_t speed);
void set_data_speed_kbps(uint16_t bus, uint16_t speed);
+ void set_canfd_non_iso(uint16_t bus, bool non_iso);
void can_send(capnp::List::Reader can_data_list);
bool can_receive(std::vector& out_vec);
diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc
new file mode 100644
index 0000000000..e73cb69318
--- /dev/null
+++ b/selfdrive/boardd/panda_comms.cc
@@ -0,0 +1,232 @@
+#include "selfdrive/boardd/panda.h"
+
+#include
+#include
+
+#include "common/swaglog.h"
+
+static int init_usb_ctx(libusb_context **context) {
+ assert(context != nullptr);
+
+ int err = libusb_init(context);
+ if (err != 0) {
+ LOGE("libusb initialization error");
+ return err;
+ }
+
+#if LIBUSB_API_VERSION >= 0x01000106
+ libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
+#else
+ libusb_set_debug(*context, 3);
+#endif
+
+ return err;
+}
+
+PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) {
+ // init libusb
+ ssize_t num_devices;
+ libusb_device **dev_list = NULL;
+ int err = init_usb_ctx(&ctx);
+ if (err != 0) { goto fail; }
+
+ // connect by serial
+ num_devices = libusb_get_device_list(ctx, &dev_list);
+ if (num_devices < 0) { goto fail; }
+ for (size_t i = 0; i < num_devices; ++i) {
+ libusb_device_descriptor desc;
+ libusb_get_device_descriptor(dev_list[i], &desc);
+ if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
+ int ret = libusb_open(dev_list[i], &dev_handle);
+ if (dev_handle == NULL || ret < 0) { goto fail; }
+
+ unsigned char desc_serial[26] = { 0 };
+ ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
+ if (ret < 0) { goto fail; }
+
+ auto hw_serial = std::string((char *)desc_serial, ret);
+ if (serial.empty() || serial == hw_serial) {
+ break;
+ }
+ libusb_close(dev_handle);
+ dev_handle = NULL;
+ }
+ }
+ if (dev_handle == NULL) goto fail;
+ libusb_free_device_list(dev_list, 1);
+ dev_list = nullptr;
+
+ if (libusb_kernel_driver_active(dev_handle, 0) == 1) {
+ libusb_detach_kernel_driver(dev_handle, 0);
+ }
+
+ err = libusb_set_configuration(dev_handle, 1);
+ if (err != 0) { goto fail; }
+
+ err = libusb_claim_interface(dev_handle, 0);
+ if (err != 0) { goto fail; }
+
+ return;
+
+fail:
+ if (dev_list != NULL) {
+ libusb_free_device_list(dev_list, 1);
+ }
+ cleanup();
+ throw std::runtime_error("Error connecting to panda");
+}
+
+PandaUsbHandle::~PandaUsbHandle() {
+ std::lock_guard lk(hw_lock);
+ cleanup();
+ connected = false;
+}
+
+void PandaUsbHandle::cleanup() {
+ if (dev_handle) {
+ libusb_release_interface(dev_handle, 0);
+ libusb_close(dev_handle);
+ }
+
+ if (ctx) {
+ libusb_exit(ctx);
+ }
+}
+
+std::vector PandaUsbHandle::list() {
+ // init libusb
+ ssize_t num_devices;
+ libusb_context *context = NULL;
+ libusb_device **dev_list = NULL;
+ std::vector serials;
+
+ int err = init_usb_ctx(&context);
+ if (err != 0) { return serials; }
+
+ num_devices = libusb_get_device_list(context, &dev_list);
+ if (num_devices < 0) {
+ LOGE("libusb can't get device list");
+ goto finish;
+ }
+ for (size_t i = 0; i < num_devices; ++i) {
+ libusb_device *device = dev_list[i];
+ libusb_device_descriptor desc;
+ libusb_get_device_descriptor(device, &desc);
+ if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
+ libusb_device_handle *handle = NULL;
+ int ret = libusb_open(device, &handle);
+ if (ret < 0) { goto finish; }
+
+ unsigned char desc_serial[26] = { 0 };
+ ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
+ libusb_close(handle);
+ if (ret < 0) { goto finish; }
+
+ serials.push_back(std::string((char *)desc_serial, ret).c_str());
+ }
+ }
+
+finish:
+ if (dev_list != NULL) {
+ libusb_free_device_list(dev_list, 1);
+ }
+ if (context) {
+ libusb_exit(context);
+ }
+ return serials;
+}
+
+void PandaUsbHandle::handle_usb_issue(int err, const char func[]) {
+ LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func);
+ if (err == LIBUSB_ERROR_NO_DEVICE) {
+ LOGE("lost connection");
+ connected = false;
+ }
+ // TODO: check other errors, is simply retrying okay?
+}
+
+int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) {
+ int err;
+ const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
+
+ if (!connected) {
+ return LIBUSB_ERROR_NO_DEVICE;
+ }
+
+ std::lock_guard lk(hw_lock);
+ do {
+ err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout);
+ if (err < 0) handle_usb_issue(err, __func__);
+ } while (err < 0 && connected);
+
+ return err;
+}
+
+int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) {
+ int err;
+ const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
+
+ if (!connected) {
+ return LIBUSB_ERROR_NO_DEVICE;
+ }
+
+ std::lock_guard lk(hw_lock);
+ do {
+ err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout);
+ if (err < 0) handle_usb_issue(err, __func__);
+ } while (err < 0 && connected);
+
+ return err;
+}
+
+int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
+ int err;
+ int transferred = 0;
+
+ if (!connected) {
+ return 0;
+ }
+
+ std::lock_guard lk(hw_lock);
+ do {
+ // Try sending can messages. If the receive buffer on the panda is full it will NAK
+ // and libusb will try again. After 5ms, it will time out. We will drop the messages.
+ err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
+
+ if (err == LIBUSB_ERROR_TIMEOUT) {
+ LOGW("Transmit buffer full");
+ break;
+ } else if (err != 0 || length != transferred) {
+ handle_usb_issue(err, __func__);
+ }
+ } while(err != 0 && connected);
+
+ return transferred;
+}
+
+int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
+ int err;
+ int transferred = 0;
+
+ if (!connected) {
+ return 0;
+ }
+
+ std::lock_guard lk(hw_lock);
+
+ do {
+ err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
+
+ if (err == LIBUSB_ERROR_TIMEOUT) {
+ break; // timeout is okay to exit, recv still happened
+ } else if (err == LIBUSB_ERROR_OVERFLOW) {
+ comms_healthy = false;
+ LOGE_100("overflow got 0x%x", transferred);
+ } else if (err != 0) {
+ handle_usb_issue(err, __func__);
+ }
+
+ } while(err != 0 && connected);
+
+ return transferred;
+}
diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h
new file mode 100644
index 0000000000..bd262dfa0e
--- /dev/null
+++ b/selfdrive/boardd/panda_comms.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+
+#define TIMEOUT 0
+#define SPI_BUF_SIZE 1024
+
+
+// comms base class
+class PandaCommsHandle {
+public:
+ PandaCommsHandle(std::string serial) {};
+ virtual ~PandaCommsHandle() {};
+ virtual void cleanup() = 0;
+
+ std::atomic connected = true;
+ std::atomic comms_healthy = true;
+ static std::vector list();
+
+ // HW communication
+ virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0;
+ virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0;
+ virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
+ virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
+
+protected:
+ std::recursive_mutex hw_lock;
+};
+
+class PandaUsbHandle : public PandaCommsHandle {
+public:
+ PandaUsbHandle(std::string serial);
+ ~PandaUsbHandle();
+ int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT);
+ int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT);
+ int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
+ int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
+ void cleanup();
+
+ static std::vector list();
+
+private:
+ libusb_context *ctx = NULL;
+ libusb_device_handle *dev_handle = NULL;
+ void handle_usb_issue(int err, const char func[]);
+};
+
+class PandaSpiHandle : public PandaCommsHandle {
+public:
+ PandaSpiHandle(std::string serial);
+ ~PandaSpiHandle();
+ int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT);
+ int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT);
+ int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
+ int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
+ void cleanup();
+
+ static std::vector list();
+
+private:
+ int spi_fd = -1;
+ uint8_t tx_buf[SPI_BUF_SIZE];
+ uint8_t rx_buf[SPI_BUF_SIZE];
+
+ int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack);
+ int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len);
+ int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
+ int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
+};
diff --git a/selfdrive/pandad.py b/selfdrive/boardd/pandad.py
similarity index 87%
rename from selfdrive/pandad.py
rename to selfdrive/boardd/pandad.py
index 1f9fddab7a..971756002b 100755
--- a/selfdrive/pandad.py
+++ b/selfdrive/boardd/pandad.py
@@ -4,13 +4,14 @@ import os
import usb1
import time
import subprocess
-from typing import NoReturn
+from typing import List, NoReturn
from functools import cmp_to_key
from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU
from common.basedir import BASEDIR
from common.params import Params
-from selfdrive.swaglog import cloudlog
+from system.hardware import HARDWARE
+from system.swaglog import cloudlog
def get_expected_signature(panda: Panda) -> bytes:
@@ -27,6 +28,7 @@ def flash_panda(panda_serial: str) -> Panda:
panda = Panda(panda_serial)
fw_signature = get_expected_signature(panda)
+ internal_panda = panda.is_internal() and not panda.bootstub
panda_version = "bootstub" if panda.bootstub else panda.get_version()
panda_signature = b"" if panda.bootstub else panda.get_signature()
@@ -40,7 +42,9 @@ def flash_panda(panda_serial: str) -> Panda:
if panda.bootstub:
bootstub_version = panda.get_version()
cloudlog.info(f"Flashed firmware not booting, flashing development bootloader. Bootstub version: {bootstub_version}")
- panda.recover()
+ if internal_panda:
+ HARDWARE.recover_internal_panda()
+ panda.recover(reset=(not internal_panda))
cloudlog.info("Done flashing bootloader")
if panda.bootstub:
@@ -79,7 +83,7 @@ def main() -> NoReturn:
while True:
try:
- params.delete("PandaSignatures")
+ params.remove("PandaSignatures")
# Flash all Pandas in DFU mode
for p in PandaDFU.list():
@@ -89,12 +93,16 @@ def main() -> NoReturn:
panda_serials = Panda.list()
if len(panda_serials) == 0:
+ if first_run:
+ cloudlog.info("Resetting internal panda")
+ HARDWARE.reset_internal_panda()
+ time.sleep(2) # wait to come back up
continue
cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}")
# Flash pandas
- pandas = []
+ pandas: List[Panda] = []
for serial in panda_serials:
pandas.append(flash_panda(serial))
@@ -111,7 +119,7 @@ def main() -> NoReturn:
# sort pandas to have deterministic order
pandas.sort(key=cmp_to_key(panda_sort_cmp))
- panda_serials = list(map(lambda p: p.get_usb_serial(), pandas))
+ panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # type: ignore
# log panda fw versions
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))
@@ -127,6 +135,7 @@ def main() -> NoReturn:
first_run = False
# run boardd with all connected serials as arguments
+ os.environ['MANAGER_DAEMON'] = 'boardd'
os.chdir(os.path.join(BASEDIR, "selfdrive/boardd"))
subprocess.run(["./boardd", *panda_serials], check=True)
diff --git a/selfdrive/boardd/pigeon.cc b/selfdrive/boardd/pigeon.cc
deleted file mode 100644
index 912f4b03e7..0000000000
--- a/selfdrive/boardd/pigeon.cc
+++ /dev/null
@@ -1,333 +0,0 @@
-#include "selfdrive/boardd/pigeon.h"
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include "selfdrive/common/gpio.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/util.h"
-#include "selfdrive/locationd/ublox_msg.h"
-
-// Termios on macos doesn't define all baud rate constants
-#ifndef B460800
-#define B460800 0010004
-#endif
-
-using namespace std::string_literals;
-
-extern ExitHandler do_exit;
-
-const std::string ack = "\xb5\x62\x05\x01\x02\x00";
-const std::string nack = "\xb5\x62\x05\x00\x02\x00";
-const std::string sos_save_ack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x01\x00\x00\x00";
-const std::string sos_save_nack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x00\x00\x00\x00";
-
-Pigeon * Pigeon::connect(Panda * p) {
- PandaPigeon * pigeon = new PandaPigeon();
- pigeon->connect(p);
-
- return pigeon;
-}
-
-Pigeon * Pigeon::connect(const char * tty) {
- TTYPigeon * pigeon = new TTYPigeon();
- pigeon->connect(tty);
-
- return pigeon;
-}
-
-bool Pigeon::wait_for_ack(const std::string &ack_, const std::string &nack_, int timeout_ms) {
- std::string s;
- const double start_t = millis_since_boot();
- while (!do_exit) {
- s += receive();
-
- if (s.find(ack_) != std::string::npos) {
- LOGD("Received ACK from ublox");
- return true;
- } else if (s.find(nack_) != std::string::npos) {
- LOGE("Received NACK from ublox");
- return false;
- } else if (s.size() > 0x1000 || ((millis_since_boot() - start_t) > timeout_ms)) {
- LOGE("No response from ublox");
- return false;
- }
-
- util::sleep_for(1); // Allow other threads to be scheduled
- }
- return false;
-}
-
-bool Pigeon::wait_for_ack() {
- return wait_for_ack(ack, nack);
-}
-
-bool Pigeon::send_with_ack(const std::string &cmd) {
- send(cmd);
- return wait_for_ack();
-}
-
-sos_restore_response Pigeon::wait_for_backup_restore_status(int timeout_ms) {
- std::string s;
- const double start_t = millis_since_boot();
- while (!do_exit) {
- s += receive();
-
- size_t position = s.find("\xb5\x62\x09\x14\x08\x00\x03");
- if (position != std::string::npos && s.size() >= (position + 11)) {
- return static_cast(s[position + 10]);
- } else if (s.size() > 0x1000 || ((millis_since_boot() - start_t) > timeout_ms)) {
- LOGE("No backup restore response from ublox");
- return error;
- }
-
- util::sleep_for(1); // Allow other threads to be scheduled
- }
- return error;
-}
-
-void Pigeon::init() {
- for (int i = 0; i < 10; i++) {
- if (do_exit) return;
- LOGW("panda GPS start");
-
- // power off pigeon
- set_power(false);
- util::sleep_for(100);
-
- // 9600 baud at init
- set_baud(9600);
-
- // power on pigeon
- set_power(true);
- util::sleep_for(500);
-
- // baud rate upping
- send("\x24\x50\x55\x42\x58\x2C\x34\x31\x2C\x31\x2C\x30\x30\x30\x37\x2C\x30\x30\x30\x33\x2C\x34\x36\x30\x38\x30\x30\x2C\x30\x2A\x31\x35\x0D\x0A"s);
- util::sleep_for(100);
-
- // set baud rate to 460800
- set_baud(460800);
-
- // init from ubloxd
- // To generate this data, run selfdrive/locationd/test/ubloxd.py
- if (!send_with_ack("\xB5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x00\x00\x06\x18"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x01\x00\x01\x08\x22"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x00\x01\x00\x03\x0A\x24"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x39\x08\x00\xFF\xAD\x62\xAD\x1E\x63\x00\x00\x83\x0C"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x24\x00\x00\x2A\x84"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x23\x00\x00\x29\x81"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x1E\x00\x00\x24\x72"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x39\x00\x00\x3F\xC3"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70"s)) continue;
- if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74"s)) continue;
-
- // check the backup restore status
- send("\xB5\x62\x09\x14\x00\x00\x1D\x60"s);
- sos_restore_response restore_status = wait_for_backup_restore_status();
- switch(restore_status) {
- case restored:
- LOGW("almanac backup restored");
- // clear the backup
- send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74"s);
- break;
- case no_backup:
- LOGW("no almanac backup found");
- break;
- default:
- LOGE("failed to restore almanac backup, status: %d", restore_status);
- }
-
- auto time = util::get_time();
- if (util::time_valid(time)) {
- LOGW("Sending current time to ublox");
- send(ublox::build_ubx_mga_ini_time_utc(time));
- }
-
- LOGW("panda GPS on");
- return;
- }
- LOGE("failed to initialize panda GPS");
-}
-
-void Pigeon::stop() {
- LOGW("Storing almanac in ublox flash");
-
- // Controlled GNSS stop
- send("\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74"s);
-
- // Store almanac in flash
- send("\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC"s);
-
- if (wait_for_ack(sos_save_ack, sos_save_nack)) {
- LOGW("Done storing almanac");
- } else {
- LOGE("Error storing almanac");
- }
-}
-
-void PandaPigeon::connect(Panda * p) {
- panda = p;
-}
-
-void PandaPigeon::set_baud(int baud) {
- panda->usb_write(0xe2, 1, 0);
- panda->usb_write(0xe4, 1, baud/300);
-}
-
-void PandaPigeon::send(const std::string &s) {
- int len = s.length();
- const char * dat = s.data();
-
- unsigned char a[0x20+1];
- a[0] = 1;
- for (int i=0; iusb_bulk_write(2, a, ll+1);
- }
-}
-
-std::string PandaPigeon::receive() {
- std::string r;
- r.reserve(0x1000 + 0x40);
- unsigned char dat[0x40];
- while (r.length() < 0x1000) {
- int len = panda->usb_read(0xe0, 1, 0, dat, sizeof(dat));
- if (len <= 0) break;
- r.append((char*)dat, len);
- }
-
- return r;
-}
-
-void PandaPigeon::set_power(bool power) {
- panda->usb_write(0xd9, power, 0);
-}
-
-PandaPigeon::~PandaPigeon() {
-}
-
-void handle_tty_issue(int err, const char func[]) {
- LOGE_100("tty error %d \"%s\" in %s", err, strerror(err), func);
-}
-
-void TTYPigeon::connect(const char * tty) {
- pigeon_tty_fd = HANDLE_EINTR(open(tty, O_RDWR));
- if (pigeon_tty_fd < 0) {
- handle_tty_issue(errno, __func__);
- assert(pigeon_tty_fd >= 0);
- }
- int err = tcgetattr(pigeon_tty_fd, &pigeon_tty);
- assert(err == 0);
-
- // configure tty
- pigeon_tty.c_cflag &= ~PARENB; // disable parity
- pigeon_tty.c_cflag &= ~CSTOPB; // single stop bit
- pigeon_tty.c_cflag |= CS8; // 8 bits per byte
- pigeon_tty.c_cflag &= ~CRTSCTS; // no RTS/CTS flow control
- pigeon_tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
- pigeon_tty.c_lflag &= ~ICANON; // disable canonical mode
- pigeon_tty.c_lflag &= ~ISIG; // disable interpretation of INTR, QUIT and SUSP
- pigeon_tty.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off software flow ctrl
- pigeon_tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // disable any special handling of received bytes
- pigeon_tty.c_oflag &= ~OPOST; // prevent special interpretation of output bytes
- pigeon_tty.c_oflag &= ~ONLCR; // prevent conversion of newline to carriage return/line feed
-
- // configure blocking behavior
- pigeon_tty.c_cc[VMIN] = 0; // min amount of characters returned
- pigeon_tty.c_cc[VTIME] = 0; // max blocking time in s/10 (0=inf)
-
- err = tcsetattr(pigeon_tty_fd, TCSANOW, &pigeon_tty);
- assert(err == 0);
-}
-
-void TTYPigeon::set_baud(int baud) {
- speed_t baud_const = 0;
- switch(baud) {
- case 9600:
- baud_const = B9600;
- break;
- case 460800:
- baud_const = B460800;
- break;
- default:
- assert(false);
- }
-
- // make sure everything is tx'ed before changing baud
- int err = tcdrain(pigeon_tty_fd);
- assert(err == 0);
-
- // change baud
- err = tcgetattr(pigeon_tty_fd, &pigeon_tty);
- assert(err == 0);
- err = cfsetspeed(&pigeon_tty, baud_const);
- assert(err == 0);
- err = tcsetattr(pigeon_tty_fd, TCSANOW, &pigeon_tty);
- assert(err == 0);
-
- // flush
- err = tcflush(pigeon_tty_fd, TCIOFLUSH);
- assert(err == 0);
-}
-
-void TTYPigeon::send(const std::string &s) {
- int err = HANDLE_EINTR(write(pigeon_tty_fd, s.data(), s.length()));
-
- if(err < 0) { handle_tty_issue(err, __func__); }
- err = tcdrain(pigeon_tty_fd);
- if(err < 0) { handle_tty_issue(err, __func__); }
-}
-
-std::string TTYPigeon::receive() {
- std::string r;
- r.reserve(0x1000 + 0x40);
- char dat[0x40];
- while (r.length() < 0x1000) {
- int len = read(pigeon_tty_fd, dat, sizeof(dat));
- if(len < 0) {
- handle_tty_issue(len, __func__);
- } else if (len == 0) {
- break;
- } else {
- r.append(dat, len);
- }
-
- }
- return r;
-}
-
-void TTYPigeon::set_power(bool power) {
-#ifdef QCOM2
- int err = 0;
- err += gpio_init(GPIO_UBLOX_RST_N, true);
- err += gpio_init(GPIO_UBLOX_SAFEBOOT_N, true);
- err += gpio_init(GPIO_UBLOX_PWR_EN, true);
-
- err += gpio_set(GPIO_UBLOX_RST_N, power);
- err += gpio_set(GPIO_UBLOX_SAFEBOOT_N, power);
- err += gpio_set(GPIO_UBLOX_PWR_EN, power);
- assert(err == 0);
-#endif
-}
-
-TTYPigeon::~TTYPigeon() {
- close(pigeon_tty_fd);
-}
diff --git a/selfdrive/boardd/pigeon.h b/selfdrive/boardd/pigeon.h
deleted file mode 100644
index c9ea4739dc..0000000000
--- a/selfdrive/boardd/pigeon.h
+++ /dev/null
@@ -1,58 +0,0 @@
-#pragma once
-
-#include
-
-#include
-#include
-
-#include "selfdrive/boardd/panda.h"
-
-enum sos_restore_response : int {
- unknown = 0,
- failed = 1,
- restored = 2,
- no_backup = 3,
- error = -1
-};
-
-class Pigeon {
- public:
- static Pigeon* connect(Panda * p);
- static Pigeon* connect(const char * tty);
- virtual ~Pigeon(){};
-
- void init();
- void stop();
- bool wait_for_ack();
- bool wait_for_ack(const std::string &ack, const std::string &nack, int timeout_ms = 1000);
- bool send_with_ack(const std::string &cmd);
- sos_restore_response wait_for_backup_restore_status(int timeout_ms = 1000);
- virtual void set_baud(int baud) = 0;
- virtual void send(const std::string &s) = 0;
- virtual std::string receive() = 0;
- virtual void set_power(bool power) = 0;
-};
-
-class PandaPigeon : public Pigeon {
- Panda * panda = NULL;
-public:
- ~PandaPigeon();
- void connect(Panda * p);
- void set_baud(int baud);
- void send(const std::string &s);
- std::string receive();
- void set_power(bool power);
-};
-
-
-class TTYPigeon : public Pigeon {
- int pigeon_tty_fd = -1;
- struct termios pigeon_tty;
-public:
- ~TTYPigeon();
- void connect(const char* tty);
- void set_baud(int baud);
- void send(const std::string &s);
- std::string receive();
- void set_power(bool power);
-};
diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc
new file mode 100644
index 0000000000..717b6ce820
--- /dev/null
+++ b/selfdrive/boardd/spi.cc
@@ -0,0 +1,296 @@
+#include
+#include
+
+#include
+#include
+#include
+
+#include "common/util.h"
+#include "common/timing.h"
+#include "common/swaglog.h"
+#include "panda/board/comms_definitions.h"
+#include "selfdrive/boardd/panda_comms.h"
+
+
+#define SPI_SYNC 0x5AU
+#define SPI_HACK 0x79U
+#define SPI_DACK 0x85U
+#define SPI_NACK 0x1FU
+#define SPI_CHECKSUM_START 0xABU
+
+struct __attribute__((packed)) spi_header {
+ uint8_t sync;
+ uint8_t endpoint;
+ uint16_t tx_len;
+ uint16_t max_rx_len;
+};
+
+const int SPI_MAX_RETRIES = 5;
+const int SPI_ACK_TIMEOUT = 50; // milliseconds
+
+
+PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
+ LOGD("opening SPI panda: %s", serial.c_str());
+
+ int err;
+ uint32_t spi_mode = SPI_MODE_0;
+ uint32_t spi_speed = 30000000;
+ uint8_t spi_bits_per_word = 8;
+
+ spi_fd = open(serial.c_str(), O_RDWR);
+ if (spi_fd < 0) {
+ LOGE("failed opening SPI device %d", err);
+ goto fail;
+ }
+
+ // SPI settings
+ err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
+ if (err < 0) {
+ LOGE("failed setting SPI mode %d", err);
+ goto fail;
+ }
+
+ err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
+ if (err < 0) {
+ LOGE("failed setting SPI speed");
+ goto fail;
+ }
+
+ err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
+ if (err < 0) {
+ LOGE("failed setting SPI bits per word");
+ goto fail;
+ }
+
+ return;
+
+fail:
+ cleanup();
+ throw std::runtime_error("Error connecting to panda");
+}
+
+PandaSpiHandle::~PandaSpiHandle() {
+ std::lock_guard lk(hw_lock);
+ cleanup();
+}
+
+void PandaSpiHandle::cleanup() {
+ if (spi_fd != -1) {
+ close(spi_fd);
+ spi_fd = -1;
+ }
+}
+
+
+
+int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) {
+ ControlPacket_t packet = {
+ .request = request,
+ .param1 = param1,
+ .param2 = param2,
+ .length = 0
+ };
+ return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), NULL, 0);
+}
+
+int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) {
+ ControlPacket_t packet = {
+ .request = request,
+ .param1 = param1,
+ .param2 = param2,
+ .length = length
+ };
+ return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), data, length);
+}
+
+int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
+ return bulk_transfer(endpoint, data, length, NULL, 0);
+}
+int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
+ return bulk_transfer(endpoint, NULL, 0, data, length);
+}
+
+int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) {
+ std::lock_guard lk(hw_lock);
+
+ const int xfer_size = 0x40 * 15;
+
+ int ret = 0;
+ uint16_t length = (tx_data != NULL) ? tx_len : rx_len;
+ for (int i = 0; i < (int)std::ceil((float)length / xfer_size); i++) {
+ int d;
+ if (tx_data != NULL) {
+ int len = std::min(xfer_size, tx_len - (xfer_size * i));
+ d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0);
+ } else {
+ uint16_t to_read = std::min(xfer_size, rx_len - ret);
+ d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), to_read);
+ }
+
+ if (d < 0) {
+ LOGE("SPI: bulk transfer failed with %d", d);
+ comms_healthy = false;
+ return -1;
+ }
+
+ ret += d;
+ if ((rx_data != NULL) && d < xfer_size) {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+std::vector PandaSpiHandle::list() {
+ // TODO: list all pandas available over SPI
+ return {};
+}
+
+void add_checksum(uint8_t *data, int data_len) {
+ data[data_len] = SPI_CHECKSUM_START;
+ for (int i=0; i < data_len; i++) {
+ data[data_len] ^= data[i];
+ }
+}
+
+bool check_checksum(uint8_t *data, int data_len) {
+ uint8_t checksum = SPI_CHECKSUM_START;
+ for (uint16_t i = 0U; i < data_len; i++) {
+ checksum ^= data[i];
+ }
+ return checksum == 0U;
+}
+
+
+int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
+ int ret;
+
+ int count = SPI_MAX_RETRIES;
+ std::lock_guard lk(hw_lock);
+ do {
+ // TODO: handle error
+ ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len);
+ count--;
+ } while (ret < 0 && connected && count > 0);
+
+ return ret;
+}
+
+int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) {
+ double start_millis = millis_since_boot();
+ while (true) {
+ int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
+ if (ret < 0) {
+ LOGE("SPI: failed to send ACK request");
+ return ret;
+ }
+
+ if (rx_buf[0] == ack) {
+ break;
+ } else if (rx_buf[0] == SPI_NACK) {
+ LOGW("SPI: got NACK");
+ return -1;
+ }
+
+ // handle timeout
+ if (millis_since_boot() - start_millis > SPI_ACK_TIMEOUT) {
+ LOGE("SPI: timed out waiting for ACK");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
+ int ret;
+ uint16_t rx_data_len;
+
+ // needs to be less, since we need to have space for the checksum
+ assert(tx_len < SPI_BUF_SIZE);
+ assert(max_rx_len < SPI_BUF_SIZE);
+
+ spi_header header = {
+ .sync = SPI_SYNC,
+ .endpoint = endpoint,
+ .tx_len = tx_len,
+ .max_rx_len = max_rx_len
+ };
+
+ spi_ioc_transfer transfer = {
+ .tx_buf = (uint64_t)tx_buf,
+ .rx_buf = (uint64_t)rx_buf
+ };
+
+ // Send header
+ memcpy(tx_buf, &header, sizeof(header));
+ add_checksum(tx_buf, sizeof(header));
+ transfer.len = sizeof(header) + 1;
+ ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
+ if (ret < 0) {
+ LOGE("SPI: failed to send header");
+ goto transfer_fail;
+ }
+
+ // Wait for (N)ACK
+ tx_buf[0] = 0x12;
+ transfer.len = 1;
+ ret = wait_for_ack(transfer, SPI_HACK);
+ if (ret < 0) {
+ goto transfer_fail;
+ }
+
+ // Send data
+ if (tx_data != NULL) {
+ memcpy(tx_buf, tx_data, tx_len);
+ }
+ add_checksum(tx_buf, tx_len);
+ transfer.len = tx_len + 1;
+ ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
+ if (ret < 0) {
+ LOGE("SPI: failed to send data");
+ goto transfer_fail;
+ }
+
+ // Wait for (N)ACK
+ tx_buf[0] = 0xab;
+ transfer.len = 1;
+ ret = wait_for_ack(transfer, SPI_DACK);
+ if (ret < 0) {
+ goto transfer_fail;
+ }
+
+ // Read data len
+ transfer.len = 2;
+ transfer.rx_buf = (uint64_t)(rx_buf + 1);
+ ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
+ if (ret < 0) {
+ LOGE("SPI: failed to read rx data len");
+ goto transfer_fail;
+ }
+ rx_data_len = *(uint16_t *)(rx_buf+1);
+ assert(rx_data_len < SPI_BUF_SIZE);
+
+ // Read data
+ transfer.len = rx_data_len + 1;
+ transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1);
+ ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
+ if (ret < 0) {
+ LOGE("SPI: failed to read rx data");
+ goto transfer_fail;
+ }
+ if (!check_checksum(rx_buf, rx_data_len + 4)) {
+ LOGE("SPI: bad checksum");
+ goto transfer_fail;
+ }
+
+ if (rx_data != NULL) {
+ memcpy(rx_data, rx_buf + 3, rx_data_len);
+ }
+
+ return rx_data_len;
+
+transfer_fail:
+ return ret;
+}
diff --git a/selfdrive/boardd/tests/boardd_old.py b/selfdrive/boardd/tests/boardd_old.py
deleted file mode 100755
index 5e740fbcd8..0000000000
--- a/selfdrive/boardd/tests/boardd_old.py
+++ /dev/null
@@ -1,245 +0,0 @@
-#!/usr/bin/env python3
-# pylint: skip-file
-
-# This file is not used by openpilot. Only boardd.cc is used.
-# The python version is slower, but has more options for development.
-
-# TODO: merge the extra functionalities of this file (like MOCK) in boardd.c and
-# delete this python version of boardd
-
-import os
-import struct
-import time
-
-import cereal.messaging as messaging
-from common.realtime import Ratekeeper
-from selfdrive.swaglog import cloudlog
-from selfdrive.boardd.boardd import can_capnp_to_can_list
-from cereal import car
-
-SafetyModel = car.CarParams.SafetyModel
-
-# USB is optional
-try:
- import usb1
- from usb1 import USBErrorIO, USBErrorOverflow # pylint: disable=no-name-in-module
-except Exception:
- pass
-
-# *** serialization functions ***
-def can_list_to_can_capnp(can_msgs, msgtype='can'):
- dat = messaging.new_message(msgtype, len(can_msgs))
- for i, can_msg in enumerate(can_msgs):
- if msgtype == 'sendcan':
- cc = dat.sendcan[i]
- else:
- cc = dat.can[i]
- cc.address = can_msg[0]
- cc.busTime = can_msg[1]
- cc.dat = bytes(can_msg[2])
- cc.src = can_msg[3]
- return dat
-
-
-# *** can driver ***
-def can_health():
- while 1:
- try:
- dat = handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd2, 0, 0, 0x16)
- break
- except (USBErrorIO, USBErrorOverflow):
- cloudlog.exception("CAN: BAD HEALTH, RETRYING")
- v, i = struct.unpack("II", dat[0:8])
- ign_line, ign_can = struct.unpack("BB", dat[20:22])
- return {"voltage": v, "current": i, "ignition_line": bool(ign_line), "ignition_can": bool(ign_can)}
-
-def __parse_can_buffer(dat):
- ret = []
- for j in range(0, len(dat), 0x10):
- ddat = dat[j:j+0x10]
- f1, f2 = struct.unpack("II", ddat[0:8])
- ret.append((f1 >> 21, f2 >> 16, ddat[8:8 + (f2 & 0xF)], (f2 >> 4) & 0xFF))
- return ret
-
-def can_send_many(arr):
- snds = []
- for addr, _, dat, alt in arr:
- if addr < 0x800: # only support 11 bit addr
- snd = struct.pack("II", ((addr << 21) | 1), len(dat) | (alt << 4)) + dat
- snd = snd.ljust(0x10, b'\x00')
- snds.append(snd)
- while 1:
- try:
- handle.bulkWrite(3, b''.join(snds))
- break
- except (USBErrorIO, USBErrorOverflow):
- cloudlog.exception("CAN: BAD SEND MANY, RETRYING")
-
-def can_recv():
- dat = b""
- while 1:
- try:
- dat = handle.bulkRead(1, 0x10*256)
- break
- except (USBErrorIO, USBErrorOverflow):
- cloudlog.exception("CAN: BAD RECV, RETRYING")
- return __parse_can_buffer(dat)
-
-def can_init():
- global handle, context
- handle = None
- cloudlog.info("attempting can init")
-
- context = usb1.USBContext()
- #context.setDebug(9)
-
- for device in context.getDeviceList(skip_on_error=True):
- if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddcc:
- handle = device.open()
- handle.claimInterface(0)
- handle.controlWrite(0x40, 0xdc, SafetyModel.allOutput, 0, b'')
-
- if handle is None:
- cloudlog.warning("CAN NOT FOUND")
- exit(-1)
-
- cloudlog.info("got handle")
- cloudlog.info("can init done")
-
-def boardd_mock_loop():
- can_init()
- handle.controlWrite(0x40, 0xdc, SafetyModel.allOutput, 0, b'')
-
- logcan = messaging.sub_sock('can')
- sendcan = messaging.pub_sock('sendcan')
-
- while 1:
- tsc = messaging.drain_sock(logcan, wait_for_one=True)
- snds = map(lambda x: can_capnp_to_can_list(x.can), tsc)
- snd = []
- for s in snds:
- snd += s
- snd = list(filter(lambda x: x[-1] <= 2, snd))
- snd_0 = len(list(filter(lambda x: x[-1] == 0, snd)))
- snd_1 = len(list(filter(lambda x: x[-1] == 1, snd)))
- snd_2 = len(list(filter(lambda x: x[-1] == 2, snd)))
- can_send_many(snd)
-
- # recv @ 100hz
- can_msgs = can_recv()
- got_0 = len(list(filter(lambda x: x[-1] == 0+0x80, can_msgs)))
- got_1 = len(list(filter(lambda x: x[-1] == 1+0x80, can_msgs)))
- got_2 = len(list(filter(lambda x: x[-1] == 2+0x80, can_msgs)))
- print("sent %3d (%3d/%3d/%3d) got %3d (%3d/%3d/%3d)" %
- (len(snd), snd_0, snd_1, snd_2, len(can_msgs), got_0, got_1, got_2))
- m = can_list_to_can_capnp(can_msgs, msgtype='sendcan')
- sendcan.send(m.to_bytes())
-
-def boardd_test_loop():
- can_init()
- cnt = 0
- while 1:
- can_send_many([[0xbb, 0, "\xaa\xaa\xaa\xaa", 0], [0xaa, 0, "\xaa\xaa\xaa\xaa"+struct.pack("!I", cnt), 1]])
- #can_send_many([[0xaa,0,"\xaa\xaa\xaa\xaa",0]])
- #can_send_many([[0xaa,0,"\xaa\xaa\xaa\xaa",1]])
- # recv @ 100hz
- can_msgs = can_recv()
- print("got %d" % (len(can_msgs)))
- time.sleep(0.01)
- cnt += 1
-
-# *** main loop ***
-def boardd_loop(rate=100):
- rk = Ratekeeper(rate)
-
- can_init()
-
- # *** publishes can and health
- logcan = messaging.pub_sock('can')
- health_sock = messaging.pub_sock('pandaStates')
-
- # *** subscribes to can send
- sendcan = messaging.sub_sock('sendcan')
-
- # drain sendcan to delete any stale messages from previous runs
- messaging.drain_sock(sendcan)
-
- while 1:
- # health packet @ 2hz
- if (rk.frame % (rate // 2)) == 0:
- health = can_health()
- msg = messaging.new_message('pandaStates', 1)
-
- # store the health to be logged
- msg.pandaState[0].voltage = health['voltage']
- msg.pandaState[0].current = health['current']
- msg.pandaState[0].ignitionLine = health['ignition_line']
- msg.pandaState[0].ignitionCan = health['ignition_can']
- msg.pandaState[0].controlsAllowed = True
-
- health_sock.send(msg.to_bytes())
-
- # recv @ 100hz
- can_msgs = can_recv()
-
- # publish to logger
- # TODO: refactor for speed
- if len(can_msgs) > 0:
- dat = can_list_to_can_capnp(can_msgs).to_bytes()
- logcan.send(dat)
-
- # send can if we have a packet
- tsc = messaging.recv_sock(sendcan)
- if tsc is not None:
- can_send_many(can_capnp_to_can_list(tsc.sendcan))
-
- rk.keep_time()
-
-# *** main loop ***
-def boardd_proxy_loop(rate=100, address="192.168.2.251"):
- rk = Ratekeeper(rate)
-
- can_init()
-
- # *** subscribes can
- logcan = messaging.sub_sock('can', addr=address)
- # *** publishes to can send
- sendcan = messaging.pub_sock('sendcan')
-
- # drain sendcan to delete any stale messages from previous runs
- messaging.drain_sock(sendcan)
-
- while 1:
- # recv @ 100hz
- can_msgs = can_recv()
- #for m in can_msgs:
- # print("R: {0} {1}".format(hex(m[0]), str(m[2]).encode("hex")))
-
- # publish to logger
- # TODO: refactor for speed
- if len(can_msgs) > 0:
- dat = can_list_to_can_capnp(can_msgs, "sendcan")
- sendcan.send(dat)
-
- # send can if we have a packet
- tsc = messaging.recv_sock(logcan)
- if tsc is not None:
- cl = can_capnp_to_can_list(tsc.can)
- #for m in cl:
- # print("S: {0} {1}".format(hex(m[0]), str(m[2]).encode("hex")))
- can_send_many(cl)
-
- rk.keep_time()
-
-def main():
- if os.getenv("MOCK") is not None:
- boardd_mock_loop()
- elif os.getenv("PROXY") is not None:
- boardd_proxy_loop()
- elif os.getenv("BOARDTEST") is not None:
- boardd_test_loop()
- else:
- boardd_loop()
-
-if __name__ == "__main__":
- main()
diff --git a/selfdrive/boardd/tests/replay_many.py b/selfdrive/boardd/tests/replay_many.py
deleted file mode 100755
index 78aef07497..0000000000
--- a/selfdrive/boardd/tests/replay_many.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import time
-import signal
-import traceback
-import usb1
-from panda import Panda, PandaDFU
-from multiprocessing import Pool
-
-jungle = "JUNGLE" in os.environ
-if jungle:
- from panda_jungle import PandaJungle # pylint: disable=import-error
-
-import cereal.messaging as messaging
-from selfdrive.boardd.boardd import can_capnp_to_can_list
-
-def initializer():
- """Ignore CTRL+C in the worker process.
- source: https://stackoverflow.com/a/44869451 """
- signal.signal(signal.SIGINT, signal.SIG_IGN)
-
-def send_thread(sender_serial):
- while True:
- try:
- if jungle:
- sender = PandaJungle(sender_serial)
- else:
- sender = Panda(sender_serial)
- sender.set_safety_mode(Panda.SAFETY_ALLOUTPUT)
-
- sender.set_can_loopback(False)
- can_sock = messaging.sub_sock('can')
-
- while True:
- tsc = messaging.recv_one(can_sock)
- snd = can_capnp_to_can_list(tsc.can)
- snd = list(filter(lambda x: x[-1] <= 2, snd))
-
- try:
- sender.can_send_many(snd)
- except usb1.USBErrorTimeout:
- pass
-
- # Drain panda message buffer
- sender.can_recv()
- except Exception:
- traceback.print_exc()
- time.sleep(1)
-
-if __name__ == "__main__":
- if jungle:
- serials = PandaJungle.list()
- else:
- serials = Panda.list()
- num_senders = len(serials)
-
- if num_senders == 0:
- print("No senders found. Exiting")
- sys.exit(1)
- else:
- print("%d senders found. Starting broadcast" % num_senders)
-
- if "FLASH" in os.environ:
- for s in PandaDFU.list():
- PandaDFU(s).recover()
-
- time.sleep(1)
- for s in serials:
- Panda(s).recover()
- Panda(s).flash()
-
- pool = Pool(num_senders, initializer=initializer)
- pool.map_async(send_thread, serials)
-
- while True:
- try:
- time.sleep(10)
- except KeyboardInterrupt:
- pool.terminate()
- pool.join()
- raise
-
diff --git a/selfdrive/boardd/tests/test_boardd b/selfdrive/boardd/tests/test_boardd
index f249724f6a..b4455ce67c 100755
Binary files a/selfdrive/boardd/tests/test_boardd and b/selfdrive/boardd/tests/test_boardd differ
diff --git a/selfdrive/boardd/tests/test_boardd_api.py b/selfdrive/boardd/tests/test_boardd_api.py
deleted file mode 100644
index 9386c7845e..0000000000
--- a/selfdrive/boardd/tests/test_boardd_api.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import random
-import numpy as np
-
-import selfdrive.boardd.tests.boardd_old as boardd_old
-import selfdrive.boardd.boardd as boardd
-
-from common.realtime import sec_since_boot
-from cereal import log
-import unittest
-
-
-def generate_random_can_data_list():
- can_list = []
- cnt = random.randint(1, 64)
- for _ in range(cnt):
- can_data = np.random.bytes(random.randint(1, 8))
- can_list.append([random.randint(0, 128), random.randint(0, 128), can_data, random.randint(0, 128)])
- return can_list, cnt
-
-
-class TestBoarddApiMethods(unittest.TestCase):
- def test_correctness(self):
- for i in range(1000):
- can_list, _ = generate_random_can_data_list()
-
- # Sendcan
- # Old API
- m_old = boardd_old.can_list_to_can_capnp(can_list, 'sendcan').to_bytes()
- # new API
- m = boardd.can_list_to_can_capnp(can_list, 'sendcan')
-
- ev_old = log.Event.from_bytes(m_old)
- ev = log.Event.from_bytes(m)
-
- self.assertEqual(ev_old.which(), ev.which())
- self.assertEqual(len(ev.sendcan), len(ev_old.sendcan))
- for i in range(len(ev.sendcan)):
- attrs = ['address', 'busTime', 'dat', 'src']
- for attr in attrs:
- self.assertEqual(getattr(ev.sendcan[i], attr, 'new'), getattr(ev_old.sendcan[i], attr, 'old'))
-
- # Can
- m_old = boardd_old.can_list_to_can_capnp(can_list, 'can').to_bytes()
- # new API
- m = boardd.can_list_to_can_capnp(can_list, 'can')
-
- ev_old = log.Event.from_bytes(m_old)
- ev = log.Event.from_bytes(m)
- self.assertEqual(ev_old.which(), ev.which())
- self.assertEqual(len(ev.can), len(ev_old.can))
- for i in range(len(ev.can)):
- attrs = ['address', 'busTime', 'dat', 'src']
- for attr in attrs:
- self.assertEqual(getattr(ev.can[i], attr, 'new'), getattr(ev_old.can[i], attr, 'old'))
-
- def test_performance(self):
- can_list, _ = generate_random_can_data_list()
- recursions = 1000
-
- n1 = sec_since_boot()
- for _ in range(recursions):
- boardd_old.can_list_to_can_capnp(can_list, 'sendcan').to_bytes()
- n2 = sec_since_boot()
- elapsed_old = n2 - n1
-
- # print('Old API, elapsed time: {} secs'.format(elapsed_old))
- n1 = sec_since_boot()
- for _ in range(recursions):
- boardd.can_list_to_can_capnp(can_list)
- n2 = sec_since_boot()
- elapsed_new = n2 - n1
- # print('New API, elapsed time: {} secs'.format(elapsed_new))
- self.assertTrue(elapsed_new < elapsed_old / 2)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py
index 631b4c987f..e9bbcb4586 100755
--- a/selfdrive/boardd/tests/test_boardd_loopback.py
+++ b/selfdrive/boardd/tests/test_boardd_loopback.py
@@ -12,7 +12,7 @@ from common.spinner import Spinner
from common.timeout import Timeout
from selfdrive.boardd.boardd import can_list_to_can_capnp
from selfdrive.car import make_can_msg
-from selfdrive.hardware import TICI
+from system.hardware import TICI
from selfdrive.test.helpers import phone_only, with_processes
diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc
index 6a13cbd71f..cc3b4bce9a 100644
--- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc
+++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc
@@ -6,8 +6,6 @@
#include "cereal/messaging/messaging.h"
#include "selfdrive/boardd/panda.h"
-const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};
-
int random_int(int min, int max) {
std::random_device dev;
std::mt19937 rng(dev());
@@ -50,7 +48,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState
can.setAddress(i);
can.setSrc(random_int(0, 3) + bus_offset);
can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size()));
- total_pakets_size += CANPACKET_HEAD_SIZE + dat.size();
+ total_pakets_size += sizeof(can_header) + dat.size();
}
can_data_list = can_list.asReader();
@@ -60,14 +58,11 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState
void PandaTest::test_can_send() {
std::vector unpacked_data;
this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) {
- int size_left = size;
- for (int i = 0, counter = 0; i < size; i += USBPACKET_MAX_SIZE, counter++) {
- REQUIRE(chunk[i] == counter);
-
- const int len = std::min(USBPACKET_MAX_SIZE, size_left);
- unpacked_data.insert(unpacked_data.end(), &chunk[i + 1], &chunk[i + len]);
- size_left -= len;
- }
+ uint32_t magic;
+ memcpy(&magic, &chunk[0], sizeof(uint32_t));
+
+ REQUIRE(magic == CAN_TRANSACTION_MAGIC);
+ unpacked_data.insert(unpacked_data.end(), &chunk[sizeof(uint32_t)], &chunk[size]);
});
REQUIRE(unpacked_data.size() == total_pakets_size);
@@ -75,9 +70,9 @@ void PandaTest::test_can_send() {
INFO("test can message integrity");
for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) {
can_header header;
- memcpy(&header, &unpacked_data[pos], CANPACKET_HEAD_SIZE);
+ memcpy(&header, &unpacked_data[pos], sizeof(can_header));
const uint8_t data_len = dlc_to_len[header.data_len_code];
- pckt_len = CANPACKET_HEAD_SIZE + data_len;
+ pckt_len = sizeof(can_header) + data_len;
REQUIRE(header.addr == cnt);
REQUIRE(test_data.find(data_len) != test_data.end());
diff --git a/selfdrive/camerad/SConscript b/selfdrive/camerad/SConscript
deleted file mode 100644
index 85bc756bc1..0000000000
--- a/selfdrive/camerad/SConscript
+++ /dev/null
@@ -1,47 +0,0 @@
-Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'USE_WEBCAM')
-
-libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon]
-
-if arch == "aarch64":
- libs += ['gsl', 'CB', 'adreno_utils', 'EGL', 'GLESv3', 'cutils', 'ui']
- cameras = ['cameras/camera_qcom.cc']
-elif arch == "larch64":
- libs += ['atomic']
- cameras = ['cameras/camera_qcom2.cc']
-else:
- env['CXXFLAGS'] += ["-Wno-deprecated-declarations"]
- if USE_WEBCAM:
- libs += ['opencv_core', 'opencv_highgui', 'opencv_imgproc', 'opencv_videoio']
- cameras = ['cameras/camera_webcam.cc']
- env = env.Clone()
- env.Append(CXXFLAGS = '-DWEBCAM')
- env.Append(CFLAGS = '-DWEBCAM')
- env.Append(CPPPATH = ['/usr/include/opencv4', '/usr/local/include/opencv4'])
- else:
- libs += ['avutil', 'avcodec', 'avformat', 'bz2', 'ssl', 'curl', 'crypto']
- # TODO: import replay_lib from root SConstruct
- cameras = ['cameras/camera_replay.cc',
- env.Object('camera-util', '#/selfdrive/ui/replay/util.cc'),
- env.Object('camera-framereader', '#/selfdrive/ui/replay/framereader.cc'),
- env.Object('camera-filereader', '#/selfdrive/ui/replay/filereader.cc')]
-
- if arch == "Darwin":
- del libs[libs.index('OpenCL')]
- del libs[libs.index(gpucommon)][gpucommon.index('GL')]
- env = env.Clone()
- env['FRAMEWORKS'] = ['OpenCL', 'OpenGL']
-
-env.Program('camerad', [
- 'main.cc',
- 'cameras/camera_common.cc',
- 'transforms/rgb_to_yuv.cc',
- 'imgproc/utils.cc',
- cameras,
- ], LIBS=libs)
-
-if GetOption("test"):
- env.Program('test/ae_gray_test', [
- 'test/ae_gray_test.cc',
- 'cameras/camera_common.cc',
- 'transforms/rgb_to_yuv.cc',
- ], LIBS=libs)
diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc
deleted file mode 100644
index da01b94177..0000000000
--- a/selfdrive/camerad/cameras/camera_qcom.cc
+++ /dev/null
@@ -1,1164 +0,0 @@
-#include "selfdrive/camerad/cameras/camera_qcom.h"
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-#include "selfdrive/camerad/cameras/sensor_i2c.h"
-#include "selfdrive/camerad/include/msm_cam_sensor.h"
-#include "selfdrive/camerad/include/msmb_camera.h"
-#include "selfdrive/camerad/include/msmb_isp.h"
-#include "selfdrive/camerad/include/msmb_ispif.h"
-#include "selfdrive/common/clutil.h"
-#include "selfdrive/common/params.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/timing.h"
-#include "selfdrive/common/util.h"
-
-// leeco actuator (DW9800W H-Bridge Driver IC)
-// from sniff
-const uint16_t INFINITY_DAC = 364;
-
-extern ExitHandler do_exit;
-
-static int cam_ioctl(int fd, unsigned long int request, void *arg, const char *log_msg = nullptr) {
- int err = HANDLE_EINTR(ioctl(fd, request, arg));
- if (err != 0 && log_msg) {
- LOG(util::string_format("%s: %d", log_msg, err).c_str());
- }
- return err;
-}
-// global var for AE/AF ops
-std::atomic road_cam_exp{{0}};
-std::atomic driver_cam_exp{{0}};
-
-CameraInfo cameras_supported[CAMERA_ID_MAX] = {
- [CAMERA_ID_IMX298] = {
- .frame_width = 2328,
- .frame_height = 1748,
- .frame_stride = 2912,
- .bayer = true,
- .bayer_flip = 3,
- .hdr = true
- },
- [CAMERA_ID_OV8865] = {
- .frame_width = 1632,
- .frame_height = 1224,
- .frame_stride = 2040, // seems right
- .bayer = true,
- .bayer_flip = 3,
- .hdr = false
- },
- // this exists to get the kernel to build for the LeEco in release
- [CAMERA_ID_IMX298_FLIPPED] = {
- .frame_width = 2328,
- .frame_height = 1748,
- .frame_stride = 2912,
- .bayer = true,
- .bayer_flip = 3,
- .hdr = true
- },
- [CAMERA_ID_OV10640] = {
- .frame_width = 1280,
- .frame_height = 1080,
- .frame_stride = 2040,
- .bayer = true,
- .bayer_flip = 0,
- .hdr = true
- },
-};
-
-static void camera_release_buffer(void* cookie, int buf_idx) {
- CameraState *s = (CameraState *)cookie;
- // printf("camera_release_buffer %d\n", buf_idx);
- s->ss[0].qbuf_info[buf_idx].dirty_buf = 1;
- HANDLE_EINTR(ioctl(s->isp_fd, VIDIOC_MSM_ISP_ENQUEUE_BUF, &s->ss[0].qbuf_info[buf_idx]));
-}
-
-int sensor_write_regs(CameraState *s, struct msm_camera_i2c_reg_array* arr, size_t size, msm_camera_i2c_data_type data_type) {
- struct msm_camera_i2c_reg_setting out_settings = {
- .reg_setting = arr,
- .size = (uint16_t)size,
- .addr_type = MSM_CAMERA_I2C_WORD_ADDR,
- .data_type = data_type,
- .delay = 0,
- };
- sensorb_cfg_data cfg_data = {.cfgtype = CFG_WRITE_I2C_ARRAY, .cfg.setting = &out_settings};
- return HANDLE_EINTR(ioctl(s->sensor_fd, VIDIOC_MSM_SENSOR_CFG, &cfg_data));
-}
-
-static int imx298_apply_exposure(CameraState *s, int gain, int integ_lines, uint32_t frame_length) {
- int analog_gain = std::min(gain, 448);
- s->digital_gain = gain > 448 ? (512.0/(512-(gain))) / 8.0 : 1.0;
- //printf("%5d/%5d %5d %f\n", s->cur_integ_lines, s->frame_length, analog_gain, s->digital_gain);
-
- struct msm_camera_i2c_reg_array reg_array[] = {
- // REG_HOLD
- {0x104,0x1,0},
- {0x3002,0x0,0}, // long autoexposure off
-
- // FRM_LENGTH
- {0x340, (uint16_t)(frame_length >> 8), 0}, {0x341, (uint16_t)(frame_length & 0xff), 0},
- // INTEG_TIME aka coarse_int_time_addr aka shutter speed
- {0x202, (uint16_t)(integ_lines >> 8), 0}, {0x203, (uint16_t)(integ_lines & 0xff),0},
- // global_gain_addr
- // if you assume 1x gain is 32, 448 is 14x gain, aka 2^14=16384
- {0x204, (uint16_t)(analog_gain >> 8), 0}, {0x205, (uint16_t)(analog_gain & 0xff),0},
-
- // digital gain for colors: gain_greenR, gain_red, gain_blue, gain_greenB
- /*{0x20e, digital_gain_gr >> 8, 0}, {0x20f,digital_gain_gr & 0xFF,0},
- {0x210, digital_gain_r >> 8, 0}, {0x211,digital_gain_r & 0xFF,0},
- {0x212, digital_gain_b >> 8, 0}, {0x213,digital_gain_b & 0xFF,0},
- {0x214, digital_gain_gb >> 8, 0}, {0x215,digital_gain_gb & 0xFF,0},*/
-
- // REG_HOLD
- {0x104,0x0,0},
- };
- return sensor_write_regs(s, reg_array, std::size(reg_array), MSM_CAMERA_I2C_BYTE_DATA);
-}
-
-static int ov8865_apply_exposure(CameraState *s, int gain, int integ_lines, uint32_t frame_length) {
- //printf("driver camera: %d %d %d\n", gain, integ_lines, frame_length);
- int coarse_gain_bitmap, fine_gain_bitmap;
-
- // get bitmaps from iso
- static const int gains[] = {0, 100, 200, 400, 800};
- int i;
- for (i = 1; i < std::size(gains); i++) {
- if (gain >= gains[i - 1] && gain < gains[i])
- break;
- }
- int coarse_gain = i - 1;
- float fine_gain = (gain - gains[coarse_gain])/(float)(gains[coarse_gain+1]-gains[coarse_gain]);
- coarse_gain_bitmap = (1 << coarse_gain) - 1;
- fine_gain_bitmap = ((int)(16*fine_gain) << 3) + 128; // 7th is always 1, 0-2nd are always 0
-
- integ_lines *= 16; // The exposure value in reg is in 16ths of a line
-
- struct msm_camera_i2c_reg_array reg_array[] = {
- //{0x104,0x1,0},
-
- // FRM_LENGTH
- {0x380e, (uint16_t)(frame_length >> 8), 0}, {0x380f, (uint16_t)(frame_length & 0xff), 0},
- // AEC EXPO
- {0x3500, (uint16_t)(integ_lines >> 16), 0}, {0x3501, (uint16_t)(integ_lines >> 8), 0}, {0x3502, (uint16_t)(integ_lines & 0xff),0},
- // AEC MANUAL
- {0x3503, 0x4, 0},
- // AEC GAIN
- {0x3508, (uint16_t)(coarse_gain_bitmap), 0}, {0x3509, (uint16_t)(fine_gain_bitmap), 0},
-
- //{0x104,0x0,0},
- };
- return sensor_write_regs(s, reg_array, std::size(reg_array), MSM_CAMERA_I2C_BYTE_DATA);
-}
-
-static void camera_init(VisionIpcServer *v, CameraState *s, int camera_id, int camera_num,
- uint32_t pixel_clock, uint32_t line_length_pclk,
- uint32_t max_gain, uint32_t fps, cl_device_id device_id, cl_context ctx,
- VisionStreamType rgb_type, VisionStreamType yuv_type) {
- s->camera_num = camera_num;
- s->camera_id = camera_id;
-
- assert(camera_id < std::size(cameras_supported));
- s->ci = cameras_supported[camera_id];
- assert(s->ci.frame_width != 0);
-
- s->pixel_clock = pixel_clock;
- s->max_gain = max_gain;
- s->fps = fps;
- s->frame_length = s->pixel_clock / line_length_pclk / s->fps;
- s->self_recover = 0;
-
- s->apply_exposure = (camera_id == CAMERA_ID_IMX298) ? imx298_apply_exposure : ov8865_apply_exposure;
- s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type, camera_release_buffer);
-}
-
-void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) {
- char project_name[1024] = {0};
- property_get("ro.boot.project_name", project_name, "");
- assert(strlen(project_name) == 0);
-
- // sensor is flipped in LP3
- // IMAGE_ORIENT = 3
- init_array_imx298[0].reg_data = 3;
-
- // 0 = ISO 100
- // 256 = ISO 200
- // 384 = ISO 400
- // 448 = ISO 800
- // 480 = ISO 1600
- // 496 = ISO 3200
- // 504 = ISO 6400, 8x digital gain
- // 508 = ISO 12800, 16x digital gain
- // 510 = ISO 25600, 32x digital gain
-
- camera_init(v, &s->road_cam, CAMERA_ID_IMX298, 0,
- /*pixel_clock=*/600000000, /*line_length_pclk=*/5536,
- /*max_gain=*/510, //0 (ISO 100)- 448 (ISO 800, max analog gain) - 511 (super noisy)
-#ifdef HIGH_FPS
- /*fps*/ 60,
-#else
- /*fps*/ 20,
-#endif
- device_id, ctx,
- VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD);
-
- camera_init(v, &s->driver_cam, CAMERA_ID_OV8865, 1,
- /*pixel_clock=*/72000000, /*line_length_pclk=*/1602,
- /*max_gain=*/510, 10, device_id, ctx,
- VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER);
-
- s->sm = new SubMaster({"driverState"});
- s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"});
-
- for (int i = 0; i < FRAME_BUF_COUNT; i++) {
- // TODO: make lengths correct
- s->focus_bufs[i].allocate(0xb80);
- s->stats_bufs[i].allocate(0xb80);
- }
- std::fill_n(s->lapres, std::size(s->lapres), 16160);
- s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, s->road_cam.buf.rgb_stride, 3);
-}
-
-static void set_exposure(CameraState *s, float exposure_frac, float gain_frac) {
- int err = 0;
- uint32_t gain = s->cur_gain;
- uint32_t integ_lines = s->cur_integ_lines;
-
- if (exposure_frac >= 0) {
- exposure_frac = std::clamp(exposure_frac, 2.0f / s->frame_length, 1.0f);
- integ_lines = s->frame_length * exposure_frac;
-
- // See page 79 of the datasheet, this is the max allowed (-1 for phase adjust)
- integ_lines = std::min(integ_lines, s->frame_length - 11);
- }
-
- if (gain_frac >= 0) {
- // ISO200 is minimum gain
- gain_frac = std::clamp(gain_frac, 1.0f/64, 1.0f);
-
- // linearize gain response
- // TODO: will be wrong for driver camera
- // 0.125 -> 448
- // 0.25 -> 480
- // 0.5 -> 496
- // 1.0 -> 504
- // 512 - 512/(128*gain_frac)
- gain = (s->max_gain/510) * (512 - 512/(256*gain_frac));
- }
-
- if (gain != s->cur_gain || integ_lines != s->cur_integ_lines) {
- if (s->apply_exposure == ov8865_apply_exposure) {
- gain = 800 * gain_frac; // ISO
- }
- err = s->apply_exposure(s, gain, integ_lines, s->frame_length);
- if (err == 0) {
- std::lock_guard lk(s->frame_info_lock);
- s->cur_gain = gain;
- s->cur_integ_lines = integ_lines;
- } else {
- LOGE("camera %d apply_exposure err: %d", s->camera_num, err);
- }
- }
-
- if (err == 0) {
- s->cur_exposure_frac = exposure_frac;
- std::lock_guard lk(s->frame_info_lock);
- s->cur_gain_frac = gain_frac;
- }
-
- //LOGD("set exposure: %f %f - %d", exposure_frac, gain_frac, err);
-}
-
-static void do_autoexposure(CameraState *s, float grey_frac) {
- const float target_grey = 0.3;
-
- s->frame_info_lock.lock();
- s->measured_grey_fraction = grey_frac;
- s->target_grey_fraction = target_grey;
- s->frame_info_lock.unlock();
-
- if (s->apply_exposure == ov8865_apply_exposure) {
- // gain limits downstream
- const float gain_frac_min = 0.015625;
- const float gain_frac_max = 1.0;
- // exposure time limits
- const uint32_t exposure_time_min = 16;
- const uint32_t exposure_time_max = s->frame_length - 11; // copied from set_exposure()
-
- float cur_gain_frac = s->cur_gain_frac;
- float exposure_factor = pow(1.05, (target_grey - grey_frac) / 0.05);
- if (cur_gain_frac > 0.125 && exposure_factor < 1) {
- cur_gain_frac *= exposure_factor;
- } else if (s->cur_integ_lines * exposure_factor <= exposure_time_max && s->cur_integ_lines * exposure_factor >= exposure_time_min) { // adjust exposure time first
- s->cur_exposure_frac *= exposure_factor;
- } else if (cur_gain_frac * exposure_factor <= gain_frac_max && cur_gain_frac * exposure_factor >= gain_frac_min) {
- cur_gain_frac *= exposure_factor;
- }
- s->frame_info_lock.lock();
- s->cur_gain_frac = cur_gain_frac;
- s->frame_info_lock.unlock();
-
- set_exposure(s, s->cur_exposure_frac, cur_gain_frac);
- } else { // keep the old for others
- float new_exposure = s->cur_exposure_frac;
- new_exposure *= pow(1.05, (target_grey - grey_frac) / 0.05 );
- //LOGD("diff %f: %f to %f", target_grey - grey_frac, s->cur_exposure_frac, new_exposure);
-
- float new_gain = s->cur_gain_frac;
- if (new_exposure < 0.10) {
- new_gain *= 0.95;
- } else if (new_exposure > 0.40) {
- new_gain *= 1.05;
- }
-
- set_exposure(s, new_exposure, new_gain);
- }
-}
-
-static void sensors_init(MultiCameraState *s) {
- msm_camera_sensor_slave_info slave_infos[2] = {
- (msm_camera_sensor_slave_info){ // road camera
- .sensor_name = "imx298",
- .eeprom_name = "sony_imx298",
- .actuator_name = "dw9800w",
- .ois_name = "",
- .flash_name = "pmic",
- .camera_id = CAMERA_0,
- .slave_addr = 32,
- .i2c_freq_mode = I2C_FAST_MODE,
- .addr_type = MSM_CAMERA_I2C_WORD_ADDR,
- .sensor_id_info = {.sensor_id_reg_addr = 22, .sensor_id = 664, .module_id = 9, .vcm_id = 6},
- .power_setting_array = {
- .power_setting_a = {
- {.seq_type = SENSOR_GPIO, .delay = 1},
- {.seq_type = SENSOR_VREG, .seq_val = 2},
- {.seq_type = SENSOR_GPIO, .seq_val = 5, .config_val = 2},
- {.seq_type = SENSOR_VREG, .seq_val = 1},
- {.seq_type = SENSOR_VREG, .seq_val = 3, .delay = 1},
- {.seq_type = SENSOR_CLK, .config_val = 24000000, .delay = 1},
- {.seq_type = SENSOR_GPIO, .config_val = 2, .delay = 10},
- },
- .size = 7,
- .power_down_setting_a = {
- {.seq_type = SENSOR_CLK, .delay = 1},
- {.seq_type = SENSOR_GPIO, .delay = 1},
- {.seq_type = SENSOR_VREG, .seq_val = 1},
- {.seq_type = SENSOR_GPIO, .seq_val = 5},
- {.seq_type = SENSOR_VREG, .seq_val = 2},
- {.seq_type = SENSOR_VREG, .seq_val = 3, .delay = 1},
- },
- .size_down = 6,
- },
- .is_init_params_valid = 0,
- .sensor_init_params = {.modes_supported = 1, .position = BACK_CAMERA_B, .sensor_mount_angle = 90},
- .output_format = MSM_SENSOR_BAYER,
- },
- (msm_camera_sensor_slave_info){ // driver camera
- .sensor_name = "ov8865_sunny",
- .eeprom_name = "ov8865_plus",
- .actuator_name = "",
- .ois_name = "",
- .flash_name = "",
- .camera_id = CAMERA_2,
- .slave_addr = 108,
- .i2c_freq_mode = I2C_FAST_MODE,
- .addr_type = MSM_CAMERA_I2C_WORD_ADDR,
- .sensor_id_info = {.sensor_id_reg_addr = 12299, .sensor_id = 34917, .module_id = 2},
- .power_setting_array = {
- .power_setting_a = {
- {.seq_type = SENSOR_GPIO, .delay = 5},
- {.seq_type = SENSOR_VREG, .seq_val = 1},
- {.seq_type = SENSOR_VREG, .seq_val = 2},
- {.seq_type = SENSOR_VREG},
- {.seq_type = SENSOR_CLK, .config_val = 24000000, .delay = 1},
- {.seq_type = SENSOR_GPIO, .config_val = 2, .delay = 1},
- },
- .size = 6,
- .power_down_setting_a = {
- {.seq_type = SENSOR_GPIO, .delay = 5},
- {.seq_type = SENSOR_CLK, .delay = 1},
- {.seq_type = SENSOR_VREG},
- {.seq_type = SENSOR_VREG, .seq_val = 1},
- {.seq_type = SENSOR_VREG, .seq_val = 2, .delay = 1},
- },
- .size_down = 5,
- },
- .is_init_params_valid = 0,
- .sensor_init_params = {.modes_supported = 1, .position = FRONT_CAMERA_B, .sensor_mount_angle = 270},
- .output_format = MSM_SENSOR_BAYER,
- }};
-
- unique_fd sensorinit_fd = open_v4l_by_name_and_index("msm_sensor_init");
- assert(sensorinit_fd >= 0);
- for (auto &info : slave_infos) {
- info.power_setting_array.power_setting = &info.power_setting_array.power_setting_a[0];
- info.power_setting_array.power_down_setting = &info.power_setting_array.power_down_setting_a[0];
- sensor_init_cfg_data sensor_init_cfg = {.cfgtype = CFG_SINIT_PROBE, .cfg.setting = &info};
- int err = cam_ioctl(sensorinit_fd, VIDIOC_MSM_SENSOR_INIT_CFG, &sensor_init_cfg, "sensor init cfg");
- assert(err >= 0);
- }
-}
-
-static void camera_open(CameraState *s, bool is_road_cam) {
- struct csid_cfg_data csid_cfg_data = {};
- struct v4l2_event_subscription sub = {};
-
- struct msm_actuator_cfg_data actuator_cfg_data = {};
-
- // open devices
- s->csid_fd = open_v4l_by_name_and_index("msm_csid", is_road_cam ? 0 : 2);
- assert(s->csid_fd >= 0);
- s->csiphy_fd = open_v4l_by_name_and_index("msm_csiphy", is_road_cam ? 0 : 2);
- assert(s->csiphy_fd >= 0);
- s->isp_fd = open_v4l_by_name_and_index("vfe", is_road_cam ? 0 : 1);
- assert(s->isp_fd >= 0);
-
- if (is_road_cam) {
- s->actuator_fd = open_v4l_by_name_and_index("msm_actuator");
- assert(s->actuator_fd >= 0);
- }
-
- // wait for sensor device
- // on first startup, these devices aren't present yet
- for (int i = 0; i < 10; i++) {
- s->sensor_fd = open_v4l_by_name_and_index(is_road_cam ? "imx298" : "ov8865_sunny");
- if (s->sensor_fd >= 0) break;
- LOGW("waiting for sensors...");
- util::sleep_for(1000); // sleep one second
- }
- assert(s->sensor_fd >= 0);
-
- // *** SHUTDOWN ALL ***
-
- // CSIPHY: release csiphy
- struct msm_camera_csi_lane_params csi_lane_params = {0};
- csi_lane_params.csi_lane_mask = 0x1f;
- csiphy_cfg_data csiphy_cfg_data = { .cfg.csi_lane_params = &csi_lane_params, .cfgtype = CSIPHY_RELEASE};
- int err = cam_ioctl(s->csiphy_fd, VIDIOC_MSM_CSIPHY_IO_CFG, &csiphy_cfg_data, "release csiphy");
-
- // CSID: release csid
- csid_cfg_data.cfgtype = CSID_RELEASE;
- cam_ioctl(s->csid_fd, VIDIOC_MSM_CSID_IO_CFG, &csid_cfg_data, "release csid");
-
- // SENSOR: send power down
- struct sensorb_cfg_data sensorb_cfg_data = {.cfgtype = CFG_POWER_DOWN};
- cam_ioctl(s->sensor_fd, VIDIOC_MSM_SENSOR_CFG, &sensorb_cfg_data, "sensor power down");
-
- // actuator powerdown
- actuator_cfg_data.cfgtype = CFG_ACTUATOR_POWERDOWN;
- cam_ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data, "actuator powerdown");
-
- // reset isp
- // struct msm_vfe_axi_halt_cmd halt_cmd = {
- // .stop_camif = 1,
- // .overflow_detected = 1,
- // .blocking_halt = 1,
- // };
- // err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_AXI_HALT, &halt_cmd);
- // printf("axi halt: %d\n", err);
-
- // struct msm_vfe_axi_reset_cmd reset_cmd = {
- // .blocking = 1,
- // .frame_id = 1,
- // };
- // err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_AXI_RESET, &reset_cmd);
- // printf("axi reset: %d\n", err);
-
- // struct msm_vfe_axi_restart_cmd restart_cmd = {
- // .enable_camif = 1,
- // };
- // err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_AXI_RESTART, &restart_cmd);
- // printf("axi restart: %d\n", err);
-
- // **** GO GO GO ****
- LOG("******************** GO GO GO ************************");
-
- // CSID: init csid
- csid_cfg_data.cfgtype = CSID_INIT;
- cam_ioctl(s->csid_fd, VIDIOC_MSM_CSID_IO_CFG, &csid_cfg_data, "init csid");
-
- // CSIPHY: init csiphy
- csiphy_cfg_data = {.cfgtype = CSIPHY_INIT};
- cam_ioctl(s->csiphy_fd, VIDIOC_MSM_CSIPHY_IO_CFG, &csiphy_cfg_data, "init csiphy");
-
- // SENSOR: stop stream
- struct msm_camera_i2c_reg_setting stop_settings = {
- .reg_setting = stop_reg_array,
- .size = std::size(stop_reg_array),
- .addr_type = MSM_CAMERA_I2C_WORD_ADDR,
- .data_type = MSM_CAMERA_I2C_BYTE_DATA,
- .delay = 0
- };
- sensorb_cfg_data.cfgtype = CFG_SET_STOP_STREAM_SETTING;
- sensorb_cfg_data.cfg.setting = &stop_settings;
- cam_ioctl(s->sensor_fd, VIDIOC_MSM_SENSOR_CFG, &sensorb_cfg_data, "stop stream");
-
- // SENSOR: send power up
- sensorb_cfg_data = {.cfgtype = CFG_POWER_UP};
- cam_ioctl(s->sensor_fd, VIDIOC_MSM_SENSOR_CFG, &sensorb_cfg_data, "sensor power up");
-
- // **** configure the sensor ****
-
- // SENSOR: send i2c configuration
- if (s->camera_id == CAMERA_ID_IMX298) {
- err = sensor_write_regs(s, init_array_imx298, std::size(init_array_imx298), MSM_CAMERA_I2C_BYTE_DATA);
- } else if (s->camera_id == CAMERA_ID_OV8865) {
- err = sensor_write_regs(s, init_array_ov8865, std::size(init_array_ov8865), MSM_CAMERA_I2C_BYTE_DATA);
- } else {
- assert(false);
- }
- LOG("sensor init i2c: %d", err);
-
- if (is_road_cam) {
- // init the actuator
- actuator_cfg_data.cfgtype = CFG_ACTUATOR_POWERUP;
- cam_ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data, "actuator powerup");
-
- actuator_cfg_data.cfgtype = CFG_ACTUATOR_INIT;
- cam_ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data, "actuator init");
-
- struct msm_actuator_reg_params_t actuator_reg_params[] = {
- {
- .reg_write_type = MSM_ACTUATOR_WRITE_DAC,
- // MSB here at address 3
- .reg_addr = 3,
- .data_type = 9,
- .addr_type = 4,
- },
- };
-
- struct reg_settings_t actuator_init_settings[] = {
- { .reg_addr=2, .addr_type=MSM_ACTUATOR_BYTE_ADDR, .reg_data=1, .data_type = MSM_ACTUATOR_BYTE_DATA, .i2c_operation = MSM_ACT_WRITE, .delay = 0 }, // PD = power down
- { .reg_addr=2, .addr_type=MSM_ACTUATOR_BYTE_ADDR, .reg_data=0, .data_type = MSM_ACTUATOR_BYTE_DATA, .i2c_operation = MSM_ACT_WRITE, .delay = 2 }, // 0 = power up
- { .reg_addr=2, .addr_type=MSM_ACTUATOR_BYTE_ADDR, .reg_data=2, .data_type = MSM_ACTUATOR_BYTE_DATA, .i2c_operation = MSM_ACT_WRITE, .delay = 2 }, // RING = SAC mode
- { .reg_addr=6, .addr_type=MSM_ACTUATOR_BYTE_ADDR, .reg_data=64, .data_type = MSM_ACTUATOR_BYTE_DATA, .i2c_operation = MSM_ACT_WRITE, .delay = 0 }, // 0x40 = SAC3 mode
- { .reg_addr=7, .addr_type=MSM_ACTUATOR_BYTE_ADDR, .reg_data=113, .data_type = MSM_ACTUATOR_BYTE_DATA, .i2c_operation = MSM_ACT_WRITE, .delay = 0 },
- // 0x71 = DIV1 | DIV0 | SACT0 -- Tvib x 1/4 (quarter)
- // SAC Tvib = 6.3 ms + 0.1 ms = 6.4 ms / 4 = 1.6 ms
- // LSC 1-step = 252 + 1*4 = 256 ms / 4 = 64 ms
- };
-
- struct region_params_t region_params[] = {
- {.step_bound = {238, 0,}, .code_per_step = 235, .qvalue = 128}
- };
-
- actuator_cfg_data.cfgtype = CFG_SET_ACTUATOR_INFO;
- actuator_cfg_data.cfg.set_info = (struct msm_actuator_set_info_t){
- .actuator_params = {
- .act_type = ACTUATOR_BIVCM,
- .reg_tbl_size = 1,
- .data_size = 10,
- .init_setting_size = 5,
- .i2c_freq_mode = I2C_STANDARD_MODE,
- .i2c_addr = 24,
- .i2c_addr_type = MSM_ACTUATOR_BYTE_ADDR,
- .i2c_data_type = MSM_ACTUATOR_WORD_DATA,
- .reg_tbl_params = &actuator_reg_params[0],
- .init_settings = &actuator_init_settings[0],
- .park_lens = {.damping_step = 1023, .damping_delay = 14000, .hw_params = 11, .max_step = 20},
- },
- .af_tuning_params = {
- .initial_code = INFINITY_DAC,
- .pwd_step = 0,
- .region_size = 1,
- .total_steps = 238,
- .region_params = ®ion_params[0],
- },
- };
-
- cam_ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data, "actuator set info");
- }
-
- if (s->camera_id == CAMERA_ID_IMX298) {
- err = sensor_write_regs(s, mode_setting_array_imx298, std::size(mode_setting_array_imx298), MSM_CAMERA_I2C_BYTE_DATA);
- LOG("sensor setup: %d", err);
- }
-
- // CSIPHY: configure csiphy
- struct msm_camera_csiphy_params csiphy_params = {};
- if (s->camera_id == CAMERA_ID_IMX298) {
- csiphy_params = {.lane_cnt = 4, .settle_cnt = 14, .lane_mask = 0x1f, .csid_core = 0};
- } else if (s->camera_id == CAMERA_ID_OV8865) {
- // guess!
- csiphy_params = {.lane_cnt = 4, .settle_cnt = 24, .lane_mask = 0x1f, .csid_core = 2};
- }
- csiphy_cfg_data.cfgtype = CSIPHY_CFG;
- csiphy_cfg_data.cfg.csiphy_params = &csiphy_params;
- cam_ioctl(s->csiphy_fd, VIDIOC_MSM_CSIPHY_IO_CFG, &csiphy_cfg_data, "csiphy configure");
-
- // CSID: configure csid
-#define CSI_STATS 0x35
-#define CSI_PD 0x36
- struct msm_camera_csid_params csid_params = {
- .lane_cnt = 4,
- .lane_assign = 0x4320,
- .phy_sel = (uint8_t)(is_road_cam ? 0 : 2),
- .lut_params.num_cid = (uint8_t)(is_road_cam ? 3 : 1),
- .lut_params.vc_cfg_a = {
- {.cid = 0, .dt = CSI_RAW10, .decode_format = CSI_DECODE_10BIT},
- {.cid = 1, .dt = CSI_PD, .decode_format = CSI_DECODE_10BIT},
- {.cid = 2, .dt = CSI_STATS, .decode_format = CSI_DECODE_10BIT},
- },
- };
-
- csid_params.lut_params.vc_cfg[0] = &csid_params.lut_params.vc_cfg_a[0];
- csid_params.lut_params.vc_cfg[1] = &csid_params.lut_params.vc_cfg_a[1];
- csid_params.lut_params.vc_cfg[2] = &csid_params.lut_params.vc_cfg_a[2];
-
- csid_cfg_data.cfgtype = CSID_CFG;
- csid_cfg_data.cfg.csid_params = &csid_params;
- cam_ioctl(s->csid_fd, VIDIOC_MSM_CSID_IO_CFG, &csid_cfg_data, "csid configure");
-
- // ISP: SMMU_ATTACH
- msm_vfe_smmu_attach_cmd smmu_attach_cmd = {.security_mode = 0, .iommu_attach_mode = IOMMU_ATTACH};
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_SMMU_ATTACH, &smmu_attach_cmd, "isp smmu attach");
-
- // ******************* STREAM RAW *****************************
-
- // configure QMET input
- struct msm_vfe_input_cfg input_cfg = {};
- for (int i = 0; i < (is_road_cam ? 3 : 1); i++) {
- StreamState *ss = &s->ss[i];
-
- memset(&input_cfg, 0, sizeof(struct msm_vfe_input_cfg));
- input_cfg.input_src = (msm_vfe_input_src)(VFE_RAW_0+i);
- input_cfg.input_pix_clk = s->pixel_clock;
- input_cfg.d.rdi_cfg.cid = i;
- input_cfg.d.rdi_cfg.frame_based = 1;
- err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_INPUT_CFG, &input_cfg);
- LOG("configure input(%d): %d", i, err);
-
- // ISP: REQUEST_STREAM
- ss->stream_req.axi_stream_handle = 0;
- if (is_road_cam) {
- ss->stream_req.session_id = 2;
- ss->stream_req.stream_id = /*ISP_META_CHANNEL_BIT | */ISP_NATIVE_BUF_BIT | (1+i);
- } else {
- ss->stream_req.session_id = 3;
- ss->stream_req.stream_id = ISP_NATIVE_BUF_BIT | 1;
- }
-
- if (i == 0) {
- ss->stream_req.output_format = v4l2_fourcc('R', 'G', '1', '0');
- } else {
- ss->stream_req.output_format = v4l2_fourcc('Q', 'M', 'E', 'T');
- }
- ss->stream_req.stream_src = (msm_vfe_axi_stream_src)(RDI_INTF_0+i);
-
-#ifdef HIGH_FPS
- if (is_road_cam) {
- ss->stream_req.frame_skip_pattern = EVERY_3FRAME;
- }
-#endif
-
- ss->stream_req.frame_base = 1;
- ss->stream_req.buf_divert = 1; //i == 0;
-
- // setup stream plane. doesn't even matter?
- /*s->stream_req.plane_cfg[0].output_plane_format = Y_PLANE;
- s->stream_req.plane_cfg[0].output_width = s->ci.frame_width;
- s->stream_req.plane_cfg[0].output_height = s->ci.frame_height;
- s->stream_req.plane_cfg[0].output_stride = s->ci.frame_width;
- s->stream_req.plane_cfg[0].output_scan_lines = s->ci.frame_height;
- s->stream_req.plane_cfg[0].rdi_cid = 0;*/
-
- err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_REQUEST_STREAM, &ss->stream_req);
- LOG("isp request stream: %d -> 0x%x", err, ss->stream_req.axi_stream_handle);
-
- // ISP: REQUEST_BUF
- ss->buf_request.session_id = ss->stream_req.session_id;
- ss->buf_request.stream_id = ss->stream_req.stream_id;
- ss->buf_request.num_buf = FRAME_BUF_COUNT;
- ss->buf_request.buf_type = ISP_PRIVATE_BUF;
- ss->buf_request.handle = 0;
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_REQUEST_BUF, &ss->buf_request, "isp request buf");
- LOG("got buf handle: 0x%x", ss->buf_request.handle);
-
- // ENQUEUE all buffers
- for (int j = 0; j < ss->buf_request.num_buf; j++) {
- ss->qbuf_info[j].handle = ss->buf_request.handle;
- ss->qbuf_info[j].buf_idx = j;
- ss->qbuf_info[j].buffer.num_planes = 1;
- ss->qbuf_info[j].buffer.planes[0].addr = ss->bufs[j].fd;
- ss->qbuf_info[j].buffer.planes[0].length = ss->bufs[j].len;
- err = ioctl(s->isp_fd, VIDIOC_MSM_ISP_ENQUEUE_BUF, &ss->qbuf_info[j]);
- }
-
- // ISP: UPDATE_STREAM
- struct msm_vfe_axi_stream_update_cmd update_cmd = {};
- update_cmd.num_streams = 1;
- update_cmd.update_info[0].user_stream_id = ss->stream_req.stream_id;
- update_cmd.update_info[0].stream_handle = ss->stream_req.axi_stream_handle;
- update_cmd.update_type = UPDATE_STREAM_ADD_BUFQ;
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_UPDATE_STREAM, &update_cmd, "isp update stream");
- }
-
- LOG("******** START STREAMS ********");
-
- sub.id = 0;
- sub.type = 0x1ff;
- cam_ioctl(s->isp_fd, VIDIOC_SUBSCRIBE_EVENT, &sub, "isp subscribe");
-
- // ISP: START_STREAM
- s->stream_cfg.cmd = START_STREAM;
- s->stream_cfg.num_streams = is_road_cam ? 3 : 1;
- for (int i = 0; i < s->stream_cfg.num_streams; i++) {
- s->stream_cfg.stream_handle[i] = s->ss[i].stream_req.axi_stream_handle;
- }
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_CFG_STREAM, &s->stream_cfg, "isp start stream");
-}
-
-static void road_camera_start(CameraState *s) {
- set_exposure(s, 1.0, 1.0);
-
- int err = sensor_write_regs(s, start_reg_array, std::size(start_reg_array), MSM_CAMERA_I2C_BYTE_DATA);
- LOG("sensor start regs: %d", err);
-
- int inf_step = 512 - INFINITY_DAC;
-
- // initial guess
- s->lens_true_pos = 400;
-
- // reset lens position
- struct msm_actuator_cfg_data actuator_cfg_data = {};
- actuator_cfg_data.cfgtype = CFG_SET_POSITION;
- actuator_cfg_data.cfg.setpos = (struct msm_actuator_set_position_t){
- .number_of_steps = 1,
- .hw_params = (uint32_t)7,
- .pos = {INFINITY_DAC, 0},
- .delay = {0,}
- };
- cam_ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data, "actuator set pos");
-
- // TODO: confirm this isn't needed
- /*memset(&actuator_cfg_data, 0, sizeof(actuator_cfg_data));
- actuator_cfg_data.cfgtype = CFG_MOVE_FOCUS;
- actuator_cfg_data.cfg.move = (struct msm_actuator_move_params_t){
- .dir = 0,
- .sign_dir = 1,
- .dest_step_pos = inf_step,
- .num_steps = inf_step,
- .curr_lens_pos = 0,
- .ringing_params = &actuator_ringing_params,
- };
- err = ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data); // should be ~332 at startup ?
- LOG("init actuator move focus: %d", err);*/
- //actuator_cfg_data.cfg.move.curr_lens_pos;
-
- s->cur_lens_pos = 0;
- s->cur_step_pos = inf_step;
-
- actuator_move(s, s->cur_lens_pos);
- LOG("init lens pos: %d", s->cur_lens_pos);
-}
-
-void actuator_move(CameraState *s, uint16_t target) {
- // LP3 moves only on even positions. TODO: use proper sensor params
-
- // focus on infinity assuming phone is perpendicular
- static struct damping_params_t actuator_ringing_params = {
- .damping_step = 1023,
- .damping_delay = 20000,
- .hw_params = 13,
- };
-
- int step = (target - s->cur_lens_pos) / 2;
-
- int dest_step_pos = s->cur_step_pos + step;
- dest_step_pos = std::clamp(dest_step_pos, 0, 255);
-
- struct msm_actuator_cfg_data actuator_cfg_data = {0};
- actuator_cfg_data.cfgtype = CFG_MOVE_FOCUS;
- actuator_cfg_data.cfg.move = (struct msm_actuator_move_params_t){
- .dir = (int8_t)((step > 0) ? MOVE_NEAR : MOVE_FAR),
- .sign_dir = (int8_t)((step > 0) ? MSM_ACTUATOR_MOVE_SIGNED_NEAR : MSM_ACTUATOR_MOVE_SIGNED_FAR),
- .dest_step_pos = (int16_t)dest_step_pos,
- .num_steps = abs(step),
- .curr_lens_pos = s->cur_lens_pos,
- .ringing_params = &actuator_ringing_params,
- };
- HANDLE_EINTR(ioctl(s->actuator_fd, VIDIOC_MSM_ACTUATOR_CFG, &actuator_cfg_data));
-
- s->cur_step_pos = dest_step_pos;
- s->cur_lens_pos = actuator_cfg_data.cfg.move.curr_lens_pos;
- //LOGD("step %d target: %d lens pos: %d", dest_step_pos, target, s->cur_lens_pos);
-}
-
-static void parse_autofocus(CameraState *s, uint8_t *d) {
- int good_count = 0;
- int16_t max_focus = -32767;
- int avg_focus = 0;
-
- /*printf("FOCUS: ");
- for (int i = 0; i < 0x10; i++) {
- printf("%2.2X ", d[i]);
- }*/
-
- for (int i = 0; i < NUM_FOCUS; i++) {
- int doff = i*5+5;
- s->confidence[i] = d[doff];
- // this should just be a 10-bit signed int instead of 11
- // TODO: write it in a nicer way
- int16_t focus_t = (d[doff+1] << 3) | (d[doff+2] >> 5);
- if (focus_t >= 1024) focus_t = -(2048-focus_t);
- s->focus[i] = focus_t;
- //printf("%x->%d ", d[doff], focus_t);
- if (s->confidence[i] > 0x20) {
- good_count++;
- max_focus = std::max(max_focus, s->focus[i]);
- avg_focus += s->focus[i];
- }
- }
- // self recover override
- if (s->self_recover > 1) {
- s->focus_err = 200 * ((s->self_recover % 2 == 0) ? 1:-1); // far for even numbers, close for odd
- s->self_recover -= 2;
- return;
- }
-
- if (good_count < 4) {
- s->focus_err = nan("");
- return;
- }
-
- avg_focus /= good_count;
-
- // outlier rejection
- if (abs(avg_focus - max_focus) > 200) {
- s->focus_err = nan("");
- return;
- }
-
- s->focus_err = max_focus*1.0;
-}
-
-static void do_autofocus(CameraState *s) {
- float lens_true_pos = s->lens_true_pos.load();
- if (!isnan(s->focus_err)) {
- // learn lens_true_pos
- const float focus_kp = 0.005;
- lens_true_pos -= s->focus_err*focus_kp;
- }
-
- // stay off the walls
- lens_true_pos = std::clamp(lens_true_pos, float(LP3_AF_DAC_DOWN), float(LP3_AF_DAC_UP));
- s->lens_true_pos.store(lens_true_pos);
- actuator_move(s, lens_true_pos);
-}
-
-void camera_autoexposure(CameraState *s, float grey_frac) {
- if (s->camera_num == 0) {
- CameraExpInfo tmp = road_cam_exp.load();
- tmp.op_id++;
- tmp.grey_frac = grey_frac;
- road_cam_exp.store(tmp);
- } else {
- CameraExpInfo tmp = driver_cam_exp.load();
- tmp.op_id++;
- tmp.grey_frac = grey_frac;
- driver_cam_exp.store(tmp);
- }
-}
-
-static void driver_camera_start(CameraState *s) {
- set_exposure(s, 1.0, 1.0);
- int err = sensor_write_regs(s, start_reg_array, std::size(start_reg_array), MSM_CAMERA_I2C_BYTE_DATA);
- LOG("sensor start regs: %d", err);
-}
-
-void cameras_open(MultiCameraState *s) {
- struct msm_ispif_param_data ispif_params = {
- .num = 4,
- .entries = {
- // road camera
- {.vfe_intf = VFE0, .intftype = RDI0, .num_cids = 1, .cids[0] = CID0, .csid = CSID0},
- // driver camera
- {.vfe_intf = VFE1, .intftype = RDI0, .num_cids = 1, .cids[0] = CID0, .csid = CSID2},
- // road camera (focus)
- {.vfe_intf = VFE0, .intftype = RDI1, .num_cids = 1, .cids[0] = CID1, .csid = CSID0},
- // road camera (stats, for AE)
- {.vfe_intf = VFE0, .intftype = RDI2, .num_cids = 1, .cids[0] = CID2, .csid = CSID0},
- },
- };
- s->msmcfg_fd = HANDLE_EINTR(open("/dev/media0", O_RDWR | O_NONBLOCK));
- assert(s->msmcfg_fd >= 0);
-
- sensors_init(s);
-
- s->v4l_fd = HANDLE_EINTR(open("/dev/video0", O_RDWR | O_NONBLOCK));
- assert(s->v4l_fd >= 0);
-
- s->ispif_fd = open_v4l_by_name_and_index("msm_ispif");
- assert(s->ispif_fd >= 0);
-
- // ISPIF: stop
- // memset(&ispif_cfg_data, 0, sizeof(ispif_cfg_data));
- // ispif_cfg_data.cfg_type = ISPIF_STOP_FRAME_BOUNDARY;
- // ispif_cfg_data.params = ispif_params;
- // err = ioctl(s->ispif_fd, VIDIOC_MSM_ISPIF_CFG, &ispif_cfg_data);
- // LOG("ispif stop: %d", err);
-
- LOG("*** open driver camera ***");
- s->driver_cam.ss[0].bufs = s->driver_cam.buf.camera_bufs.get();
- camera_open(&s->driver_cam, false);
-
- LOG("*** open road camera ***");
- s->road_cam.ss[0].bufs = s->road_cam.buf.camera_bufs.get();
- s->road_cam.ss[1].bufs = s->focus_bufs;
- s->road_cam.ss[2].bufs = s->stats_bufs;
- camera_open(&s->road_cam, true);
-
- if (getenv("CAMERA_TEST")) {
- cameras_close(s);
- exit(0);
- }
-
- // ISPIF: set vfe info
- struct ispif_cfg_data ispif_cfg_data = {.cfg_type = ISPIF_SET_VFE_INFO, .vfe_info.num_vfe = 2};
- int err = HANDLE_EINTR(ioctl(s->ispif_fd, VIDIOC_MSM_ISPIF_CFG, &ispif_cfg_data));
- LOG("ispif set vfe info: %d", err);
-
- // ISPIF: setup
- ispif_cfg_data = {.cfg_type = ISPIF_INIT, .csid_version = 0x30050000 /* CSID_VERSION_V35*/};
- cam_ioctl(s->ispif_fd, VIDIOC_MSM_ISPIF_CFG, &ispif_cfg_data, "ispif setup");
-
- ispif_cfg_data = {.cfg_type = ISPIF_CFG, .params = ispif_params};
- cam_ioctl(s->ispif_fd, VIDIOC_MSM_ISPIF_CFG, &ispif_cfg_data, "ispif cfg");
-
- ispif_cfg_data.cfg_type = ISPIF_START_FRAME_BOUNDARY;
- cam_ioctl(s->ispif_fd, VIDIOC_MSM_ISPIF_CFG, &ispif_cfg_data, "ispif start_frame_boundary");
-
- driver_camera_start(&s->driver_cam);
- road_camera_start(&s->road_cam);
-}
-
-
-static void camera_close(CameraState *s) {
- // ISP: STOP_STREAM
- s->stream_cfg.cmd = STOP_STREAM;
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_CFG_STREAM, &s->stream_cfg, "isp stop stream");
-
- for (int i = 0; i < 3; i++) {
- StreamState *ss = &s->ss[i];
- if (ss->stream_req.axi_stream_handle != 0) {
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_RELEASE_BUF, &ss->buf_request, "isp release buf");
-
- struct msm_vfe_axi_stream_release_cmd stream_release = {
- .stream_handle = ss->stream_req.axi_stream_handle,
- };
- cam_ioctl(s->isp_fd, VIDIOC_MSM_ISP_RELEASE_STREAM, &stream_release, "isp release stream");
- }
- }
-}
-
-const char* get_isp_event_name(uint32_t type) {
- switch (type) {
- case ISP_EVENT_REG_UPDATE: return "ISP_EVENT_REG_UPDATE";
- case ISP_EVENT_EPOCH_0: return "ISP_EVENT_EPOCH_0";
- case ISP_EVENT_EPOCH_1: return "ISP_EVENT_EPOCH_1";
- case ISP_EVENT_START_ACK: return "ISP_EVENT_START_ACK";
- case ISP_EVENT_STOP_ACK: return "ISP_EVENT_STOP_ACK";
- case ISP_EVENT_IRQ_VIOLATION: return "ISP_EVENT_IRQ_VIOLATION";
- case ISP_EVENT_STATS_OVERFLOW: return "ISP_EVENT_STATS_OVERFLOW";
- case ISP_EVENT_ERROR: return "ISP_EVENT_ERROR";
- case ISP_EVENT_SOF: return "ISP_EVENT_SOF";
- case ISP_EVENT_EOF: return "ISP_EVENT_EOF";
- case ISP_EVENT_BUF_DONE: return "ISP_EVENT_BUF_DONE";
- case ISP_EVENT_BUF_DIVERT: return "ISP_EVENT_BUF_DIVERT";
- case ISP_EVENT_STATS_NOTIFY: return "ISP_EVENT_STATS_NOTIFY";
- case ISP_EVENT_COMP_STATS_NOTIFY: return "ISP_EVENT_COMP_STATS_NOTIFY";
- case ISP_EVENT_FE_READ_DONE: return "ISP_EVENT_FE_READ_DONE";
- case ISP_EVENT_IOMMU_P_FAULT: return "ISP_EVENT_IOMMU_P_FAULT";
- case ISP_EVENT_HW_FATAL_ERROR: return "ISP_EVENT_HW_FATAL_ERROR";
- case ISP_EVENT_PING_PONG_MISMATCH: return "ISP_EVENT_PING_PONG_MISMATCH";
- case ISP_EVENT_REG_UPDATE_MISSING: return "ISP_EVENT_REG_UPDATE_MISSING";
- case ISP_EVENT_BUF_FATAL_ERROR: return "ISP_EVENT_BUF_FATAL_ERROR";
- case ISP_EVENT_STREAM_UPDATE_DONE: return "ISP_EVENT_STREAM_UPDATE_DONE";
- default: return "unknown";
- }
-}
-
-static FrameMetadata get_frame_metadata(CameraState *s, uint32_t frame_id) {
- std::lock_guard lk(s->frame_info_lock);
- for (auto &i : s->frame_metadata) {
- if (i.frame_id == frame_id) {
- return i;
- }
- }
- // should never happen
- return (FrameMetadata){
- .frame_id = (uint32_t)-1,
- };
-}
-
-static void ops_thread(MultiCameraState *s) {
- int last_road_cam_op_id = 0;
- int last_driver_cam_op_id = 0;
-
- CameraExpInfo road_cam_op;
- CameraExpInfo driver_cam_op;
-
- util::set_thread_name("camera_settings");
- while(!do_exit) {
- road_cam_op = road_cam_exp.load();
- if (road_cam_op.op_id != last_road_cam_op_id) {
- do_autoexposure(&s->road_cam, road_cam_op.grey_frac);
- do_autofocus(&s->road_cam);
- last_road_cam_op_id = road_cam_op.op_id;
- }
-
- driver_cam_op = driver_cam_exp.load();
- if (driver_cam_op.op_id != last_driver_cam_op_id) {
- do_autoexposure(&s->driver_cam, driver_cam_op.grey_frac);
- last_driver_cam_op_id = driver_cam_op.op_id;
- }
-
- util::sleep_for(50);
- }
-}
-
-static void setup_self_recover(CameraState *c, const uint16_t *lapres, size_t lapres_size) {
- const float lens_true_pos = c->lens_true_pos.load();
- int self_recover = c->self_recover.load();
- if (self_recover < 2 && (lens_true_pos < (LP3_AF_DAC_DOWN + 1) || lens_true_pos > (LP3_AF_DAC_UP - 1)) && is_blur(lapres, lapres_size)) {
- // truly stuck, needs help
- if (--self_recover < -FOCUS_RECOVER_PATIENCE) {
- LOGD("road camera bad state detected. attempting recovery from %.1f, recover state is %d", lens_true_pos, self_recover);
- // parity determined by which end is stuck at
- self_recover = FOCUS_RECOVER_STEPS + (lens_true_pos < LP3_AF_DAC_M ? 1 : 0);
- }
- } else if (self_recover < 2 && (lens_true_pos < (LP3_AF_DAC_M - LP3_AF_DAC_3SIG) || lens_true_pos > (LP3_AF_DAC_M + LP3_AF_DAC_3SIG))) {
- // in suboptimal position with high prob, but may still recover by itself
- if (--self_recover < -(FOCUS_RECOVER_PATIENCE * 3)) {
- self_recover = FOCUS_RECOVER_STEPS / 2 + (lens_true_pos < LP3_AF_DAC_M ? 1 : 0);
- }
- } else if (self_recover < 0) {
- self_recover += 1; // reset if fine
- }
- c->self_recover.store(self_recover);
-}
-
-// called by processing_thread
-void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
- const CameraBuf *b = &c->buf;
- const int roi_id = cnt % std::size(s->lapres); // rolling roi
- s->lapres[roi_id] = s->lap_conv->Update(b->q, (uint8_t *)b->cur_rgb_buf->addr, roi_id);
- setup_self_recover(c, &s->lapres[0], std::size(s->lapres));
-
- MessageBuilder msg;
- auto framed = msg.initEvent().initRoadCameraState();
- fill_frame_data(framed, b->cur_frame_data);
- if (env_send_road) {
- framed.setImage(get_frame_image(b));
- }
- framed.setFocusVal(s->road_cam.focus);
- framed.setFocusConf(s->road_cam.confidence);
- framed.setRecoverState(s->road_cam.self_recover);
- framed.setSharpnessScore(s->lapres);
- framed.setTransform(b->yuv_transform.v);
- s->pm->send("roadCameraState", msg);
-
- if (cnt % 3 == 0) {
- const int x = 290, y = 322, width = 560, height = 314;
- const int skip = 1;
- camera_autoexposure(c, set_exposure_target(b, x, x + width, skip, y, y + height, skip));
- }
-}
-
-void cameras_run(MultiCameraState *s) {
- std::vector threads;
- threads.push_back(std::thread(ops_thread, s));
- threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera));
- threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera));
-
- CameraState* cameras[2] = {&s->road_cam, &s->driver_cam};
-
- while (!do_exit) {
- struct pollfd fds[2] = {{.fd = cameras[0]->isp_fd, .events = POLLPRI},
- {.fd = cameras[1]->isp_fd, .events = POLLPRI}};
- int ret = poll(fds, std::size(fds), 1000);
- if (ret < 0) {
- if (errno == EINTR || errno == EAGAIN) continue;
- LOGE("poll failed (%d - %d)", ret, errno);
- break;
- }
-
- // process cameras
- for (int i=0; i<2; i++) {
- if (!fds[i].revents) continue;
-
- CameraState *c = cameras[i];
-
- struct v4l2_event ev = {};
- ret = HANDLE_EINTR(ioctl(c->isp_fd, VIDIOC_DQEVENT, &ev));
- const msm_isp_event_data *isp_event_data = (const msm_isp_event_data *)ev.u.data;
-
- if (ev.type == ISP_EVENT_BUF_DIVERT) {
- const int buf_idx = isp_event_data->u.buf_done.buf_idx;
- const int buffer = (isp_event_data->u.buf_done.stream_id & 0xFFFF) - 1;
- if (buffer == 0) {
- c->buf.camera_bufs_metadata[buf_idx] = get_frame_metadata(c, isp_event_data->frame_id);
- c->buf.queue(buf_idx);
- } else {
- auto &ss = c->ss[buffer];
- if (buffer == 1) {
- parse_autofocus(c, (uint8_t *)(ss.bufs[buf_idx].addr));
- }
- ss.qbuf_info[buf_idx].dirty_buf = 1;
- HANDLE_EINTR(ioctl(c->isp_fd, VIDIOC_MSM_ISP_ENQUEUE_BUF, &ss.qbuf_info[buf_idx]));
- }
-
- } else if (ev.type == ISP_EVENT_EOF) {
- const uint64_t timestamp = (isp_event_data->mono_timestamp.tv_sec * 1000000000ULL + isp_event_data->mono_timestamp.tv_usec * 1000);
- std::lock_guard lk(c->frame_info_lock);
- c->frame_metadata[c->frame_metadata_idx] = (FrameMetadata){
- .frame_id = isp_event_data->frame_id,
- .timestamp_eof = timestamp,
- .frame_length = (uint32_t)c->frame_length,
- .integ_lines = (uint32_t)c->cur_integ_lines,
- .lens_pos = c->cur_lens_pos,
- .lens_err = c->focus_err,
- .lens_true_pos = c->lens_true_pos,
- .gain = c->cur_gain_frac,
- .measured_grey_fraction = c->measured_grey_fraction,
- .target_grey_fraction = c->target_grey_fraction,
- .high_conversion_gain = false,
- };
- c->frame_metadata_idx = (c->frame_metadata_idx + 1) % METADATA_BUF_COUNT;
-
- } else if (ev.type == ISP_EVENT_ERROR) {
- LOGE("ISP_EVENT_ERROR! err type: 0x%08x", isp_event_data->u.error_info.err_type);
- }
- }
- }
-
- LOG(" ************** STOPPING **************");
-
- for (auto &t : threads) t.join();
-
- cameras_close(s);
-}
-
-void cameras_close(MultiCameraState *s) {
- camera_close(&s->road_cam);
- camera_close(&s->driver_cam);
- for (int i = 0; i < FRAME_BUF_COUNT; i++) {
- s->focus_bufs[i].free();
- s->stats_bufs[i].free();
- }
-
- delete s->lap_conv;
- delete s->sm;
- delete s->pm;
-}
diff --git a/selfdrive/camerad/cameras/camera_qcom.h b/selfdrive/camerad/cameras/camera_qcom.h
deleted file mode 100644
index 87bbe4b7e7..0000000000
--- a/selfdrive/camerad/cameras/camera_qcom.h
+++ /dev/null
@@ -1,106 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-#include "cereal/messaging/messaging.h"
-#include "cereal/visionipc/visionbuf.h"
-#include "selfdrive/camerad/cameras/camera_common.h"
-#include "selfdrive/camerad/imgproc/utils.h"
-#include "selfdrive/camerad/include/msm_cam_sensor.h"
-#include "selfdrive/camerad/include/msmb_camera.h"
-#include "selfdrive/camerad/include/msmb_isp.h"
-#include "selfdrive/camerad/include/msmb_ispif.h"
-#include "selfdrive/common/mat.h"
-#include "selfdrive/common/util.h"
-
-#define FRAME_BUF_COUNT 4
-#define METADATA_BUF_COUNT 4
-
-#define NUM_FOCUS 8
-
-#define LP3_AF_DAC_DOWN 366
-#define LP3_AF_DAC_UP 634
-#define LP3_AF_DAC_M 440
-#define LP3_AF_DAC_3SIG 52
-
-#define FOCUS_RECOVER_PATIENCE 50 // 2.5 seconds of complete blur
-#define FOCUS_RECOVER_STEPS 240 // 6 seconds
-
-typedef struct CameraState CameraState;
-
-typedef int (*camera_apply_exposure_func)(CameraState *s, int gain, int integ_lines, uint32_t frame_length);
-
-typedef struct StreamState {
- struct msm_isp_buf_request buf_request;
- struct msm_vfe_axi_stream_request_cmd stream_req;
- struct msm_isp_qbuf_info qbuf_info[FRAME_BUF_COUNT];
- VisionBuf *bufs;
-} StreamState;
-
-typedef struct CameraState {
- int camera_num;
- int camera_id;
-
- int fps;
- CameraInfo ci;
-
- unique_fd csid_fd;
- unique_fd csiphy_fd;
- unique_fd sensor_fd;
- unique_fd isp_fd;
-
- struct msm_vfe_axi_stream_cfg_cmd stream_cfg;
-
- StreamState ss[3];
- CameraBuf buf;
-
- std::mutex frame_info_lock;
- FrameMetadata frame_metadata[METADATA_BUF_COUNT];
- int frame_metadata_idx;
-
- // exposure
- uint32_t pixel_clock, line_length_pclk;
- uint32_t frame_length;
- unsigned int max_gain;
- float cur_exposure_frac, cur_gain_frac;
- int cur_gain, cur_integ_lines;
-
- float measured_grey_fraction;
- float target_grey_fraction;
-
- std::atomic digital_gain;
- camera_apply_exposure_func apply_exposure;
-
- // rear camera only,used for focusing
- unique_fd actuator_fd;
- std::atomic focus_err;
- std::atomic lens_true_pos;
- std::atomic self_recover; // af recovery counter, neg is patience, pos is active
- uint16_t cur_step_pos;
- uint16_t cur_lens_pos;
- int16_t focus[NUM_FOCUS];
- uint8_t confidence[NUM_FOCUS];
-} CameraState;
-
-
-typedef struct MultiCameraState {
- unique_fd ispif_fd;
- unique_fd msmcfg_fd;
- unique_fd v4l_fd;
- uint16_t lapres[(ROI_X_MAX-ROI_X_MIN+1)*(ROI_Y_MAX-ROI_Y_MIN+1)];
-
- VisionBuf focus_bufs[FRAME_BUF_COUNT];
- VisionBuf stats_bufs[FRAME_BUF_COUNT];
-
- CameraState road_cam;
- CameraState driver_cam;
-
- SubMaster *sm;
- PubMaster *pm;
- LapConv *lap_conv;
-} MultiCameraState;
-
-void actuator_move(CameraState *s, uint16_t target);
-int sensor_write_regs(CameraState *s, struct msm_camera_i2c_reg_array* arr, size_t size, int data_type);
diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc
deleted file mode 100644
index f75e982be2..0000000000
--- a/selfdrive/camerad/cameras/camera_qcom2.cc
+++ /dev/null
@@ -1,1111 +0,0 @@
-#include "selfdrive/camerad/cameras/camera_qcom2.h"
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "media/cam_defs.h"
-#include "media/cam_isp.h"
-#include "media/cam_isp_ife.h"
-#include "media/cam_sensor.h"
-#include "media/cam_sensor_cmn_header.h"
-#include "media/cam_sync.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/camerad/cameras/sensor2_i2c.h"
-
-extern ExitHandler do_exit;
-
-const size_t FRAME_WIDTH = 1928;
-const size_t FRAME_HEIGHT = 1208;
-const size_t FRAME_STRIDE = 2416; // for 10 bit output
-
-const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py
-
-CameraInfo cameras_supported[CAMERA_ID_MAX] = {
- [CAMERA_ID_AR0231] = {
- .frame_width = FRAME_WIDTH,
- .frame_height = FRAME_HEIGHT,
- .frame_stride = FRAME_STRIDE,
- .bayer = true,
- .bayer_flip = 1,
- .hdr = false
- },
-};
-
-const float DC_GAIN = 2.5;
-const float sensor_analog_gains[] = {
- 1.0/8.0, 2.0/8.0, 2.0/7.0, 3.0/7.0, // 0, 1, 2, 3
- 3.0/6.0, 4.0/6.0, 4.0/5.0, 5.0/5.0, // 4, 5, 6, 7
- 5.0/4.0, 6.0/4.0, 6.0/3.0, 7.0/3.0, // 8, 9, 10, 11
- 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass
-
-const int ANALOG_GAIN_MIN_IDX = 0x1; // 0.25x
-const int ANALOG_GAIN_REC_IDX = 0x6; // 0.8x
-const int ANALOG_GAIN_MAX_IDX = 0xD; // 4.0x
-
-const int EXPOSURE_TIME_MIN = 2; // with HDR, fastest ss
-const int EXPOSURE_TIME_MAX = 1904; // with HDR, slowest ss
-
-// ************** low level camera helpers ****************
-int cam_control(int fd, int op_code, void *handle, int size) {
- struct cam_control camcontrol = {0};
- camcontrol.op_code = op_code;
- camcontrol.handle = (uint64_t)handle;
- if (size == 0) {
- camcontrol.size = 8;
- camcontrol.handle_type = CAM_HANDLE_MEM_HANDLE;
- } else {
- camcontrol.size = size;
- camcontrol.handle_type = CAM_HANDLE_USER_POINTER;
- }
-
- int ret = HANDLE_EINTR(ioctl(fd, VIDIOC_CAM_CONTROL, &camcontrol));
- if (ret == -1) {
- printf("OP CODE ERR - %d \n", op_code);
- perror("wat");
- }
- return ret;
-}
-
-std::optional device_acquire(int fd, int32_t session_handle, void *data) {
- struct cam_acquire_dev_cmd cmd = {
- .session_handle = session_handle,
- .handle_type = CAM_HANDLE_USER_POINTER,
- .num_resources = (uint32_t)(data ? 1 : 0),
- .resource_hdl = (uint64_t)data,
- };
- int err = cam_control(fd, CAM_ACQUIRE_DEV, &cmd, sizeof(cmd));
- return err == 0 ? std::make_optional(cmd.dev_handle) : std::nullopt;
-};
-
-int device_config(int fd, int32_t session_handle, int32_t dev_handle, uint64_t packet_handle) {
- struct cam_config_dev_cmd cmd = {
- .session_handle = session_handle,
- .dev_handle = dev_handle,
- .packet_handle = packet_handle,
- };
- return cam_control(fd, CAM_CONFIG_DEV, &cmd, sizeof(cmd));
-}
-
-int device_control(int fd, int op_code, int session_handle, int dev_handle) {
- // start stop and release are all the same
- struct cam_start_stop_dev_cmd cmd { .session_handle = session_handle, .dev_handle = dev_handle };
- return cam_control(fd, op_code, &cmd, sizeof(cmd));
-}
-
-void *alloc_w_mmu_hdl(int video0_fd, int len, uint32_t *handle, int align = 8, int flags = CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE,
- int mmu_hdl = 0, int mmu_hdl2 = 0) {
- struct cam_mem_mgr_alloc_cmd mem_mgr_alloc_cmd = {0};
- mem_mgr_alloc_cmd.len = len;
- mem_mgr_alloc_cmd.align = align;
- mem_mgr_alloc_cmd.flags = flags;
- mem_mgr_alloc_cmd.num_hdl = 0;
- if (mmu_hdl != 0) {
- mem_mgr_alloc_cmd.mmu_hdls[0] = mmu_hdl;
- mem_mgr_alloc_cmd.num_hdl++;
- }
- if (mmu_hdl2 != 0) {
- mem_mgr_alloc_cmd.mmu_hdls[1] = mmu_hdl2;
- mem_mgr_alloc_cmd.num_hdl++;
- }
-
- cam_control(video0_fd, CAM_REQ_MGR_ALLOC_BUF, &mem_mgr_alloc_cmd, sizeof(mem_mgr_alloc_cmd));
- *handle = mem_mgr_alloc_cmd.out.buf_handle;
-
- void *ptr = NULL;
- if (mem_mgr_alloc_cmd.out.fd > 0) {
- ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, mem_mgr_alloc_cmd.out.fd, 0);
- assert(ptr != MAP_FAILED);
- }
-
- // LOGD("allocated: %x %d %llx mapped %p", mem_mgr_alloc_cmd.out.buf_handle, mem_mgr_alloc_cmd.out.fd, mem_mgr_alloc_cmd.out.vaddr, ptr);
-
- return ptr;
-}
-
-void release(int video0_fd, uint32_t handle) {
- int ret;
- struct cam_mem_mgr_release_cmd mem_mgr_release_cmd = {0};
- mem_mgr_release_cmd.buf_handle = handle;
-
- ret = cam_control(video0_fd, CAM_REQ_MGR_RELEASE_BUF, &mem_mgr_release_cmd, sizeof(mem_mgr_release_cmd));
- assert(ret == 0);
-}
-
-void release_fd(int video0_fd, uint32_t handle) {
- // handle to fd
- close(handle>>16);
- release(video0_fd, handle);
-}
-
-void clear_req_queue(int fd, int32_t session_hdl, int32_t link_hdl) {
- struct cam_req_mgr_flush_info req_mgr_flush_request = {0};
- req_mgr_flush_request.session_hdl = session_hdl;
- req_mgr_flush_request.link_hdl = link_hdl;
- req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL;
- int ret;
- ret = cam_control(fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request));
- // LOGD("flushed all req: %d", ret);
-}
-
-// ************** high level camera helpers ****************
-
-void sensors_poke(struct CameraState *s, int request_id) {
- uint32_t cam_packet_handle = 0;
- int size = sizeof(struct cam_packet);
- struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle);
- pkt->num_cmd_buf = 0;
- pkt->kmd_cmd_buf_index = -1;
- pkt->header.size = size;
- pkt->header.op_code = 0x7f;
- pkt->header.request_id = request_id;
-
- int ret = device_config(s->sensor_fd, s->session_handle, s->sensor_dev_handle, cam_packet_handle);
- assert(ret == 0);
-
- munmap(pkt, size);
- release_fd(s->multi_cam_state->video0_fd, cam_packet_handle);
-}
-
-void sensors_i2c(struct CameraState *s, struct i2c_random_wr_payload* dat, int len, int op_code) {
- // LOGD("sensors_i2c: %d", len);
- uint32_t cam_packet_handle = 0;
- int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1;
- struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle);
- pkt->num_cmd_buf = 1;
- pkt->kmd_cmd_buf_index = -1;
- pkt->header.size = size;
- pkt->header.op_code = op_code;
- struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload;
-
- buf_desc[0].size = buf_desc[0].length = sizeof(struct i2c_rdwr_header) + len*sizeof(struct i2c_random_wr_payload);
- buf_desc[0].type = CAM_CMD_BUF_I2C;
-
- struct cam_cmd_i2c_random_wr *i2c_random_wr = (struct cam_cmd_i2c_random_wr *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle);
- i2c_random_wr->header.count = len;
- i2c_random_wr->header.op_code = 1;
- i2c_random_wr->header.cmd_type = CAMERA_SENSOR_CMD_TYPE_I2C_RNDM_WR;
- i2c_random_wr->header.data_type = CAMERA_SENSOR_I2C_TYPE_WORD;
- i2c_random_wr->header.addr_type = CAMERA_SENSOR_I2C_TYPE_WORD;
- memcpy(i2c_random_wr->random_wr_payload, dat, len*sizeof(struct i2c_random_wr_payload));
-
- int ret = device_config(s->sensor_fd, s->session_handle, s->sensor_dev_handle, cam_packet_handle);
- assert(ret == 0);
-
- munmap(i2c_random_wr, buf_desc[0].size);
- release_fd(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle);
- munmap(pkt, size);
- release_fd(s->multi_cam_state->video0_fd, cam_packet_handle);
-}
-static cam_cmd_power *power_set_wait(cam_cmd_power *power, int16_t delay_ms) {
- cam_cmd_unconditional_wait *unconditional_wait = (cam_cmd_unconditional_wait *)((char *)power + (sizeof(struct cam_cmd_power) + (power->count - 1) * sizeof(struct cam_power_settings)));
- unconditional_wait->cmd_type = CAMERA_SENSOR_CMD_TYPE_WAIT;
- unconditional_wait->delay = delay_ms;
- unconditional_wait->op_code = CAMERA_SENSOR_WAIT_OP_SW_UCND;
- return (struct cam_cmd_power *)(unconditional_wait + 1);
-};
-
-void sensors_init(int video0_fd, int sensor_fd, int camera_num) {
- uint32_t cam_packet_handle = 0;
- int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2;
- struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(video0_fd, size, &cam_packet_handle);
- pkt->num_cmd_buf = 2;
- pkt->kmd_cmd_buf_index = -1;
- pkt->header.op_code = 0x1000003;
- pkt->header.size = size;
- struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload;
-
- buf_desc[0].size = buf_desc[0].length = sizeof(struct cam_cmd_i2c_info) + sizeof(struct cam_cmd_probe);
- buf_desc[0].type = CAM_CMD_BUF_LEGACY;
- struct cam_cmd_i2c_info *i2c_info = (struct cam_cmd_i2c_info *)alloc_w_mmu_hdl(video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle);
- auto probe = (struct cam_cmd_probe *)(i2c_info + 1);
-
- switch (camera_num) {
- case 0:
- // port 0
- i2c_info->slave_addr = 0x20;
- probe->camera_id = 0;
- break;
- case 1:
- // port 1
- i2c_info->slave_addr = 0x30;
- probe->camera_id = 1;
- break;
- case 2:
- // port 2
- i2c_info->slave_addr = 0x20;
- probe->camera_id = 2;
- break;
- }
-
- // 0(I2C_STANDARD_MODE) = 100khz, 1(I2C_FAST_MODE) = 400khz
- //i2c_info->i2c_freq_mode = I2C_STANDARD_MODE;
- i2c_info->i2c_freq_mode = I2C_FAST_MODE;
- i2c_info->cmd_type = CAMERA_SENSOR_CMD_TYPE_I2C_INFO;
-
- probe->data_type = CAMERA_SENSOR_I2C_TYPE_WORD;
- probe->addr_type = CAMERA_SENSOR_I2C_TYPE_WORD;
- probe->op_code = 3; // don't care?
- probe->cmd_type = CAMERA_SENSOR_CMD_TYPE_PROBE;
- probe->reg_addr = 0x3000; //0x300a; //0x300b;
- probe->expected_data = 0x354; //0x7750; //0x885a;
- probe->data_mask = 0;
-
- //buf_desc[1].size = buf_desc[1].length = 148;
- buf_desc[1].size = buf_desc[1].length = 196;
- buf_desc[1].type = CAM_CMD_BUF_I2C;
- struct cam_cmd_power *power_settings = (struct cam_cmd_power *)alloc_w_mmu_hdl(video0_fd, buf_desc[1].size, (uint32_t*)&buf_desc[1].mem_handle);
- memset(power_settings, 0, buf_desc[1].size);
- // 7750
- /*power->count = 2;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP;
- power->power_settings[0].power_seq_type = 2;
- power->power_settings[1].power_seq_type = 8;
- power = (void*)power + (sizeof(struct cam_cmd_power) + (power->count-1)*sizeof(struct cam_power_settings));*/
-
- // 885a
- struct cam_cmd_power *power = power_settings;
- power->count = 4;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP;
- power->power_settings[0].power_seq_type = 3; // clock??
- power->power_settings[1].power_seq_type = 1; // analog
- power->power_settings[2].power_seq_type = 2; // digital
- power->power_settings[3].power_seq_type = 8; // reset low
- power = power_set_wait(power, 5);
-
- // set clock
- power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP;
- power->power_settings[0].power_seq_type = 0;
- power->power_settings[0].config_val_low = 19200000; //Hz
- power = power_set_wait(power, 10);
-
- // 8,1 is this reset?
- power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP;
- power->power_settings[0].power_seq_type = 8;
- power->power_settings[0].config_val_low = 1;
- power = power_set_wait(power, 100);
-
- // probe happens here
-
- // disable clock
- power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN;
- power->power_settings[0].power_seq_type = 0;
- power->power_settings[0].config_val_low = 0;
- power = power_set_wait(power, 1);
-
- // reset high
- power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN;
- power->power_settings[0].power_seq_type = 8;
- power->power_settings[0].config_val_low = 1;
- power = power_set_wait(power, 1);
-
- // reset low
- power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN;
- power->power_settings[0].power_seq_type = 8;
- power->power_settings[0].config_val_low = 0;
- power = power_set_wait(power, 1);
-
- // 7750
- /*power->count = 1;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN;
- power->power_settings[0].power_seq_type = 2;
- power = (void*)power + (sizeof(struct cam_cmd_power) + (power->count-1)*sizeof(struct cam_power_settings));*/
-
- // 885a
- power->count = 3;
- power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_DOWN;
- power->power_settings[0].power_seq_type = 2;
- power->power_settings[1].power_seq_type = 1;
- power->power_settings[2].power_seq_type = 3;
-
- LOGD("probing the sensor");
- int ret = cam_control(sensor_fd, CAM_SENSOR_PROBE_CMD, (void *)(uintptr_t)cam_packet_handle, 0);
- assert(ret == 0);
-
- munmap(i2c_info, buf_desc[0].size);
- release_fd(video0_fd, buf_desc[0].mem_handle);
- munmap(power_settings, buf_desc[1].size);
- release_fd(video0_fd, buf_desc[1].mem_handle);
- munmap(pkt, size);
- release_fd(video0_fd, cam_packet_handle);
-}
-
-void config_isp(struct CameraState *s, int io_mem_handle, int fence, int request_id, int buf0_mem_handle, int buf0_offset) {
- uint32_t cam_packet_handle = 0;
- int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2;
- if (io_mem_handle != 0) {
- size += sizeof(struct cam_buf_io_cfg);
- }
- struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle);
- pkt->num_cmd_buf = 2;
- pkt->kmd_cmd_buf_index = 0;
- // YUV has kmd_cmd_buf_offset = 1780
- // I guess this is the ISP command
- // YUV also has patch_offset = 0x1030 and num_patches = 10
-
- if (io_mem_handle != 0) {
- pkt->io_configs_offset = sizeof(struct cam_cmd_buf_desc)*2;
- pkt->num_io_configs = 1;
- }
-
- if (io_mem_handle != 0) {
- pkt->header.op_code = 0xf000001;
- pkt->header.request_id = request_id;
- } else {
- pkt->header.op_code = 0xf000000;
- }
- pkt->header.size = size;
- struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload;
- struct cam_buf_io_cfg *io_cfg = (struct cam_buf_io_cfg *)((char*)&pkt->payload + pkt->io_configs_offset);
-
- // TODO: support MMU
- buf_desc[0].size = 65624;
- buf_desc[0].length = 0;
- buf_desc[0].type = CAM_CMD_BUF_DIRECT;
- buf_desc[0].meta_data = 3;
- buf_desc[0].mem_handle = buf0_mem_handle;
- buf_desc[0].offset = buf0_offset;
-
- // parsed by cam_isp_packet_generic_blob_handler
- struct isp_packet {
- uint32_t type_0;
- cam_isp_resource_hfr_config resource_hfr;
-
- uint32_t type_1;
- cam_isp_clock_config clock;
- uint64_t extra_rdi_hz[3];
-
- uint32_t type_2;
- cam_isp_bw_config bw;
- struct cam_isp_bw_vote extra_rdi_vote[6];
- } __attribute__((packed)) tmp;
- memset(&tmp, 0, sizeof(tmp));
-
- tmp.type_0 = CAM_ISP_GENERIC_BLOB_TYPE_HFR_CONFIG;
- tmp.type_0 |= sizeof(cam_isp_resource_hfr_config) << 8;
- static_assert(sizeof(cam_isp_resource_hfr_config) == 0x20);
- tmp.resource_hfr = {
- .num_ports = 1, // 10 for YUV (but I don't think we need them)
- .port_hfr_config[0] = {
- .resource_type = CAM_ISP_IFE_OUT_RES_RDI_0, // CAM_ISP_IFE_OUT_RES_FULL for YUV
- .subsample_pattern = 1,
- .subsample_period = 0,
- .framedrop_pattern = 1,
- .framedrop_period = 0,
- }};
-
- tmp.type_1 = CAM_ISP_GENERIC_BLOB_TYPE_CLOCK_CONFIG;
- tmp.type_1 |= (sizeof(cam_isp_clock_config) + sizeof(tmp.extra_rdi_hz)) << 8;
- static_assert((sizeof(cam_isp_clock_config) + sizeof(tmp.extra_rdi_hz)) == 0x38);
- tmp.clock = {
- .usage_type = 1, // dual mode
- .num_rdi = 4,
- .left_pix_hz = 404000000,
- .right_pix_hz = 404000000,
- .rdi_hz[0] = 404000000,
- };
-
-
- tmp.type_2 = CAM_ISP_GENERIC_BLOB_TYPE_BW_CONFIG;
- tmp.type_2 |= (sizeof(cam_isp_bw_config) + sizeof(tmp.extra_rdi_vote)) << 8;
- static_assert((sizeof(cam_isp_bw_config) + sizeof(tmp.extra_rdi_vote)) == 0xe0);
- tmp.bw = {
- .usage_type = 1, // dual mode
- .num_rdi = 4,
- .left_pix_vote = {
- .resource_id = 0,
- .cam_bw_bps = 450000000,
- .ext_bw_bps = 450000000,
- },
- .rdi_vote[0] = {
- .resource_id = 0,
- .cam_bw_bps = 8706200000,
- .ext_bw_bps = 8706200000,
- },
- };
-
- static_assert(offsetof(struct isp_packet, type_2) == 0x60);
-
- buf_desc[1].size = sizeof(tmp);
- buf_desc[1].offset = io_mem_handle != 0 ? 0x60 : 0;
- buf_desc[1].length = buf_desc[1].size - buf_desc[1].offset;
- buf_desc[1].type = CAM_CMD_BUF_GENERIC;
- buf_desc[1].meta_data = CAM_ISP_PACKET_META_GENERIC_BLOB_COMMON;
- uint32_t *buf2 = (uint32_t *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[1].size, (uint32_t*)&buf_desc[1].mem_handle, 0x20);
- memcpy(buf2, &tmp, sizeof(tmp));
-
- if (io_mem_handle != 0) {
- io_cfg[0].mem_handle[0] = io_mem_handle;
- io_cfg[0].planes[0] = (struct cam_plane_cfg){
- .width = FRAME_WIDTH,
- .height = FRAME_HEIGHT,
- .plane_stride = FRAME_STRIDE,
- .slice_height = FRAME_HEIGHT,
- .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000)
- .meta_size = 0x0,
- .meta_offset = 0x0,
- .packer_config = 0x0, // 0xb for YUV
- .mode_config = 0x0, // 0x9ef for YUV
- .tile_config = 0x0,
- .h_init = 0x0,
- .v_init = 0x0,
- };
- io_cfg[0].format = CAM_FORMAT_MIPI_RAW_10; // CAM_FORMAT_UBWC_TP10 for YUV
- io_cfg[0].color_space = CAM_COLOR_SPACE_BASE; // CAM_COLOR_SPACE_BT601_FULL for YUV
- io_cfg[0].color_pattern = 0x5; // 0x0 for YUV
- io_cfg[0].bpp = 0xa;
- io_cfg[0].resource_type = CAM_ISP_IFE_OUT_RES_RDI_0; // CAM_ISP_IFE_OUT_RES_FULL for YUV
- io_cfg[0].fence = fence;
- io_cfg[0].direction = CAM_BUF_OUTPUT;
- io_cfg[0].subsample_pattern = 0x1;
- io_cfg[0].framedrop_pattern = 0x1;
- }
-
- int ret = device_config(s->multi_cam_state->isp_fd, s->session_handle, s->isp_dev_handle, cam_packet_handle);
- assert(ret == 0);
- if (ret != 0) {
- printf("ISP CONFIG FAILED\n");
- }
-
- munmap(buf2, buf_desc[1].size);
- release_fd(s->multi_cam_state->video0_fd, buf_desc[1].mem_handle);
- // release_fd(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle);
- munmap(pkt, size);
- release_fd(s->multi_cam_state->video0_fd, cam_packet_handle);
-}
-
-void enqueue_buffer(struct CameraState *s, int i, bool dp) {
- int ret;
- int request_id = s->request_ids[i];
-
- if (s->buf_handle[i]) {
- release(s->multi_cam_state->video0_fd, s->buf_handle[i]);
- // wait
- struct cam_sync_wait sync_wait = {0};
- sync_wait.sync_obj = s->sync_objs[i];
- sync_wait.timeout_ms = 50; // max dt tolerance, typical should be 23
- ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait));
- // LOGD("fence wait: %d %d", ret, sync_wait.sync_obj);
-
- s->buf.camera_bufs_metadata[i].timestamp_eof = (uint64_t)nanos_since_boot(); // set true eof
- if (dp) s->buf.queue(i);
-
- // destroy old output fence
- struct cam_sync_info sync_destroy = {0};
- strcpy(sync_destroy.name, "NodeOutputPortFence");
- sync_destroy.sync_obj = s->sync_objs[i];
- ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_DESTROY, &sync_destroy, sizeof(sync_destroy));
- // LOGD("fence destroy: %d %d", ret, sync_destroy.sync_obj);
- }
-
- // do stuff
- struct cam_req_mgr_sched_request req_mgr_sched_request = {0};
- req_mgr_sched_request.session_hdl = s->session_handle;
- req_mgr_sched_request.link_hdl = s->link_handle;
- req_mgr_sched_request.req_id = request_id;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_SCHED_REQ, &req_mgr_sched_request, sizeof(req_mgr_sched_request));
- // LOGD("sched req: %d %d", ret, request_id);
-
- // create output fence
- struct cam_sync_info sync_create = {0};
- strcpy(sync_create.name, "NodeOutputPortFence");
- ret = cam_control(s->multi_cam_state->video1_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create));
- // LOGD("fence req: %d %d", ret, sync_create.sync_obj);
- s->sync_objs[i] = sync_create.sync_obj;
-
- // configure ISP to put the image in place
- struct cam_mem_mgr_map_cmd mem_mgr_map_cmd = {0};
- mem_mgr_map_cmd.mmu_hdls[0] = s->multi_cam_state->device_iommu;
- mem_mgr_map_cmd.num_hdl = 1;
- mem_mgr_map_cmd.flags = CAM_MEM_FLAG_HW_READ_WRITE;
- mem_mgr_map_cmd.fd = s->buf.camera_bufs[i].fd;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_MAP_BUF, &mem_mgr_map_cmd, sizeof(mem_mgr_map_cmd));
- // LOGD("map buf req: (fd: %d) 0x%x %d", s->bufs[i].fd, mem_mgr_map_cmd.out.buf_handle, ret);
- s->buf_handle[i] = mem_mgr_map_cmd.out.buf_handle;
-
- // poke sensor
- sensors_poke(s, request_id);
- // LOGD("Poked sensor");
-
- // push the buffer
- config_isp(s, s->buf_handle[i], s->sync_objs[i], request_id, s->buf0_handle, 65632*(i+1));
-}
-
-void enqueue_req_multi(struct CameraState *s, int start, int n, bool dp) {
- for (int i=start;irequest_ids[(i - 1) % FRAME_BUF_COUNT] = i;
- enqueue_buffer(s, (i - 1) % FRAME_BUF_COUNT, dp);
- }
-}
-
-// ******************* camera *******************
-
-static void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, CameraState *s, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type) {
- LOGD("camera init %d", camera_num);
- s->multi_cam_state = multi_cam_state;
- assert(camera_id < std::size(cameras_supported));
- s->ci = cameras_supported[camera_id];
- assert(s->ci.frame_width != 0);
-
- s->camera_num = camera_num;
-
- s->request_id_last = 0;
- s->skipped = true;
-
- s->min_ev = EXPOSURE_TIME_MIN * sensor_analog_gains[ANALOG_GAIN_MIN_IDX];
- s->max_ev = EXPOSURE_TIME_MAX * sensor_analog_gains[ANALOG_GAIN_MAX_IDX] * DC_GAIN;
- s->target_grey_fraction = 0.3;
-
- s->dc_gain_enabled = false;
- s->gain_idx = ANALOG_GAIN_REC_IDX;
- s->exposure_time = 5;
- s->cur_ev[0] = s->cur_ev[1] = s->cur_ev[2] = (s->dc_gain_enabled ? DC_GAIN : 1) * sensor_analog_gains[s->gain_idx] * s->exposure_time;
-
- s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type);
-}
-
-static void camera_open(CameraState *s) {
- s->sensor_fd = open_v4l_by_name_and_index("cam-sensor-driver", s->camera_num);
- assert(s->sensor_fd >= 0);
- LOGD("opened sensor for %d", s->camera_num);
-
- // probe the sensor
- LOGD("-- Probing sensor %d", s->camera_num);
- sensors_init(s->multi_cam_state->video0_fd, s->sensor_fd, s->camera_num);
-
- // create session
- struct cam_req_mgr_session_info session_info = {};
- int ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info));
- LOGD("get session: %d 0x%X", ret, session_info.session_hdl);
- s->session_handle = session_info.session_hdl;
-
- // access the sensor
- LOGD("-- Accessing sensor");
- auto sensor_dev_handle = device_acquire(s->sensor_fd, s->session_handle, nullptr);
- assert(sensor_dev_handle);
- s->sensor_dev_handle = *sensor_dev_handle;
- LOGD("acquire sensor dev");
-
- struct cam_isp_in_port_info in_port_info = {
- .res_type = (uint32_t[]){CAM_ISP_IFE_IN_RES_PHY_0, CAM_ISP_IFE_IN_RES_PHY_1, CAM_ISP_IFE_IN_RES_PHY_2}[s->camera_num],
-
- .lane_type = CAM_ISP_LANE_TYPE_DPHY,
- .lane_num = 4,
- .lane_cfg = 0x3210,
-
- .vc = 0x0,
- // .dt = 0x2C; //CSI_RAW12
- .dt = 0x2B, //CSI_RAW10
- .format = CAM_FORMAT_MIPI_RAW_10,
-
- .test_pattern = 0x2, // 0x3?
- .usage_type = 0x0,
-
- .left_start = 0,
- .left_stop = FRAME_WIDTH - 1,
- .left_width = FRAME_WIDTH,
-
- .right_start = 0,
- .right_stop = FRAME_WIDTH - 1,
- .right_width = FRAME_WIDTH,
-
- .line_start = 0,
- .line_stop = FRAME_HEIGHT - 1,
- .height = FRAME_HEIGHT,
-
- .pixel_clk = 0x0,
- .batch_size = 0x0,
- .dsp_mode = CAM_ISP_DSP_MODE_NONE,
- .hbi_cnt = 0x0,
- .custom_csid = 0x0,
-
- .num_out_res = 0x1,
- .data[0] = (struct cam_isp_out_port_info){
- .res_type = CAM_ISP_IFE_OUT_RES_RDI_0,
- .format = CAM_FORMAT_MIPI_RAW_10,
- .width = FRAME_WIDTH,
- .height = FRAME_HEIGHT,
- .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0,
- },
- };
- struct cam_isp_resource isp_resource = {
- .resource_id = CAM_ISP_RES_ID_PORT,
- .handle_type = CAM_HANDLE_USER_POINTER,
- .res_hdl = (uint64_t)&in_port_info,
- .length = sizeof(in_port_info),
- };
-
- auto isp_dev_handle = device_acquire(s->multi_cam_state->isp_fd, s->session_handle, &isp_resource);
- assert(isp_dev_handle);
- s->isp_dev_handle = *isp_dev_handle;
- LOGD("acquire isp dev");
-
- s->csiphy_fd = open_v4l_by_name_and_index("cam-csiphy-driver", s->camera_num);
- assert(s->csiphy_fd >= 0);
- LOGD("opened csiphy for %d", s->camera_num);
-
- struct cam_csiphy_acquire_dev_info csiphy_acquire_dev_info = {.combo_mode = 0};
- auto csiphy_dev_handle = device_acquire(s->csiphy_fd, s->session_handle, &csiphy_acquire_dev_info);
- assert(csiphy_dev_handle);
- s->csiphy_dev_handle = *csiphy_dev_handle;
- LOGD("acquire csiphy dev");
-
- // config ISP
- alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, 984480, (uint32_t*)&s->buf0_handle, 0x20, CAM_MEM_FLAG_HW_READ_WRITE | CAM_MEM_FLAG_KMD_ACCESS | CAM_MEM_FLAG_UMD_ACCESS | CAM_MEM_FLAG_CMD_BUF_TYPE, s->multi_cam_state->device_iommu, s->multi_cam_state->cdm_iommu);
- config_isp(s, 0, 0, 1, s->buf0_handle, 0);
-
- LOG("-- Configuring sensor");
- sensors_i2c(s, init_array_ar0231, std::size(init_array_ar0231), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG);
- //sensors_i2c(s, start_reg_array, std::size(start_reg_array), CAM_SENSOR_PACKET_OPCODE_SENSOR_STREAMON);
- //sensors_i2c(s, stop_reg_array, std::size(stop_reg_array), CAM_SENSOR_PACKET_OPCODE_SENSOR_STREAMOFF);
-
-
- // config csiphy
- LOG("-- Config CSI PHY");
- {
- uint32_t cam_packet_handle = 0;
- int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1;
- struct cam_packet *pkt = (struct cam_packet *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, size, &cam_packet_handle);
- pkt->num_cmd_buf = 1;
- pkt->kmd_cmd_buf_index = -1;
- pkt->header.size = size;
- struct cam_cmd_buf_desc *buf_desc = (struct cam_cmd_buf_desc *)&pkt->payload;
-
- buf_desc[0].size = buf_desc[0].length = sizeof(struct cam_csiphy_info);
- buf_desc[0].type = CAM_CMD_BUF_GENERIC;
-
- struct cam_csiphy_info *csiphy_info = (struct cam_csiphy_info *)alloc_w_mmu_hdl(s->multi_cam_state->video0_fd, buf_desc[0].size, (uint32_t*)&buf_desc[0].mem_handle);
- csiphy_info->lane_mask = 0x1f;
- csiphy_info->lane_assign = 0x3210;// skip clk. How is this 16 bit for 5 channels??
- csiphy_info->csiphy_3phase = 0x0; // no 3 phase, only 2 conductors per lane
- csiphy_info->combo_mode = 0x0;
- csiphy_info->lane_cnt = 0x4;
- csiphy_info->secure_mode = 0x0;
- csiphy_info->settle_time = MIPI_SETTLE_CNT * 200000000ULL;
- csiphy_info->data_rate = 48000000; // Calculated by camera_freqs.py
-
- int ret_ = device_config(s->csiphy_fd, s->session_handle, s->csiphy_dev_handle, cam_packet_handle);
- assert(ret_ == 0);
-
- munmap(csiphy_info, buf_desc[0].size);
- release_fd(s->multi_cam_state->video0_fd, buf_desc[0].mem_handle);
- munmap(pkt, size);
- release_fd(s->multi_cam_state->video0_fd, cam_packet_handle);
- }
-
- // link devices
- LOG("-- Link devices");
- struct cam_req_mgr_link_info req_mgr_link_info = {0};
- req_mgr_link_info.session_hdl = s->session_handle;
- req_mgr_link_info.num_devices = 2;
- req_mgr_link_info.dev_hdls[0] = s->isp_dev_handle;
- req_mgr_link_info.dev_hdls[1] = s->sensor_dev_handle;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK, &req_mgr_link_info, sizeof(req_mgr_link_info));
- s->link_handle = req_mgr_link_info.link_hdl;
- LOGD("link: %d hdl: 0x%X", ret, s->link_handle);
-
- struct cam_req_mgr_link_control req_mgr_link_control = {0};
- req_mgr_link_control.ops = CAM_REQ_MGR_LINK_ACTIVATE;
- req_mgr_link_control.session_hdl = s->session_handle;
- req_mgr_link_control.num_links = 1;
- req_mgr_link_control.link_hdls[0] = s->link_handle;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK_CONTROL, &req_mgr_link_control, sizeof(req_mgr_link_control));
- LOGD("link control: %d", ret);
-
- ret = device_control(s->csiphy_fd, CAM_START_DEV, s->session_handle, s->csiphy_dev_handle);
- LOGD("start csiphy: %d", ret);
- ret = device_control(s->multi_cam_state->isp_fd, CAM_START_DEV, s->session_handle, s->isp_dev_handle);
- LOGD("start isp: %d", ret);
- ret = device_control(s->sensor_fd, CAM_START_DEV, s->session_handle, s->sensor_dev_handle);
- LOGD("start sensor: %d", ret);
-
- enqueue_req_multi(s, 1, FRAME_BUF_COUNT, 0);
-}
-
-void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) {
- camera_init(s, v, &s->driver_cam, CAMERA_ID_AR0231, 2, 20, device_id, ctx,
- VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER);
- printf("driver camera initted \n");
- if (!env_only_driver) {
- camera_init(s, v, &s->road_cam, CAMERA_ID_AR0231, 1, 20, device_id, ctx,
- VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); // swap left/right
- printf("road camera initted \n");
- camera_init(s, v, &s->wide_road_cam, CAMERA_ID_AR0231, 0, 20, device_id, ctx,
- VISION_STREAM_RGB_WIDE, VISION_STREAM_WIDE_ROAD);
- printf("wide road camera initted \n");
- }
-
- s->sm = new SubMaster({"driverState"});
- s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"});
-}
-
-void cameras_open(MultiCameraState *s) {
- int ret;
-
- LOG("-- Opening devices");
- // video0 is req_mgr, the target of many ioctls
- s->video0_fd = HANDLE_EINTR(open("/dev/v4l/by-path/platform-soc:qcom_cam-req-mgr-video-index0", O_RDWR | O_NONBLOCK));
- assert(s->video0_fd >= 0);
- LOGD("opened video0");
-
- // video1 is cam_sync, the target of some ioctls
- s->video1_fd = HANDLE_EINTR(open("/dev/v4l/by-path/platform-cam_sync-video-index0", O_RDWR | O_NONBLOCK));
- assert(s->video1_fd >= 0);
- LOGD("opened video1");
-
- // looks like there's only one of these
- s->isp_fd = HANDLE_EINTR(open("/dev/v4l-subdev1", O_RDWR | O_NONBLOCK));
- assert(s->isp_fd >= 0);
- LOGD("opened isp");
-
- // query icp for MMU handles
- LOG("-- Query ICP for MMU handles");
- static struct cam_isp_query_cap_cmd isp_query_cap_cmd = {0};
- static struct cam_query_cap_cmd query_cap_cmd = {0};
- query_cap_cmd.handle_type = 1;
- query_cap_cmd.caps_handle = (uint64_t)&isp_query_cap_cmd;
- query_cap_cmd.size = sizeof(isp_query_cap_cmd);
- ret = cam_control(s->isp_fd, CAM_QUERY_CAP, &query_cap_cmd, sizeof(query_cap_cmd));
- assert(ret == 0);
- LOGD("using MMU handle: %x", isp_query_cap_cmd.device_iommu.non_secure);
- LOGD("using MMU handle: %x", isp_query_cap_cmd.cdm_iommu.non_secure);
- s->device_iommu = isp_query_cap_cmd.device_iommu.non_secure;
- s->cdm_iommu = isp_query_cap_cmd.cdm_iommu.non_secure;
-
- // subscribe
- LOG("-- Subscribing");
- static struct v4l2_event_subscription sub = {0};
- sub.type = V4L_EVENT_CAM_REQ_MGR_EVENT;
- sub.id = 2; // should use boot time for sof
- ret = HANDLE_EINTR(ioctl(s->video0_fd, VIDIOC_SUBSCRIBE_EVENT, &sub));
- printf("req mgr subscribe: %d\n", ret);
-
- camera_open(&s->driver_cam);
- printf("driver camera opened \n");
- if (!env_only_driver) {
- camera_open(&s->road_cam);
- printf("road camera opened \n");
- camera_open(&s->wide_road_cam);
- printf("wide road camera opened \n");
- }
-}
-
-static void camera_close(CameraState *s) {
- int ret;
-
- // stop devices
- LOG("-- Stop devices");
- // ret = device_control(s->sensor_fd, CAM_STOP_DEV, s->session_handle, s->sensor_dev_handle);
- // LOGD("stop sensor: %d", ret);
- ret = device_control(s->multi_cam_state->isp_fd, CAM_STOP_DEV, s->session_handle, s->isp_dev_handle);
- LOGD("stop isp: %d", ret);
- ret = device_control(s->csiphy_fd, CAM_STOP_DEV, s->session_handle, s->csiphy_dev_handle);
- LOGD("stop csiphy: %d", ret);
- // link control stop
- LOG("-- Stop link control");
- static struct cam_req_mgr_link_control req_mgr_link_control = {0};
- req_mgr_link_control.ops = CAM_REQ_MGR_LINK_DEACTIVATE;
- req_mgr_link_control.session_hdl = s->session_handle;
- req_mgr_link_control.num_links = 1;
- req_mgr_link_control.link_hdls[0] = s->link_handle;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_LINK_CONTROL, &req_mgr_link_control, sizeof(req_mgr_link_control));
- LOGD("link control stop: %d", ret);
-
- // unlink
- LOG("-- Unlink");
- static struct cam_req_mgr_unlink_info req_mgr_unlink_info = {0};
- req_mgr_unlink_info.session_hdl = s->session_handle;
- req_mgr_unlink_info.link_hdl = s->link_handle;
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_UNLINK, &req_mgr_unlink_info, sizeof(req_mgr_unlink_info));
- LOGD("unlink: %d", ret);
-
- // release devices
- LOGD("-- Release devices");
- ret = device_control(s->sensor_fd, CAM_RELEASE_DEV, s->session_handle, s->sensor_dev_handle);
- LOGD("release sensor: %d", ret);
- ret = device_control(s->multi_cam_state->isp_fd, CAM_RELEASE_DEV, s->session_handle, s->isp_dev_handle);
- LOGD("release isp: %d", ret);
- ret = device_control(s->csiphy_fd, CAM_RELEASE_DEV, s->session_handle, s->csiphy_dev_handle);
- LOGD("release csiphy: %d", ret);
-
- // destroyed session
- struct cam_req_mgr_session_info session_info = {.session_hdl = s->session_handle};
- ret = cam_control(s->multi_cam_state->video0_fd, CAM_REQ_MGR_DESTROY_SESSION, &session_info, sizeof(session_info));
- LOGD("destroyed session: %d", ret);
-}
-
-void cameras_close(MultiCameraState *s) {
- camera_close(&s->driver_cam);
- if (!env_only_driver) {
- camera_close(&s->road_cam);
- camera_close(&s->wide_road_cam);
- }
-
- delete s->sm;
- delete s->pm;
-}
-
-// ******************* just a helper *******************
-
-void handle_camera_event(CameraState *s, void *evdat) {
- struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)evdat;
-
- uint64_t timestamp = event_data->u.frame_msg.timestamp;
- int main_id = event_data->u.frame_msg.frame_id;
- int real_id = event_data->u.frame_msg.request_id;
-
- if (real_id != 0) { // next ready
- if (real_id == 1) {s->idx_offset = main_id;}
- int buf_idx = (real_id - 1) % FRAME_BUF_COUNT;
-
- // check for skipped frames
- if (main_id > s->frame_id_last + 1 && !s->skipped) {
- // realign
- clear_req_queue(s->multi_cam_state->video0_fd, event_data->session_hdl, event_data->u.frame_msg.link_hdl);
- enqueue_req_multi(s, real_id + 1, FRAME_BUF_COUNT - 1, 0);
- s->skipped = true;
- } else if (main_id == s->frame_id_last + 1) {
- s->skipped = false;
- }
-
- // check for dropped requests
- if (real_id > s->request_id_last + 1) {
- enqueue_req_multi(s, s->request_id_last + 1 + FRAME_BUF_COUNT, real_id - (s->request_id_last + 1), 0);
- }
-
- // metas
- s->frame_id_last = main_id;
- s->request_id_last = real_id;
-
- auto &meta_data = s->buf.camera_bufs_metadata[buf_idx];
- meta_data.frame_id = main_id - s->idx_offset;
- meta_data.timestamp_sof = timestamp;
- s->exp_lock.lock();
- meta_data.gain = s->dc_gain_enabled ? s->analog_gain_frac * DC_GAIN : s->analog_gain_frac;
- meta_data.high_conversion_gain = s->dc_gain_enabled;
- meta_data.integ_lines = s->exposure_time;
- meta_data.measured_grey_fraction = s->measured_grey_fraction;
- meta_data.target_grey_fraction = s->target_grey_fraction;
- s->exp_lock.unlock();
-
- // dispatch
- enqueue_req_multi(s, real_id + FRAME_BUF_COUNT, 1, 1);
- } else { // not ready
- // reset after half second of no response
- if (main_id > s->frame_id_last + 10) {
- clear_req_queue(s->multi_cam_state->video0_fd, event_data->session_hdl, event_data->u.frame_msg.link_hdl);
- enqueue_req_multi(s, s->request_id_last + 1, FRAME_BUF_COUNT, 0);
- s->frame_id_last = main_id;
- s->skipped = true;
- }
- }
-}
-
-static void set_camera_exposure(CameraState *s, float grey_frac) {
- const float dt = 0.05;
-
- const float ts_grey = 10.0;
- const float ts_ev = 0.05;
-
- const float k_grey = (dt / ts_grey) / (1.0 + dt / ts_grey);
- const float k_ev = (dt / ts_ev) / (1.0 + dt / ts_ev);
-
- // It takes 3 frames for the commanded exposure settings to take effect. The first frame is already started by the time
- // we reach this function, the other 2 are due to the register buffering in the sensor.
- // Therefore we use the target EV from 3 frames ago, the grey fraction that was just measured was the result of that control action.
- // TODO: Lower latency to 2 frames, by using the histogram outputed by the sensor we can do AE before the debayering is complete
-
- const float cur_ev = s->cur_ev[s->buf.cur_frame_data.frame_id % 3];
-
- // Scale target grey between 0.1 and 0.4 depending on lighting conditions
- float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + cur_ev) / log2(6000.0), 0.1, 0.4);
- float target_grey = (1.0 - k_grey) * s->target_grey_fraction + k_grey * new_target_grey;
-
- float desired_ev = std::clamp(cur_ev * target_grey / grey_frac, s->min_ev, s->max_ev);
- float k = (1.0 - k_ev) / 3.0;
- desired_ev = (k * s->cur_ev[0]) + (k * s->cur_ev[1]) + (k * s->cur_ev[2]) + (k_ev * desired_ev);
-
- float best_ev_score = 1e6;
- int new_g = 0;
- int new_t = 0;
-
- // Hysteresis around high conversion gain
- // We usually want this on since it results in lower noise, but turn off in very bright day scenes
- bool enable_dc_gain = s->dc_gain_enabled;
- if (!enable_dc_gain && target_grey < 0.2) {
- enable_dc_gain = true;
- } else if (enable_dc_gain && target_grey > 0.3) {
- enable_dc_gain = false;
- }
-
- // Simple brute force optimizer to choose sensor parameters
- // to reach desired EV
- for (int g = std::max((int)ANALOG_GAIN_MIN_IDX, s->gain_idx - 1); g <= std::min((int)ANALOG_GAIN_MAX_IDX, s->gain_idx + 1); g++) {
- float gain = sensor_analog_gains[g] * (enable_dc_gain ? DC_GAIN : 1);
-
- // Compute optimal time for given gain
- int t = std::clamp(int(std::round(desired_ev / gain)), EXPOSURE_TIME_MIN, EXPOSURE_TIME_MAX);
-
- // Only go below recomended gain when absolutely necessary to not overexpose
- if (g < ANALOG_GAIN_REC_IDX && t > 20 && g < s->gain_idx) {
- continue;
- }
-
- // Compute error to desired ev
- float score = std::abs(desired_ev - (t * gain)) * 10;
-
- // Going below recomended gain needs lower penalty to not overexpose
- float m = g > ANALOG_GAIN_REC_IDX ? 5.0 : 0.1;
- score += std::abs(g - (int)ANALOG_GAIN_REC_IDX) * m;
-
- // LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", s->camera_num, g, t, desired_ev / gain, score, score + std::abs(g - s->gain_idx) * (score + 1.0) / 10.0, desired_ev, s->min_ev);
-
- // Small penalty on changing gain
- score += std::abs(g - s->gain_idx) * (score + 1.0) / 10.0;
-
- if (score < best_ev_score) {
- new_t = t;
- new_g = g;
- best_ev_score = score;
- }
- }
-
- s->exp_lock.lock();
-
- s->measured_grey_fraction = grey_frac;
- s->target_grey_fraction = target_grey;
-
- s->analog_gain_frac = sensor_analog_gains[new_g];
- s->gain_idx = new_g;
- s->exposure_time = new_t;
- s->dc_gain_enabled = enable_dc_gain;
-
- float gain = s->analog_gain_frac * (s->dc_gain_enabled ? DC_GAIN : 1.0);
- s->cur_ev[s->buf.cur_frame_data.frame_id % 3] = s->exposure_time * gain;
-
- s->exp_lock.unlock();
-
- // Processing a frame takes right about 50ms, so we need to wait a few ms
- // so we don't send i2c commands around the frame start.
- int ms = (nanos_since_boot() - s->buf.cur_frame_data.timestamp_sof) / 1000000;
- if (ms < 60) {
- util::sleep_for(60 - ms);
- }
- // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", s->camera_num, 1e-9 * nanos_since_boot(), 1e-9 * s->buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - s->buf.cur_frame_data.timestamp_sof));
-
- uint16_t analog_gain_reg = 0xFF00 | (new_g << 4) | new_g;
- struct i2c_random_wr_payload exp_reg_array[] = {
- {0x3366, analog_gain_reg},
- {0x3362, (uint16_t)(s->dc_gain_enabled ? 0x1 : 0x0)},
- {0x3012, (uint16_t)s->exposure_time},
- };
- sensors_i2c(s, exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload),
- CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG);
-
-}
-
-void camera_autoexposure(CameraState *s, float grey_frac) {
- set_camera_exposure(s, grey_frac);
-}
-
-// called by processing_thread
-void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
- const CameraBuf *b = &c->buf;
-
- MessageBuilder msg;
- auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState();
- fill_frame_data(framed, b->cur_frame_data);
- if ((c == &s->road_cam && env_send_road) || (c == &s->wide_road_cam && env_send_wide_road)) {
- framed.setImage(get_frame_image(b));
- }
- if (c == &s->road_cam) {
- framed.setTransform(b->yuv_transform.v);
- }
- s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg);
-
- const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986);
- const int skip = 2;
- camera_autoexposure(c, set_exposure_target(b, x, x + w, skip, y, y + h, skip));
-}
-
-void cameras_run(MultiCameraState *s) {
- LOG("-- Starting threads");
- std::vector threads;
- threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera));
- if (!env_only_driver) {
- threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera));
- threads.push_back(start_process_thread(s, &s->wide_road_cam, process_road_camera));
- }
-
- // start devices
- LOG("-- Starting devices");
- int start_reg_len = sizeof(start_reg_array) / sizeof(struct i2c_random_wr_payload);
- sensors_i2c(&s->driver_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG);
- if (!env_only_driver) {
- sensors_i2c(&s->road_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG);
- sensors_i2c(&s->wide_road_cam, start_reg_array, start_reg_len, CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG);
- }
-
- // poll events
- LOG("-- Dequeueing Video events");
- while (!do_exit) {
- struct pollfd fds[1] = {{0}};
-
- fds[0].fd = s->video0_fd;
- fds[0].events = POLLPRI;
-
- int ret = poll(fds, std::size(fds), 1000);
- if (ret < 0) {
- if (errno == EINTR || errno == EAGAIN) continue;
- LOGE("poll failed (%d - %d)", ret, errno);
- break;
- }
-
- if (!fds[0].revents) continue;
-
- struct v4l2_event ev = {0};
- ret = HANDLE_EINTR(ioctl(fds[0].fd, VIDIOC_DQEVENT, &ev));
- if (ret == 0) {
- if (ev.type == V4L_EVENT_CAM_REQ_MGR_EVENT) {
- struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)ev.u.data;
- // LOGD("v4l2 event: sess_hdl 0x%X, link_hdl 0x%X, frame_id %d, req_id %lld, timestamp 0x%llx, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp, event_data->u.frame_msg.sof_status);
- if (env_debug_frames) {
- printf("sess_hdl 0x%X, link_hdl 0x%X, frame_id %lu, req_id %lu, timestamp 0x%lx, sof_status %d\n", event_data->session_hdl, event_data->u.frame_msg.link_hdl, event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp, event_data->u.frame_msg.sof_status);
- }
-
- if (event_data->session_hdl == s->road_cam.session_handle) {
- handle_camera_event(&s->road_cam, event_data);
- } else if (event_data->session_hdl == s->wide_road_cam.session_handle) {
- handle_camera_event(&s->wide_road_cam, event_data);
- } else if (event_data->session_hdl == s->driver_cam.session_handle) {
- handle_camera_event(&s->driver_cam, event_data);
- } else {
- printf("Unknown vidioc event source\n");
- assert(false);
- }
- }
- } else {
- LOGE("VIDIOC_DQEVENT failed, errno=%d", errno);
- }
- }
-
- LOG(" ************** STOPPING **************");
-
- for (auto &t : threads) t.join();
-
- cameras_close(s);
-}
diff --git a/selfdrive/camerad/cameras/camera_qcom2.h b/selfdrive/camerad/cameras/camera_qcom2.h
deleted file mode 100644
index f8ab2da8ae..0000000000
--- a/selfdrive/camerad/cameras/camera_qcom2.h
+++ /dev/null
@@ -1,67 +0,0 @@
-#pragma once
-
-#include
-
-#include
-
-#include "selfdrive/camerad/cameras/camera_common.h"
-#include "selfdrive/common/util.h"
-
-#define FRAME_BUF_COUNT 4
-
-typedef struct CameraState {
- MultiCameraState *multi_cam_state;
- CameraInfo ci;
-
- std::mutex exp_lock;
-
- int exposure_time;
- bool dc_gain_enabled;
- float analog_gain_frac;
-
- float cur_ev[3];
- float min_ev, max_ev;
-
- float measured_grey_fraction;
- float target_grey_fraction;
- int gain_idx;
-
- unique_fd sensor_fd;
- unique_fd csiphy_fd;
-
- int camera_num;
-
- int32_t session_handle;
- int32_t sensor_dev_handle;
- int32_t isp_dev_handle;
- int32_t csiphy_dev_handle;
-
- int32_t link_handle;
-
- int buf0_handle;
- int buf_handle[FRAME_BUF_COUNT];
- int sync_objs[FRAME_BUF_COUNT];
- int request_ids[FRAME_BUF_COUNT];
- int request_id_last;
- int frame_id_last;
- int idx_offset;
- bool skipped;
-
- CameraBuf buf;
-} CameraState;
-
-typedef struct MultiCameraState {
- unique_fd video0_fd;
- unique_fd video1_fd;
- unique_fd isp_fd;
- int device_iommu;
- int cdm_iommu;
-
-
- CameraState road_cam;
- CameraState wide_road_cam;
- CameraState driver_cam;
-
- SubMaster *sm;
- PubMaster *pm;
-} MultiCameraState;
diff --git a/selfdrive/camerad/cameras/camera_replay.cc b/selfdrive/camerad/cameras/camera_replay.cc
deleted file mode 100644
index b5b2e6ad29..0000000000
--- a/selfdrive/camerad/cameras/camera_replay.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-#include "selfdrive/camerad/cameras/camera_replay.h"
-
-#include
-#include
-
-#include "selfdrive/common/clutil.h"
-#include "selfdrive/common/util.h"
-
-extern ExitHandler do_exit;
-
-void camera_autoexposure(CameraState *s, float grey_frac) {}
-
-namespace {
-
-const char *BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/";
-
-const std::string road_camera_route = "0c94aa1e1296d7c6|2021-05-05--19-48-37";
-// const std::string driver_camera_route = "534ccd8a0950a00c|2021-06-08--12-15-37";
-
-std::string get_url(std::string route_name, const std::string &camera, int segment_num) {
- std::replace(route_name.begin(), route_name.end(), '|', '/');
- return util::string_format("%s%s/%d/%s.hevc", BASE_URL, route_name.c_str(), segment_num, camera.c_str());
-}
-
-void camera_init(VisionIpcServer *v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, const std::string &url) {
- s->frame = new FrameReader();
- if (!s->frame->load(url)) {
- printf("failed to load stream from %s", url.c_str());
- assert(0);
- }
-
- CameraInfo ci = {
- .frame_width = s->frame->width,
- .frame_height = s->frame->height,
- .frame_stride = s->frame->width * 3,
- };
- s->ci = ci;
- s->camera_num = camera_id;
- s->fps = fps;
- s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type);
-}
-
-void camera_close(CameraState *s) {
- delete s->frame;
-}
-
-void run_camera(CameraState *s) {
- uint32_t stream_frame_id = 0, frame_id = 0;
- size_t buf_idx = 0;
- std::unique_ptr rgb_buf = std::make_unique(s->frame->getRGBSize());
- std::unique_ptr yuv_buf = std::make_unique(s->frame->getYUVSize());
- while (!do_exit) {
- if (stream_frame_id == s->frame->getFrameCount()) {
- // loop stream
- stream_frame_id = 0;
- }
- if (s->frame->get(stream_frame_id++, rgb_buf.get(), yuv_buf.get())) {
- s->buf.camera_bufs_metadata[buf_idx] = {.frame_id = frame_id};
- auto &buf = s->buf.camera_bufs[buf_idx];
- CL_CHECK(clEnqueueWriteBuffer(buf.copy_q, buf.buf_cl, CL_TRUE, 0, s->frame->getRGBSize(), rgb_buf.get(), 0, NULL, NULL));
- s->buf.queue(buf_idx);
- ++frame_id;
- buf_idx = (buf_idx + 1) % FRAME_BUF_COUNT;
- }
- util::sleep_for(1000 / s->fps);
- }
-}
-
-void road_camera_thread(CameraState *s) {
- util::set_thread_name("replay_road_camera_thread");
- run_camera(s);
-}
-
-// void driver_camera_thread(CameraState *s) {
-// util::set_thread_name("replay_driver_camera_thread");
-// run_camera(s);
-// }
-
-void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
- const CameraBuf *b = &c->buf;
- MessageBuilder msg;
- auto framed = msg.initEvent().initRoadCameraState();
- fill_frame_data(framed, b->cur_frame_data);
- framed.setImage(kj::arrayPtr((const uint8_t *)b->cur_yuv_buf->addr, b->cur_yuv_buf->len));
- framed.setTransform(b->yuv_transform.v);
- s->pm->send("roadCameraState", msg);
-}
-
-// void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) {
-// MessageBuilder msg;
-// auto framed = msg.initEvent().initDriverCameraState();
-// framed.setFrameType(cereal::FrameData::FrameType::FRONT);
-// fill_frame_data(framed, c->buf.cur_frame_data);
-// s->pm->send("driverCameraState", msg);
-// }
-
-} // namespace
-
-void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) {
- camera_init(v, &s->road_cam, CAMERA_ID_LGC920, 20, device_id, ctx,
- VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD, get_url(road_camera_route, "fcamera", 0));
- // camera_init(v, &s->driver_cam, CAMERA_ID_LGC615, 10, device_id, ctx,
- // VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER, get_url(driver_camera_route, "dcamera", 0));
- s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"});
-}
-
-void cameras_open(MultiCameraState *s) {}
-
-void cameras_close(MultiCameraState *s) {
- camera_close(&s->road_cam);
- camera_close(&s->driver_cam);
- delete s->pm;
-}
-
-void cameras_run(MultiCameraState *s) {
- std::vector threads;
- threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera));
- // threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera));
- // threads.push_back(std::thread(driver_camera_thread, &s->driver_cam));
- road_camera_thread(&s->road_cam);
-
- for (auto &t : threads) t.join();
-
- cameras_close(s);
-}
diff --git a/selfdrive/camerad/cameras/camera_replay.h b/selfdrive/camerad/cameras/camera_replay.h
deleted file mode 100644
index 7c41af0ab4..0000000000
--- a/selfdrive/camerad/cameras/camera_replay.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-#include "selfdrive/camerad/cameras/camera_common.h"
-#include "selfdrive/ui/replay/framereader.h"
-
-#define FRAME_BUF_COUNT 16
-
-typedef struct CameraState {
- int camera_num;
- CameraInfo ci;
-
- int fps;
- float digital_gain = 0;
-
- CameraBuf buf;
- FrameReader *frame = nullptr;
-} CameraState;
-
-typedef struct MultiCameraState {
- CameraState road_cam;
- CameraState driver_cam;
-
- SubMaster *sm = nullptr;
- PubMaster *pm = nullptr;
-} MultiCameraState;
diff --git a/selfdrive/camerad/cameras/camera_webcam.cc b/selfdrive/camerad/cameras/camera_webcam.cc
deleted file mode 100644
index 956f2dc88f..0000000000
--- a/selfdrive/camerad/cameras/camera_webcam.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-#include "selfdrive/camerad/cameras/camera_webcam.h"
-
-#include
-
-#include
-#include
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wundefined-inline"
-#include
-#include
-#include
-#include
-#pragma clang diagnostic pop
-
-#include "selfdrive/common/clutil.h"
-#include "selfdrive/common/swaglog.h"
-#include "selfdrive/common/timing.h"
-#include "selfdrive/common/util.h"
-
-// id of the video capturing device
-const int ROAD_CAMERA_ID = util::getenv("ROADCAM_ID", 1);
-const int DRIVER_CAMERA_ID = util::getenv("DRIVERCAM_ID", 2);
-
-#define FRAME_WIDTH 1164
-#define FRAME_HEIGHT 874
-#define FRAME_WIDTH_FRONT 1152
-#define FRAME_HEIGHT_FRONT 864
-
-extern ExitHandler do_exit;
-
-namespace {
-
-CameraInfo cameras_supported[CAMERA_ID_MAX] = {
- // road facing
- [CAMERA_ID_LGC920] = {
- .frame_width = FRAME_WIDTH,
- .frame_height = FRAME_HEIGHT,
- .frame_stride = FRAME_WIDTH*3,
- .bayer = false,
- .bayer_flip = false,
- },
- // driver facing
- [CAMERA_ID_LGC615] = {
- .frame_width = FRAME_WIDTH_FRONT,
- .frame_height = FRAME_HEIGHT_FRONT,
- .frame_stride = FRAME_WIDTH_FRONT*3,
- .bayer = false,
- .bayer_flip = false,
- },
-};
-
-void camera_open(CameraState *s, bool rear) {
- // empty
-}
-
-void camera_close(CameraState *s) {
- // empty
-}
-
-void camera_init(VisionIpcServer * v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type) {
- assert(camera_id < std::size(cameras_supported));
- s->ci = cameras_supported[camera_id];
- assert(s->ci.frame_width != 0);
-
- s->camera_num = camera_id;
- s->fps = fps;
- s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type);
-}
-
-void run_camera(CameraState *s, cv::VideoCapture &video_cap, float *ts) {
- assert(video_cap.isOpened());
-
- cv::Size size(s->ci.frame_width, s->ci.frame_height);
- const cv::Mat transform = cv::Mat(3, 3, CV_32F, ts);
- uint32_t frame_id = 0;
- size_t buf_idx = 0;
-
- while (!do_exit) {
- cv::Mat frame_mat, transformed_mat;
- video_cap >> frame_mat;
- if (frame_mat.empty()) continue;
-
- cv::warpPerspective(frame_mat, transformed_mat, transform, size, cv::INTER_LINEAR, cv::BORDER_CONSTANT, 0);
-
- s->buf.camera_bufs_metadata[buf_idx] = {.frame_id = frame_id};
-
- auto &buf = s->buf.camera_bufs[buf_idx];
- int transformed_size = transformed_mat.total() * transformed_mat.elemSize();
- CL_CHECK(clEnqueueWriteBuffer(buf.copy_q, buf.buf_cl, CL_TRUE, 0, transformed_size, transformed_mat.data, 0, NULL, NULL));
-
- s->buf.queue(buf_idx);
-
- ++frame_id;
- buf_idx = (buf_idx + 1) % FRAME_BUF_COUNT;
- }
-}
-
-static void road_camera_thread(CameraState *s) {
- util::set_thread_name("webcam_road_camera_thread");
-
- cv::VideoCapture cap_road(ROAD_CAMERA_ID, cv::CAP_V4L2); // road
- cap_road.set(cv::CAP_PROP_FRAME_WIDTH, 853);
- cap_road.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
- cap_road.set(cv::CAP_PROP_FPS, s->fps);
- cap_road.set(cv::CAP_PROP_AUTOFOCUS, 0); // off
- cap_road.set(cv::CAP_PROP_FOCUS, 0); // 0 - 255?
- // cv::Rect roi_rear(160, 0, 960, 720);
-
- // transforms calculation see tools/webcam/warp_vis.py
- float ts[9] = {1.50330396, 0.0, -59.40969163,
- 0.0, 1.50330396, 76.20704846,
- 0.0, 0.0, 1.0};
- // if camera upside down:
- // float ts[9] = {-1.50330396, 0.0, 1223.4,
- // 0.0, -1.50330396, 797.8,
- // 0.0, 0.0, 1.0};
-
- run_camera(s, cap_road, ts);
-}
-
-void driver_camera_thread(CameraState *s) {
- cv::VideoCapture cap_driver(DRIVER_CAMERA_ID, cv::CAP_V4L2); // driver
- cap_driver.set(cv::CAP_PROP_FRAME_WIDTH, 853);
- cap_driver.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
- cap_driver.set(cv::CAP_PROP_FPS, s->fps);
- // cv::Rect roi_front(320, 0, 960, 720);
-
- // transforms calculation see tools/webcam/warp_vis.py
- float ts[9] = {1.42070485, 0.0, -30.16740088,
- 0.0, 1.42070485, 91.030837,
- 0.0, 0.0, 1.0};
- // if camera upside down:
- // float ts[9] = {-1.42070485, 0.0, 1182.2,
- // 0.0, -1.42070485, 773.0,
- // 0.0, 0.0, 1.0};
- run_camera(s, cap_driver, ts);
-}
-
-} // namespace
-
-void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) {
- camera_init(v, &s->road_cam, CAMERA_ID_LGC920, 20, device_id, ctx,
- VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD);
- camera_init(v, &s->driver_cam, CAMERA_ID_LGC615, 10, device_id, ctx,
- VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER);
- s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"});
-}
-
-void camera_autoexposure(CameraState *s, float grey_frac) {}
-
-void cameras_open(MultiCameraState *s) {
- // LOG("*** open driver camera ***");
- camera_open(&s->driver_cam, false);
- // LOG("*** open road camera ***");
- camera_open(&s->road_cam, true);
-}
-
-void cameras_close(MultiCameraState *s) {
- camera_close(&s->road_cam);
- camera_close(&s->driver_cam);
- delete s->pm;
-}
-
-void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) {
- MessageBuilder msg;
- auto framed = msg.initEvent().initDriverCameraState();
- framed.setFrameType(cereal::FrameData::FrameType::FRONT);
- fill_frame_data(framed, c->buf.cur_frame_data);
- s->pm->send("driverCameraState", msg);
-}
-
-void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
- const CameraBuf *b = &c->buf;
- MessageBuilder msg;
- auto framed = msg.initEvent().initRoadCameraState();
- fill_frame_data(framed, b->cur_frame_data);
- framed.setImage(kj::arrayPtr((const uint8_t *)b->cur_yuv_buf->addr, b->cur_yuv_buf->len));
- framed.setTransform(b->yuv_transform.v);
- s->pm->send("roadCameraState", msg);
-}
-
-void cameras_run(MultiCameraState *s) {
- std::vector threads;
- threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera));
- threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera));
-
- std::thread t_rear = std::thread(road_camera_thread, &s->road_cam);
- util::set_thread_name("webcam_thread");
- driver_camera_thread(&s->driver_cam);
-
- t_rear.join();
-
- for (auto &t : threads) t.join();
-
- cameras_close(s);
-}
diff --git a/selfdrive/camerad/cameras/camera_webcam.h b/selfdrive/camerad/cameras/camera_webcam.h
deleted file mode 100644
index 819387f3d6..0000000000
--- a/selfdrive/camerad/cameras/camera_webcam.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-#ifdef __APPLE__
-#include
-#else
-#include
-#endif
-
-#include "selfdrive/camerad/cameras/camera_common.h"
-
-#define FRAME_BUF_COUNT 16
-
-typedef struct CameraState {
- CameraInfo ci;
- int camera_num;
- int fps;
- float digital_gain;
- CameraBuf buf;
-} CameraState;
-
-
-typedef struct MultiCameraState {
- CameraState road_cam;
- CameraState driver_cam;
-
- SubMaster *sm;
- PubMaster *pm;
-} MultiCameraState;
diff --git a/selfdrive/camerad/cameras/debayer.cl b/selfdrive/camerad/cameras/debayer.cl
deleted file mode 100644
index 4e4b832203..0000000000
--- a/selfdrive/camerad/cameras/debayer.cl
+++ /dev/null
@@ -1,140 +0,0 @@
-const __constant float3 color_correction[3] = {
- // Matrix from WBraw -> sRGBD65 (normalized)
- (float3)( 1.62393627, -0.2092988, 0.00119886),
- (float3)(-0.45734315, 1.5534676, -0.59296798),
- (float3)(-0.16659312, -0.3441688, 1.59176912),
-};
-
-float3 color_correct(float3 x) {
- float3 ret = (0,0,0);
-
- // white balance of daylight
- x /= (float3)(0.4609375, 1.0, 0.546875);
- x = max(0.0, min(1.0, x));
-
- // fix up the colors
- ret += x.x * color_correction[0];
- ret += x.y * color_correction[1];
- ret += x.z * color_correction[2];
- return ret;
-}
-
-float3 srgb_gamma(float3 p) {
- // go all out and add an sRGB gamma curve
- const float3 ph = (1.0f + 0.055f)*pow(p, 1/2.4f) - 0.055f;
- const float3 pl = p*12.92f;
- return select(ph, pl, islessequal(p, 0.0031308f));
-}
-
-#if HDR
-
-__constant int dpcm_lookup[512] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, 935, 951, 967, 983, 999, 1015, 1031, 1047, 1063, 1079, 1095, 1111, 1127, 1143, 1159, 1175, 1191, 1207, 1223, 1239, 1255, 1271, 1287, 1303, 1319, 1335, 1351, 1367, 1383, 1399, 1415, 1431, -935, -951, -967, -983, -999, -1015, -1031, -1047, -1063, -1079, -1095, -1111, -1127, -1143, -1159, -1175, -1191, -1207, -1223, -1239, -1255, -1271, -1287, -1303, -1319, -1335, -1351, -1367, -1383, -1399, -1415, -1431, 419, 427, 435, 443, 451, 459, 467, 475, 483, 491, 499, 507, 515, 523, 531, 539, 547, 555, 563, 571, 579, 587, 595, 603, 611, 619, 627, 635, 643, 651, 659, 667, 675, 683, 691, 699, 707, 715, 723, 731, 739, 747, 755, 763, 771, 779, 787, 795, 803, 811, 819, 827, 835, 843, 851, 859, 867, 875, 883, 891, 899, 907, 915, 923, -419, -427, -435, -443, -451, -459, -467, -475, -483, -491, -499, -507, -515, -523, -531, -539, -547, -555, -563, -571, -579, -587, -595, -603, -611, -619, -627, -635, -643, -651, -659, -667, -675, -683, -691, -699, -707, -715, -723, -731, -739, -747, -755, -763, -771, -779, -787, -795, -803, -811, -819, -827, -835, -843, -851, -859, -867, -875, -883, -891, -899, -907, -915, -923, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233, 237, 241, 245, 249, 253, 257, 261, 265, 269, 273, 277, 281, 285, 289, 293, 297, 301, 305, 309, 313, 317, 321, 325, 329, 333, 337, 341, 345, 349, 353, 357, 361, 365, 369, 373, 377, 381, 385, 389, 393, 397, 401, 405, 409, 413, -161, -165, -169, -173, -177, -181, -185, -189, -193, -197, -201, -205, -209, -213, -217, -221, -225, -229, -233, -237, -241, -245, -249, -253, -257, -261, -265, -269, -273, -277, -281, -285, -289, -293, -297, -301, -305, -309, -313, -317, -321, -325, -329, -333, -337, -341, -345, -349, -353, -357, -361, -365, -369, -373, -377, -381, -385, -389, -393, -397, -401, -405, -409, -413, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, -32, -34, -36, -38, -40, -42, -44, -46, -48, -50, -52, -54, -56, -58, -60, -62, -64, -66, -68, -70, -72, -74, -76, -78, -80, -82, -84, -86, -88, -90, -92, -94, -96, -98, -100, -102, -104, -106, -108, -110, -112, -114, -116, -118, -120, -122, -124, -126, -128, -130, -132, -134, -136, -138, -140, -142, -144, -146, -148, -150, -152, -154, -156, -158};
-
-inline uint4 decompress(uint4 p, uint4 pl) {
- uint4 r1 = (pl + (uint4)(dpcm_lookup[p.s0], dpcm_lookup[p.s1], dpcm_lookup[p.s2], dpcm_lookup[p.s3]));
- uint4 r2 = ((p-0x200)<<5) | 0xF;
- r2 += select((uint4)(0,0,0,0), (uint4)(1,1,1,1), r2 <= pl);
- return select(r2, r1, p < 0x200);
-}
-
-#endif
-
-__kernel void debayer10(__global uchar const * const in,
- __global uchar * out, float digital_gain)
-{
- const int oy = get_global_id(0);
- if (oy >= RGB_HEIGHT) return;
- const int iy = oy * 2;
-
-#if HDR
- uint4 pint_last;
- for (int ox = 0; ox < RGB_WIDTH; ox += 2) {
-#else
- int ox = get_global_id(1) * 2;
- {
-#endif
- const int ix = (ox/2) * 5;
-
- // TODO: why doesn't this work for the frontview
- /*const uchar8 v1 = vload8(0, &in[iy * FRAME_STRIDE + ix]);
- const uchar ex1 = v1.s4;
- const uchar8 v2 = vload8(0, &in[(iy+1) * FRAME_STRIDE + ix]);
- const uchar ex2 = v2.s4;*/
-
- const uchar4 v1 = vload4(0, &in[iy * FRAME_STRIDE + ix]);
- const uchar ex1 = in[iy * FRAME_STRIDE + ix + 4];
- const uchar4 v2 = vload4(0, &in[(iy+1) * FRAME_STRIDE + ix]);
- const uchar ex2 = in[(iy+1) * FRAME_STRIDE + ix + 4];
-
- uint4 pinta[2];
- pinta[0] = (uint4)(
- (((uint)v1.s0 << 2) + ( (ex1 >> 0) & 3)),
- (((uint)v1.s1 << 2) + ( (ex1 >> 2) & 3)),
- (((uint)v2.s0 << 2) + ( (ex2 >> 0) & 3)),
- (((uint)v2.s1 << 2) + ( (ex2 >> 2) & 3)));
- pinta[1] = (uint4)(
- (((uint)v1.s2 << 2) + ( (ex1 >> 4) & 3)),
- (((uint)v1.s3 << 2) + ( (ex1 >> 6) & 3)),
- (((uint)v2.s2 << 2) + ( (ex2 >> 4) & 3)),
- (((uint)v2.s3 << 2) + ( (ex2 >> 6) & 3)));
-
- #pragma unroll
- for (uint px = 0; px < 2; px++) {
- uint4 pint = pinta[px];
-
-#if HDR
- // decompress HDR
- pint = (ox == 0 && px == 0) ? ((pint<<4) | 8) : decompress(pint, pint_last);
- pint_last = pint;
-#endif
-
- float4 p = convert_float4(pint);
-
- // 64 is the black level of the sensor, remove
- // (changed to 56 for HDR)
- const float black_level = 56.0f;
- // TODO: switch to max here?
- p = (p - black_level);
-
- // correct vignetting (no pow function?)
- // see https://www.eecis.udel.edu/~jye/lab_research/09/JiUp.pdf the A (4th order)
- const float r = ((oy - RGB_HEIGHT/2)*(oy - RGB_HEIGHT/2) + (ox - RGB_WIDTH/2)*(ox - RGB_WIDTH/2));
- const float fake_f = 700.0f; // should be 910, but this fits...
- const float lil_a = (1.0f + r/(fake_f*fake_f));
- p = p * lil_a * lil_a;
-
- // rescale to 1.0
-#if HDR
- p /= (16384.0f-black_level);
-#else
- p /= (1024.0f-black_level);
-#endif
-
- // digital gain
- p *= digital_gain;
-
- // use both green channels
-#if BAYER_FLIP == 3
- float3 c1 = (float3)(p.s3, (p.s1+p.s2)/2.0f, p.s0);
-#elif BAYER_FLIP == 2
- float3 c1 = (float3)(p.s2, (p.s0+p.s3)/2.0f, p.s1);
-#elif BAYER_FLIP == 1
- float3 c1 = (float3)(p.s1, (p.s0+p.s3)/2.0f, p.s2);
-#elif BAYER_FLIP == 0
- float3 c1 = (float3)(p.s0, (p.s1+p.s2)/2.0f, p.s3);
-#endif
-
- // color correction
- c1 = color_correct(c1);
-
-#if HDR
- // srgb gamma isn't right for YUV, so it's disabled for now
- c1 = srgb_gamma(c1);
-#endif
-
- // output BGR
- const int ooff = oy * RGB_STRIDE/3 + ox;
- vstore3(convert_uchar3_sat(c1.zyx * 255.0f), ooff+px, out);
- }
- }
-}
diff --git a/selfdrive/camerad/cameras/real_debayer.cl b/selfdrive/camerad/cameras/real_debayer.cl
deleted file mode 100644
index fe6a99f373..0000000000
--- a/selfdrive/camerad/cameras/real_debayer.cl
+++ /dev/null
@@ -1,207 +0,0 @@
-#pragma OPENCL EXTENSION cl_khr_fp16 : enable
-
-const half black_level = 42.0;
-
-const __constant half3 color_correction[3] = {
- // post wb CCM
- (half3)(1.82717181, -0.31231438, 0.07307673),
- (half3)(-0.5743977, 1.36858544, -0.53183455),
- (half3)(-0.25277411, -0.05627105, 1.45875782),
-};
-
-// tone mapping params
-const half cpk = 0.75;
-const half cpb = 0.125;
-const half cpxk = 0.0025;
-const half cpxb = 0.01;
-
-half mf(half x, half cp) {
- half rk = 9 - 100*cp;
- if (x > cp) {
- return (rk * (x-cp) * (1-(cpk*cp+cpb)) * (1+1/(rk*(1-cp))) / (1+rk*(x-cp))) + cpk*cp + cpb;
- } else if (x < cp) {
- return (rk * (x-cp) * (cpk*cp+cpb) * (1+1/(rk*cp)) / (1-rk*(x-cp))) + cpk*cp + cpb;
- } else {
- return x;
- }
-}
-
-half3 color_correct(half3 rgb) {
- half3 ret = (0,0,0);
- half cpx = 0.01;
- ret += (half)rgb.x * color_correction[0];
- ret += (half)rgb.y * color_correction[1];
- ret += (half)rgb.z * color_correction[2];
- ret.x = mf(ret.x, cpx);
- ret.y = mf(ret.y, cpx);
- ret.z = mf(ret.z, cpx);
- ret = clamp(0.0h, 255.0h, ret*255.0h);
- return ret;
-}
-
-half val_from_10(const uchar * source, int gx, int gy) {
- // parse 10bit
- int start = gy * FRAME_STRIDE + (5 * (gx / 4));
- int offset = gx % 4;
- uint major = (uint)source[start + offset] << 2;
- uint minor = (source[start + 4] >> (2 * offset)) & 3;
- half pv = (half)(major + minor);
-
- // normalize
- pv = max(0.0h, pv - black_level);
- pv *= 0.00101833h; // /= (1024.0f - black_level);
-
- // correct vignetting
- if (CAM_NUM == 1) { // fcamera
- gx = (gx - RGB_WIDTH/2);
- gy = (gy - RGB_HEIGHT/2);
- float r = gx*gx + gy*gy;
- half s;
- if (r < 62500) {
- s = (half)(1.0f + 0.0000008f*r);
- } else if (r < 490000) {
- s = (half)(0.9625f + 0.0000014f*r);
- } else if (r < 1102500) {
- s = (half)(1.26434f + 0.0000000000016f*r*r);
- } else {
- s = (half)(0.53503625f + 0.0000000000022f*r*r);
- }
- pv = s * pv;
- }
-
- pv = clamp(0.0h, 1.0h, pv);
- return pv;
-}
-
-half fabs_diff(half x, half y) {
- return fabs(x-y);
-}
-
-half phi(half x) {
- // detection funtion
- return 2 - x;
- // if (x > 1) {
- // return 1 / x;
- // } else {
- // return 2 - x;
- // }
-}
-
-__kernel void debayer10(const __global uchar * in,
- __global uchar * out,
- __local half * cached
- )
-{
- const int x_global = get_global_id(0);
- const int y_global = get_global_id(1);
-
- const int localRowLen = 2 + get_local_size(0); // 2 padding
- const int x_local = get_local_id(0); // 0-15
- const int y_local = get_local_id(1); // 0-15
- const int localOffset = (y_local + 1) * localRowLen + x_local + 1; // max 18x18-1
-
- int out_idx = 3 * x_global + 3 * y_global * RGB_WIDTH;
-
- half pv = val_from_10(in, x_global, y_global);
- cached[localOffset] = pv;
-
- // don't care
- if (x_global < 1 || x_global >= RGB_WIDTH - 1 || y_global < 1 || y_global >= RGB_HEIGHT - 1) {
- return;
- }
-
- // cache padding
- int localColOffset = -1;
- int globalColOffset = -1;
-
- // cache padding
- if (x_local < 1) {
- localColOffset = x_local;
- globalColOffset = -1;
- cached[(y_local + 1) * localRowLen + x_local] = val_from_10(in, x_global-1, y_global);
- } else if (x_local >= get_local_size(0) - 1) {
- localColOffset = x_local + 2;
- globalColOffset = 1;
- cached[localOffset + 1] = val_from_10(in, x_global+1, y_global);
- }
-
- if (y_local < 1) {
- cached[y_local * localRowLen + x_local + 1] = val_from_10(in, x_global, y_global-1);
- if (localColOffset != -1) {
- cached[y_local * localRowLen + localColOffset] = val_from_10(in, x_global+globalColOffset, y_global-1);
- }
- } else if (y_local >= get_local_size(1) - 1) {
- cached[(y_local + 2) * localRowLen + x_local + 1] = val_from_10(in, x_global, y_global+1);
- if (localColOffset != -1) {
- cached[(y_local + 2) * localRowLen + localColOffset] = val_from_10(in, x_global+globalColOffset, y_global+1);
- }
- }
-
- // sync
- barrier(CLK_LOCAL_MEM_FENCE);
-
- half d1 = cached[localOffset - localRowLen - 1];
- half d2 = cached[localOffset - localRowLen + 1];
- half d3 = cached[localOffset + localRowLen - 1];
- half d4 = cached[localOffset + localRowLen + 1];
- half n1 = cached[localOffset - localRowLen];
- half n2 = cached[localOffset + 1];
- half n3 = cached[localOffset + localRowLen];
- half n4 = cached[localOffset - 1];
-
- half3 rgb;
-
- // a simplified version of https://opensignalprocessingjournal.com/contents/volumes/V6/TOSIGPJ-6-1/TOSIGPJ-6-1.pdf
- if (x_global % 2 == 0) {
- if (y_global % 2 == 0) {
- rgb.y = pv; // G1(R)
- half k1 = phi(fabs_diff(d1, pv) + fabs_diff(d2, pv));
- half k2 = phi(fabs_diff(d2, pv) + fabs_diff(d4, pv));
- half k3 = phi(fabs_diff(d3, pv) + fabs_diff(d4, pv));
- half k4 = phi(fabs_diff(d1, pv) + fabs_diff(d3, pv));
- // R_G1
- rgb.x = (k2*n2+k4*n4)/(k2+k4);
- // B_G1
- rgb.z = (k1*n1+k3*n3)/(k1+k3);
- } else {
- rgb.z = pv; // B
- half k1 = phi(fabs_diff(d1, d3) + fabs_diff(d2, d4));
- half k2 = phi(fabs_diff(n1, n4) + fabs_diff(n2, n3));
- half k3 = phi(fabs_diff(d1, d2) + fabs_diff(d3, d4));
- half k4 = phi(fabs_diff(n1, n2) + fabs_diff(n3, n4));
- // G_B
- rgb.y = (k1*(n1+n3)*0.5+k3*(n2+n4)*0.5)/(k1+k3);
- // R_B
- rgb.x = (k2*(d2+d3)*0.5+k4*(d1+d4)*0.5)/(k2+k4);
- }
- } else {
- if (y_global % 2 == 0) {
- rgb.x = pv; // R
- half k1 = phi(fabs_diff(d1, d3) + fabs_diff(d2, d4));
- half k2 = phi(fabs_diff(n1, n4) + fabs_diff(n2, n3));
- half k3 = phi(fabs_diff(d1, d2) + fabs_diff(d3, d4));
- half k4 = phi(fabs_diff(n1, n2) + fabs_diff(n3, n4));
- // G_R
- rgb.y = (k1*(n1+n3)*0.5+k3*(n2+n4)*0.5)/(k1+k3);
- // B_R
- rgb.z = (k2*(d2+d3)*0.5+k4*(d1+d4)*0.5)/(k2+k4);
- } else {
- rgb.y = pv; // G2(B)
- half k1 = phi(fabs_diff(d1, pv) + fabs_diff(d2, pv));
- half k2 = phi(fabs_diff(d2, pv) + fabs_diff(d4, pv));
- half k3 = phi(fabs_diff(d3, pv) + fabs_diff(d4, pv));
- half k4 = phi(fabs_diff(d1, pv) + fabs_diff(d3, pv));
- // R_G2
- rgb.x = (k1*n1+k3*n3)/(k1+k3);
- // B_G2
- rgb.z = (k2*n2+k4*n4)/(k2+k4);
- }
- }
-
- rgb = clamp(0.0h, 1.0h, rgb);
- rgb = color_correct(rgb);
-
- out[out_idx + 0] = (uchar)(rgb.z);
- out[out_idx + 1] = (uchar)(rgb.y);
- out[out_idx + 2] = (uchar)(rgb.x);
-}
diff --git a/selfdrive/camerad/cameras/sensor2_i2c.h b/selfdrive/camerad/cameras/sensor2_i2c.h
deleted file mode 100644
index c3d8861a97..0000000000
--- a/selfdrive/camerad/cameras/sensor2_i2c.h
+++ /dev/null
@@ -1,124 +0,0 @@
-struct i2c_random_wr_payload start_reg_array[] = {{0x301A, 0x91C}};
-struct i2c_random_wr_payload stop_reg_array[] = {{0x301A, 0x918}};
-
-struct i2c_random_wr_payload init_array_ar0231[] = {
- {0x301A, 0x0018}, // RESET_REGISTER
-
- // CLOCK Settings
- {0x302A, 0x0006}, // VT_PIX_CLK_DIV
- {0x302C, 0x0001}, // VT_SYS_CLK_DIV
- {0x302E, 0x0002}, // PRE_PLL_CLK_DIV
- {0x3030, 0x0032}, // PLL_MULTIPLIER
- {0x3036, 0x000A}, // OP_WORD_CLK_DIV
- {0x3038, 0x0001}, // OP_SYS_CLK_DIV
-
- // FORMAT
- {0x3040, 0xC000}, // READ_MODE
- {0x3004, 0x0000}, // X_ADDR_START_ (A)
- {0x308A, 0x0000}, // X_ADDR_START_ (B)
- {0x3008, 0x0787}, // X_ADDR_END_ (A)
- {0x308E, 0x0787}, // X_ADDR_END_ (B)
- {0x3002, 0x0000}, // Y_ADDR_START_ (A)
- {0x308C, 0x0000}, // Y_ADDR_START_ (B)
- {0x3006, 0x04B7}, // Y_ADDR_END_ (A)
- {0x3090, 0x04B7}, // Y_ADDR_END_ (B)
- {0x3032, 0x0000}, // SCALING_MODE
- {0x30A2, 0x0001}, // X_ODD_INC_ (A)
- {0x30AE, 0x0001}, // X_ODD_INC_ (B)
- {0x30A6, 0x0001}, // Y_ODD_INC_ (A)
- {0x30A8, 0x0001}, // Y_ODD_INC_ (B)
- {0x3402, 0x0F10}, // X_OUTPUT_CONTROL
- {0x3404, 0x0970}, // Y_OUTPUT_CONTROL
- {0x3064, 0x1802}, // SMIA_TEST
- {0x30BA, 0x11F2}, // DIGITAL_CTRL
-
- // SLAV* MODE
- {0x30CE, 0x0120},
- {0x340A, 0xE6}, // E6 // 0000 1110 0110
- {0x340C, 0x802}, // 2 // 0000 0000 0010
-
- // Readout timing
- {0x300C, 0x07B9}, // LINE_LENGTH_PCK (A)
- {0x303E, 0x07B9}, // LINE_LENGTH_PCK (B)
- {0x300A, 0x07E7}, // FRAME_LENGTH_LINES (A)
- {0x30AA, 0x07E7}, // FRAME_LENGTH_LINES (B)
- {0x3042, 0x0000}, // EXTRA_DELAY
-
- // Readout Settings
- {0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI
- {0x31AC, 0x0C0A}, // DATA_FORMAT_BITS, 12 -> 10
- {0x3342, 0x122B}, // MIPI_F1_PDT_EDT
- {0x3346, 0x122B}, // MIPI_F2_PDT_EDT
- {0x334A, 0x122B}, // MIPI_F3_PDT_EDT
- {0x334E, 0x122B}, // MIPI_F4_PDT_EDT
- {0x3344, 0x0011}, // MIPI_F1_VDT_VC
- {0x3348, 0x0111}, // MIPI_F2_VDT_VC
- {0x334C, 0x0211}, // MIPI_F3_VDT_VC
- {0x3350, 0x0311}, // MIPI_F4_VDT_VC
- {0x31B0, 0x0053}, // FRAME_PREAMBLE
- {0x31B2, 0x003B}, // LINE_PREAMBLE
- {0x301A, 0x001C}, // RESET_REGISTER
-
- // Noise Corrections
- {0x3092, 0x0C24}, // ROW_NOISE_CONTROL
- {0x337A, 0x0C80}, // DBLC_SCALE0
- {0x3370, 0x03B1}, // DBLC
- {0x3044, 0x0400}, // DARK_CONTROL
-
- // Enable dead pixel correction using
- // the 1D line correction scheme
- {0x31E0, 0x0003},
-
- // HDR Settings
- {0x3082, 0x0004}, // OPERATION_MODE_CTRL (A)
- {0x3084, 0x0004}, // OPERATION_MODE_CTRL (B)
-
- {0x3238, 0x0004}, // EXPOSURE_RATIO (A)
- {0x323A, 0x0004}, // EXPOSURE_RATIO (B)
-
- {0x3014, 0x098E}, // FINE_INTEGRATION_TIME_ (A)
- {0x3018, 0x098E}, // FINE_INTEGRATION_TIME_ (B)
-
- {0x321E, 0x098E}, // FINE_INTEGRATION_TIME2 (A)
- {0x3220, 0x098E}, // FINE_INTEGRATION_TIME2 (B)
-
- {0x31D0, 0x0000}, // COMPANDING, no good in 10 bit?
- {0x33DA, 0x0000}, // COMPANDING
- {0x318E, 0x0200}, // PRE_HDR_GAIN_EN
-
- // DLO Settings
- {0x3100, 0x4000}, // DLO_CONTROL0
- {0x3280, 0x0CCC}, // T1 G1
- {0x3282, 0x0CCC}, // T1 R
- {0x3284, 0x0CCC}, // T1 B
- {0x3286, 0x0CCC}, // T1 G2
- {0x3288, 0x0FA0}, // T2 G1
- {0x328A, 0x0FA0}, // T2 R
- {0x328C, 0x0FA0}, // T2 B
- {0x328E, 0x0FA0}, // T2 G2
-
- // Initial Gains
- {0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_
- {0x3366, 0xFF77}, // ANALOG_GAIN (1x) (A)
- {0x3368, 0xFF77}, // ANALOG_GAIN (1x) (B)
-
- {0x3060, 0x3333}, // ANALOG_COLOR_GAIN
-
- {0x3362, 0x0000}, // DC GAIN (A & B)
-
- {0x305A, 0x00F8}, // red gain (A)
- {0x3058, 0x0122}, // blue gain (A)
- {0x3056, 0x009A}, // g1 gain (A)
- {0x305C, 0x009A}, // g2 gain (A)
-
- {0x30C0, 0x00F8}, // red gain (B)
- {0x30BE, 0x0122}, // blue gain (B)
- {0x30BC, 0x009A}, // g1 gain (B)
- {0x30C2, 0x009A}, // g2 gain (B)
-
- {0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_
-
- // Initial Integration Time
- {0x3012, 0x0005}, // (A)
- {0x3016, 0x0005}, // (B)
-};
diff --git a/selfdrive/camerad/cameras/sensor_i2c.h b/selfdrive/camerad/cameras/sensor_i2c.h
deleted file mode 100644
index b46ebb370a..0000000000
--- a/selfdrive/camerad/cameras/sensor_i2c.h
+++ /dev/null
@@ -1,651 +0,0 @@
-static struct msm_camera_i2c_reg_array init_array_imx298[] = {
- {0x101,0x0,0}, // IMAGE_ORIENT
- {0x601,0x0,0}, // test pattern
- //{0xb02,0,0}, // green correction?
- // external clock setting
- {0x136,0x18,0}, {0x137,0x0,0}, // EXCK_FREQ = Extclk_frequency_mhz
- // global setting?
- {0x30f4,0x1,0},
- {0x30f5,0x7a,0},
- {0x30f6,0x0,0},
- {0x30f7,0xec,0},
- {0x30fc,0x1,0},
- {0x3101,0x1,0},
- {0x5b2f,0x8,0},
- {0x5d32,0x5,0},
- {0x5d7c,0x0,0},
- {0x5d7d,0x0,0},
- {0x5db9,0x1,0},
- {0x5e43,0x0,0},
- {0x6300,0x0,0},
- {0x6301,0xea,0},
- {0x6302,0x0,0},
- {0x6303,0xb4,0},
- {0x6564,0x0,0},
- {0x6565,0xb6,0},
- {0x6566,0x0,0},
- {0x6567,0xe6,0},
- {0x6714,0x1,0},
- {0x6758,0xb,0},
- {0x6910,0x4,0},
- {0x6916,0x1,0},
- {0x6918,0x4,0},
- {0x691e,0x1,0},
- {0x6931,0x1,0},
- {0x6937,0x2,0},
- {0x693b,0x2,0},
- {0x6d00,0x4a,0},
- {0x6d01,0x41,0},
- {0x6d02,0x23,0},
- {0x6d05,0x4c,0},
- {0x6d06,0x10,0},
- {0x6d08,0x30,0},
- {0x6d09,0x38,0},
- {0x6d0a,0x2c,0},
- {0x6d0b,0x2d,0},
- {0x6d0c,0x34,0},
- {0x6d0d,0x42,0},
- {0x6d19,0x1c,0},
- {0x6d1a,0x71,0},
- {0x6d1b,0xc6,0},
- {0x6d1c,0x94,0},
- {0x6d24,0xe4,0},
- {0x6d30,0xa,0},
- {0x6d31,0x1,0},
- {0x6d33,0xb,0},
- {0x6d34,0x5,0},
- {0x6d35,0x0,0},
- {0x83c2,0x3,0},
- {0x83c3,0x8,0},
- {0x83c4,0x48,0},
- {0x83c7,0x8,0},
- {0x83cb,0x0,0},
- {0xb101,0xff,0},
- {0xb103,0xff,0},
- {0xb105,0xff,0},
- {0xb107,0xff,0},
- {0xb109,0xff,0},
- {0xb10b,0xff,0},
- {0xb10d,0xff,0},
- {0xb10f,0xff,0},
- {0xb111,0xff,0},
- {0xb163,0x3c,0},
- {0xc2a0,0x8,0},
- {0xc2a3,0x3,0},
- {0xc2a5,0x8,0},
- {0xc2a6,0x48,0},
- {0xc2a9,0x0,0},
- {0xf800,0x5e,0},
- {0xf801,0x5e,0},
- {0xf802,0xcd,0},
- {0xf803,0x20,0},
- {0xf804,0x55,0},
- {0xf805,0xd4,0},
- {0xf806,0x1f,0},
- {0xf808,0xf8,0},
- {0xf809,0x3a,0},
- {0xf80a,0xf1,0},
- {0xf80b,0x7e,0},
- {0xf80c,0x55,0},
- {0xf80d,0x38,0},
- {0xf80e,0xe3,0},
- {0xf810,0x74,0},
- {0xf811,0x41,0},
- {0xf812,0xbf,0},
- {0xf844,0x40,0},
- {0xf845,0xba,0},
- {0xf846,0x70,0},
- {0xf847,0x47,0},
- {0xf848,0xc0,0},
- {0xf849,0xba,0},
- {0xf84a,0x70,0},
- {0xf84b,0x47,0},
- {0xf84c,0x82,0},
- {0xf84d,0xf6,0},
- {0xf84e,0x32,0},
- {0xf84f,0xfd,0},
- {0xf851,0xf0,0},
- {0xf852,0x2,0},
- {0xf853,0xf8,0},
- {0xf854,0x81,0},
- {0xf855,0xf6,0},
- {0xf856,0xc0,0},
- {0xf857,0xff,0},
- {0xf858,0x10,0},
- {0xf859,0xb5,0},
- {0xf85a,0xd,0},
- {0xf85b,0x48,0},
- {0xf85c,0x40,0},
- {0xf85d,0x7a,0},
- {0xf85e,0x1,0},
- {0xf85f,0x28,0},
- {0xf860,0x15,0},
- {0xf861,0xd1,0},
- {0xf862,0xc,0},
- {0xf863,0x49,0},
- {0xf864,0xc,0},
- {0xf865,0x46,0},
- {0xf866,0x40,0},
- {0xf867,0x3c,0},
- {0xf868,0x48,0},
- {0xf869,0x8a,0},
- {0xf86a,0x62,0},
- {0xf86b,0x8a,0},
- {0xf86c,0x80,0},
- {0xf86d,0x1a,0},
- {0xf86e,0x8a,0},
- {0xf86f,0x89,0},
- {0xf871,0xb2,0},
- {0xf872,0x10,0},
- {0xf873,0x18,0},
- {0xf874,0xa,0},
- {0xf875,0x46,0},
- {0xf876,0x20,0},
- {0xf877,0x32,0},
- {0xf878,0x12,0},
- {0xf879,0x88,0},
- {0xf87a,0x90,0},
- {0xf87b,0x42,0},
- {0xf87d,0xda,0},
- {0xf87e,0x10,0},
- {0xf87f,0x46,0},
- {0xf880,0x80,0},
- {0xf881,0xb2,0},
- {0xf882,0x88,0},
- {0xf883,0x81,0},
- {0xf884,0x84,0},
- {0xf885,0xf6,0},
- {0xf886,0xd2,0},
- {0xf887,0xf9,0},
- {0xf888,0xe0,0},
- {0xf889,0x67,0},
- {0xf88a,0x85,0},
- {0xf88b,0xf6,0},
- {0xf88c,0xa1,0},
- {0xf88d,0xfc,0},
- {0xf88e,0x10,0},
- {0xf88f,0xbd,0},
- {0xf891,0x18,0},
- {0xf892,0x21,0},
- {0xf893,0x24,0},
- {0xf895,0x18,0},
- {0xf896,0x19,0},
- {0xf897,0xb4,0},
- {0x4e29,0x1,0},
- // PDAF stuff
- {0x3166,0x1,0}, //AREA_EN_0
- {0x3167,0x1,0},
- {0x3168,0x1,0},
- {0x3169,0x1,0},
- {0x316a,0x1,0},
- {0x316b,0x1,0},
- {0x316c,0x1,0},
- {0x316d,0x1,0},
- {0x3158,0x2,0},
- {0x3159,0x2,0},
- {0x315a,0x2,0},
- {0x315b,0x3,0},
- {0x3013,0x7,0}, //RMSC_NR_MODE
- {0x3035,0x1,0},
- {0x3051,0x0,0},
- {0x3056,0x2,0},
- {0x3057,0x1,0},
- {0x3060,0x0,0},
- {0x8435,0x0,0},
- {0x8455,0x0,0},
- {0x847c,0x0,0},
- {0x84fb,0x1,0},
- {0x9619,0xa0,0},
- {0x961b,0xa0,0},
- {0x961d,0xa0,0},
- {0x961f,0x20,0},
- {0x9621,0x20,0},
- {0x9623,0x20,0},
- {0x9625,0xa0,0},
- {0x9627,0xa0,0},
- {0x9629,0xa0,0},
- {0x962b,0x20,0},
- {0x962d,0x20,0},
- {0x962f,0x20,0},
- {0x9901,0x35,0},
- {0x9903,0x23,0},
- {0x9905,0x23,0},
- {0x9906,0x0,0},
- {0x9907,0x31,0},
- {0x9908,0x0,0},
- {0x9909,0x1b,0},
- {0x990a,0x0,0},
- {0x990b,0x15,0},
- {0x990d,0x3f,0},
- {0x990f,0x3f,0},
- {0x9911,0x3f,0},
- {0x9913,0x64,0},
- {0x9915,0x64,0},
- {0x9917,0x64,0},
- {0x9919,0x50,0},
- {0x991b,0x60,0},
- {0x991d,0x65,0},
- {0x991f,0x1,0},
- {0x9921,0x1,0},
- {0x9923,0x1,0},
- {0x9925,0x23,0},
- {0x9927,0x23,0},
- {0x9929,0x23,0},
- {0x992b,0x2f,0},
- {0x992d,0x1a,0},
- {0x992f,0x14,0},
- {0x9931,0x3f,0},
- {0x9933,0x3f,0},
- {0x9935,0x3f,0},
- {0x9937,0x6b,0},
- {0x9939,0x7c,0},
- {0x993b,0x81,0},
- {0x9943,0xf,0},
- {0x9945,0xf,0},
- {0x9947,0xf,0},
- {0x9949,0xf,0},
- {0x994b,0xf,0},
- {0x994d,0xf,0},
- {0x994f,0x42,0},
- {0x9951,0xf,0},
- {0x9953,0xb,0},
- {0x9955,0x5a,0},
- {0x9957,0x13,0},
- {0x9959,0xc,0},
- {0x995a,0x0,0},
- {0x995b,0x0,0},
- {0x995c,0x0,0},
- {0x996b,0x0,0},
- {0x996d,0x10,0},
- {0x996f,0x10,0},
- {0x9971,0xc8,0},
- {0x9973,0x32,0},
- {0x9975,0x4,0},
- {0x9976,0xa,0},
- {0x9977,0xa,0},
- {0x9978,0xa,0},
- {0x99a4,0x2f,0},
- {0x99a5,0x2f,0},
- {0x99a6,0x2f,0},
- {0x99a7,0xa,0},
- {0x99a8,0xa,0},
- {0x99a9,0xa,0},
- {0x99aa,0x2f,0},
- {0x99ab,0x2f,0},
- {0x99ac,0x2f,0},
- {0x99ad,0x0,0},
- {0x99ae,0x0,0},
- {0x99af,0x0,0},
- {0x99b0,0x40,0},
- {0x99b1,0x40,0},
- {0x99b2,0x40,0},
- {0x99b3,0x30,0},
- {0x99b4,0x30,0},
- {0x99b5,0x30,0},
- {0x99bb,0xa,0},
- {0x99bd,0xa,0},
- {0x99bf,0xa,0},
- {0x99c0,0x9,0},
- {0x99c1,0x9,0},
- {0x99c2,0x9,0},
- {0x99c6,0x3c,0},
- {0x99c7,0x3c,0},
- {0x99c8,0x3c,0},
- {0x99c9,0xff,0},
- {0x99ca,0xff,0},
- {0x99cb,0xff,0},
- {0x9b01,0x35,0},
- {0x9b03,0x14,0},
- {0x9b05,0x14,0},
- {0x9b07,0x31,0},
- {0x9b09,0x1b,0},
- {0x9b0b,0x15,0},
- {0x9b0d,0x1e,0},
- {0x9b0f,0x1e,0},
- {0x9b11,0x1e,0},
- {0x9b13,0x64,0},
- {0x9b15,0x64,0},
- {0x9b17,0x64,0},
- {0x9b19,0x50,0},
- {0x9b1b,0x60,0},
- {0x9b1d,0x65,0},
- {0x9b1f,0x1,0},
- {0x9b21,0x1,0},
- {0x9b23,0x1,0},
- {0x9b25,0x14,0},
- {0x9b27,0x14,0},
- {0x9b29,0x14,0},
- {0x9b2b,0x2f,0},
- {0x9b2d,0x1a,0},
- {0x9b2f,0x14,0},
- {0x9b31,0x1e,0},
- {0x9b33,0x1e,0},
- {0x9b35,0x1e,0},
- {0x9b37,0x6b,0},
- {0x9b39,0x7c,0},
- {0x9b3b,0x81,0},
- {0x9b43,0xf,0},
- {0x9b45,0xf,0},
- {0x9b47,0xf,0},
- {0x9b49,0xf,0},
- {0x9b4b,0xf,0},
- {0x9b4d,0xf,0},
- {0x9b4f,0x2d,0},
- {0x9b51,0xb,0},
- {0x9b53,0x8,0},
- {0x9b55,0x40,0},
- {0x9b57,0xd,0},
- {0x9b59,0x8,0},
- {0x9b5a,0x0,0},
- {0x9b5b,0x0,0},
- {0x9b5c,0x0,0},
- {0x9b6b,0x0,0},
- {0x9b6d,0x10,0},
- {0x9b6f,0x10,0},
- {0x9b71,0xc8,0},
- {0x9b73,0x32,0},
- {0x9b75,0x4,0},
- {0x9bb0,0x40,0},
- {0x9bb1,0x40,0},
- {0x9bb2,0x40,0},
- {0x9bb3,0x30,0},
- {0x9bb4,0x30,0},
- {0x9bb5,0x30,0},
- {0x9bbb,0xa,0},
- {0x9bbd,0xa,0},
- {0x9bbf,0xa,0},
- {0x9bc0,0x9,0},
- {0x9bc1,0x9,0},
- {0x9bc2,0x9,0},
- {0x9bc6,0x18,0},
- {0x9bc7,0x18,0},
- {0x9bc8,0x18,0},
- {0x9bc9,0xff,0},
- {0x9bca,0xff,0},
- {0x9bcb,0xff,0},
- {0xb2b2,0x1,0},
-};
-
-static struct msm_camera_i2c_reg_array mode_setting_array_imx298[] = {
-// i2c settings for mode 3
-// {
-// .x_output = 2328,
-// .y_output = 1748,
-// .line_length_pclk = 5536,
-// .frame_length_lines = 1802,
-// .vt_pixel_clk = 299300000,
-// .op_pixel_clk = 299300000,
-// .binning_factor = 2,
-// .min_fps = 15.000000,
-// .max_fps = 30.020000,
-// .mode = 1,
-// .offset_x = 0,
-// .offset_y = 0,
-// .scale_factor = 1.000000,
-// .is_pdaf_supported = 1,
-// }
-
-// mode settings
-
-// hdr settings
-{0x0114, 0x03, 0}, // CSI_LANE_MODE = 4-lane
-/*{0x0220, 0x00, 0}, // HDR_MODE = disable
-{0x0221, 0x11, 0}, // HDR_RESO_REDU_H/V = Full Pixel
-{0x0222, 0x10, 0}, // EXPO_RATIO = 16*/
-{0x0220, 0x01, 0}, // HDR_MODE = enable with combined gain and 16x ratio
-{0x0221, 0x22, 0}, // HDR_RESO_REDU_H/V = 2 binning
-{0x0222, 0x10, 0}, // EXPO_RATIO = 16
-
-{0x0340, 0x07, 0}, {0x0341, 0x0a, 0}, // FRM_LENGTH = frame_length_lines = 1802
-{0x0342, 0x15, 0}, {0x0343, 0xa0, 0}, // LINE_LENGTH = line_length_pclk = 5536
-{0x0344, 0x00, 0}, {0x0345, 0x00, 0}, // x_addr_start
-{0x0346, 0x00, 0}, {0x0347, 0x00, 0}, // y_addr_start
-{0x0348, 0x12, 0}, {0x0349, 0x2f, 0}, // x_addr_end
-{0x034a, 0x0d, 0}, {0x034b, 0xa7, 0}, // y_addr_end
-{0x0381, 0x01, 0}, // x_even_inc
-{0x0383, 0x01, 0}, // x_odd_inc
-{0x0385, 0x01, 0}, // y_even_inc
-{0x0387, 0x01, 0}, // y_odd_inc
-{0x0900, 0x01, 0}, // BINNING_MODE = enable
-{0x0901, 0x22, 0}, // BINING_TYPE_H/V = 2binning
-{0x0902, 0x00, 0}, // binning_weighting = average
-
-{0x0b06, 1, 0}, // SING_DEF_CORR_EN
-{0x0b0a, 1, 0}, // combined defect correct
-
-{0x3010, 0x66, 0}, // HDR_OUTPUT_CTRL = ATR + HDR compose + DPC1D + DCP2D
-{0x3011, 0x01, 0}, // HDR_OUTPUT_CTRL2 = PD enable
-{0x30c0, 0x11, 0}, // RED_GAIN_CB?
-{0x300d, 0x00, 0}, // FORCE_FDSUM = disable
-{0x30fd, 0x00, 0},
-{0x8493, 0x00, 0},
-{0x8863, 0x00, 0},
-{0x90d7, 0x19, 0},
-
-// set black level
-{0x3090, 1, 0},
-{0x3092, 0, 0},
-{0x3093, 0x38, 0},
-
-// output size settings
-{0x0112, 0x0a, 0}, {0x0113, 0x0a, 0}, // CS_DT_FMT_H = 0x0a0a (RAW10 output)
-{0x034c, 0x09, 0}, {0x034d, 0x18, 0}, // X_OUT_SIZE = 2328 (1164*2)
-{0x034e, 0x06, 0}, {0x034f, 0xd4, 0}, // Y_OUT_SIZE = 1748 (874*2)
-{0x0401, 0x00, 0}, // SCALING_MODE
-{0x0404, 0x00, 0}, {0x0405, 0x10, 0}, // SCALE_M
-{0x0408, 0x00, 0}, {0x0409, 0x00, 0}, // DCROP_XOFS
-{0x040a, 0x00, 0}, {0x040b, 0x00, 0}, // DCROP_YOFS
-{0x040c, 0x09, 0}, {0x040d, 0x18, 0}, // DCROP_WIDTH
-{0x040e, 0x06, 0}, {0x040f, 0xd4, 0}, // DCROP_HIGT
-
-// clock settings
-// 299300000
-/*
-{0x0301, 0x05, 0}, // VT_PIX_CLK_DIV
-{0x0303, 0x02, 0}, // VT_SYS_CLK_DIV
-{0x0305, 0x04, 0}, // PRE_PLL_CLK_DIV
-{0x0306, 0x00, 0}, {0x0307, 0x7d, 0}, // PLL_MULTIPLIER . mode 1: 0xf6
-{0x0309, 0x0a, 0}, // OP_PIX_CLK_DIV
-{0x030b, 0x01, 0}, // OP_SYS_CLK_DIV
-{0x030d, 0x0f, 0}, // PREPLLCK_OP_DIV
-{0x030e, 0x03, 0}, {0x030f, 0x41, 0}, // PLL_OP_MPY
-{0x0310, 0x00, 0}, // PLL_MULT_DRIV
-*/
-// 600000000
-{0x0301, 0x05, 0},
-{0x0303, 0x02, 0},
-{0x0305, 0x04, 0},
-{0x0306, 0x00, 0},
-{0x0307, 0xfa, 0},
-{0x0309, 0x0a, 0},
-{0x030b, 0x01, 0},
-{0x030d, 0x0f, 0},
-{0x030e, 0x03, 0}, {0x030f, 0x41, 0},
-{0x0310, 0x00, 0},
-
-// data rate settings
-/*{0x0820, 0x0b, 0}, // requested_link_bit_rate_mbps = 3000
-{0x0821, 0xb8, 0},
-{0x0822, 0x00, 0},
-{0x0823, 0x00, 0},*/
-{0x0820, 0x17, 0}, // requested_link_bit_rate_mbps = 6000
-{0x0821, 0x70, 0},
-{0x0822, 0x00, 0},
-{0x0823, 0x00, 0},
-
-//integration time settings
-{0x0202, 0x07, 0}, {0x0203, 0x00, 0}, // INTEG_TIME = 1792
-{0x0224, 0x01, 0}, {0x0225, 0xf4, 0}, // ST_COARSE_INTEG_TIME = 506
-
-// gain settings
-{0x0204, 0x00, 0}, {0x0205, 0x00, 0}, // ANA_GAIN_GLOBAL = 512 / (512 - X)
-{0x0216, 0x00, 0}, {0x0217, 0x00, 0}, // ST_ANA_GAIN_GLOBAL[8]
-{0x020e, 0x01, 0}, {0x020f, 0x00, 0}, // DIG_GAIN_GR
-{0x0210, 0x01, 0}, {0x0211, 0x00, 0}, // DIG_GAIN_R
-{0x0212, 0x01, 0}, {0x0213, 0x00, 0}, // DIG_GAIN_B
-{0x0214, 0x01, 0}, {0x0215, 0x00, 0}, // DIG_GAIN_GB
-
-// HDR white balance settings (ABS_GAIN)
-{0xb8e, 0x01, 0}, {0xb8f, 0x00, 0}, // GR
-{0xb90, 0x02, 0}, {0xb91, 0x2b, 0}, // R
-{0xb92, 0x01, 0}, {0xb93, 0xd4, 0}, // B
-{0xb94, 0x01, 0}, {0xb95, 0x00, 0}, // GB
-
-// phase detection settings
-{0x3058, 0x00, 0}, // NML_NR_EN
-{0x3103, 0x01, 0}, // NML_PD_CAL_ENABLE = enable
-{0x3108, 0x00, 0}, {0x3109, 0x2c, 0}, //PD_AREA_X_OFFSET
-{0x310a, 0x00, 0}, {0x310b, 0x24, 0}, //PD_AREA_Y_OFFSET
-{0x310c, 0x01, 0}, {0x310d, 0xa4, 0}, //PD_AREA_WIDTH
-{0x310e, 0x01, 0}, {0x310f, 0xa4, 0}, //PD_AREA_HEIGHT
-// whole size is 0x918 x 0x6d4
-{0x3110, 0x03, 0}, // PD_AREA_0 = 0x375-0x4d1, 0x258-0x3b6
-{0x3111, 0x75, 0},
-{0x3112, 0x02, 0},
-{0x3113, 0x58, 0},
-{0x3114, 0x04, 0},
-{0x3115, 0xd1, 0},
-{0x3116, 0x03, 0},
-{0x3117, 0xb6, 0},
-{0x3118, 0x04, 0}, // PD_AREA_1 = 0x446-0x5a2, 0x258-0x3b6
-{0x3119, 0x46, 0},
-{0x311a, 0x02, 0},
-{0x311b, 0x58, 0},
-{0x311c, 0x05, 0},
-{0x311d, 0xa2, 0},
-{0x311e, 0x03, 0},
-{0x311f, 0xb6, 0},
-{0x3120, 0x03, 0}, // PD_AREA_2 = 0x375-0x4d1, 0x32a-0x488
-{0x3121, 0x75, 0},
-{0x3122, 0x03, 0},
-{0x3123, 0x2a, 0},
-{0x3124, 0x04, 0},
-{0x3125, 0xd1, 0},
-{0x3126, 0x04, 0},
-{0x3127, 0x88, 0},
-{0x3128, 0x04, 0}, // PD_AREA_3 = 0x446-0x5a2, 0x32a-0x488
-{0x3129, 0x46, 0},
-{0x312a, 0x03, 0},
-{0x312b, 0x2a, 0},
-{0x312c, 0x05, 0},
-{0x312d, 0xa2, 0},
-{0x312e, 0x04, 0},
-{0x312f, 0x88, 0},
-{0x3130, 0x03, 0}, // PD_AREA_4 = 0x375-0x5a2, 0x258-0x488
-{0x3131, 0x75, 0},
-{0x3132, 0x02, 0},
-{0x3133, 0x58, 0},
-{0x3134, 0x05, 0},
-{0x3135, 0xa2, 0},
-{0x3136, 0x04, 0},
-{0x3137, 0x88, 0},
-{0x3138, 0x02, 0}, // PD_AREA_5 = 0x2ba-0x65d, 0x210-0x4d0
-{0x3139, 0xba, 0},
-{0x313a, 0x02, 0},
-{0x313b, 0x10, 0},
-{0x313c, 0x06, 0},
-{0x313d, 0x5d, 0},
-{0x313e, 0x04, 0},
-{0x313f, 0xd0, 0},
-{0x3140, 0x00, 0}, // PD_AREA_6 = 0xa1-0x876, 0x6b-0x676
-{0x3141, 0xa1, 0},
-{0x3142, 0x00, 0},
-{0x3143, 0x6b, 0},
-{0x3144, 0x08, 0},
-{0x3145, 0x76, 0},
-{0x3146, 0x06, 0},
-{0x3147, 0x76, 0},
-{0x3148, 0x00, 0}, // PD_AREA_7 = 0xa1-0x876, 0x5e-0x34c
-{0x3149, 0xa1, 0},
-{0x314a, 0x00, 0},
-{0x314b, 0x5e, 0},
-{0x314c, 0x08, 0},
-{0x314d, 0x76, 0},
-{0x314e, 0x03, 0},
-{0x314f, 0x4c, 0},
-{0x3165, 0x02, 0}, // AREA_EN_0 = free area
-};
-
-// static struct msm_camera_i2c_reg_array reg_array3[] = {
-// // REG_HOLD ON
-// {0x104,0x1,0},
-// // from regression
-// {0x3002,0x0,0},
-// // FRM_LENGTH, 1802 vs 3554
-// // {0x340,0x7,0}, {0x341,0xa,0}, // camera start {0x340,0xd,0}, {0x341,0xe2,0},
-// // INTEG_TIME aka coarse_int_time_addr aka shutter speed
-// {0x202,0x03,0}, {0x203,0xda,0},
-// // global_gain_addr
-// {0x204,0x0,0}, {0x205,0x0,0},
-
-// //??
-// {0x20e,0x1,0}, {0x20f,0x0,0},
-// {0x210,0x1,0}, {0x211,0x0,0},
-// {0x212,0x1,0}, {0x213,0x0,0},
-// {0x214,0x1,0}, {0x215,0x0,0},
-
-// // REG_HOLD: mode setting
-// {0x104,0x0,0},
-// };
-
-// start, remove standby mode
-static struct msm_camera_i2c_reg_array start_reg_array[] = {{0x100,0x1,0}};
-
-// stop, enable standby mode
-static struct msm_camera_i2c_reg_array stop_reg_array[] = {{0x100,0x0,0}};
-
-/*
-// still mode settings:
- {0x847f, 0x0c0c,}, //_M_EQCTL
-
- {0x8436, 0xfd7f,}, //_M_Kgxdr
- {0x8440, 0xf07f,}, //_M_X_LMT
- {0x8443, 0xb41e,}, //_M_X_TGT
- {0x841b, 0x4001,}, //_M_Kgx10
-
- {0x84b6, 0xfd7f,}, //_M_Kgydr
- {0x84c0, 0xf07f,}, //_M_Y_LMT
- {0x84c3, 0xb41e,}, //_M_Y_TGT
- {0x849b, 0x4001,}, //_M_Kgy10
-
- {0x8438, 0x2d0f,}, //_M_Kgx11
- {0x84b8, 0x2d0f,}, //_M_Kgy11
- {0x8447, 0x002b,}, //_M_KgxTG
- {0x84c7, 0x002b,}, //_M_KgyTG
-
- {0x847f, 0x0d0d,}, //_M_EQCTL
-*/
-
-static struct msm_camera_i2c_reg_array init_array_ov8865[] = {
-// round 1
-//{0x103,0x1,0}, // software reset
-{0x100,0x0,0}, // standby on
-{0x3638,0xff,0},
-{0x302,0x1e,0}, {0x303,0x0,0}, {0x304,0x3,0}, {0x30d,0x1e,0}, {0x30e,0x0,0}, {0x30f,0x9,0}, {0x312,0x1,0}, {0x31e,0xc,0}, // PLL control
-{0x3015,0x1,0}, {0x3018,0x72,0}, {0x3020,0x93,0}, {0x3022,0x1,0},
-{0x3031,0xa,0}, // 10-bit mode
-{0x3106,0x1,0}, {0x3305,0xf1,0}, {0x3308,0x0,0}, {0x3309,0x28,0}, {0x330a,0x0,0}, {0x330b,0x20,0}, {0x330c,0x0,0}, {0x330d,0x0,0}, {0x330e,0x0,0}, {0x330f,0x40,0}, {0x3307,0x4,0}, {0x3604,0x4,0}, {0x3602,0x30,0}, {0x3605,0x0,0}, {0x3607,0x20,0}, {0x3608,0x11,0}, {0x3609,0x68,0}, {0x360a,0x40,0}, {0x360c,0xdd,0}, {0x360e,0xc,0}, {0x3610,0x7,0}, {0x3612,0x86,0}, {0x3613,0x58,0}, {0x3614,0x28,0}, {0x3617,0x40,0}, {0x3618,0x5a,0}, {0x3619,0x9b,0}, {0x361c,0x0,0}, {0x361d,0x60,0}, {0x3631,0x60,0}, {0x3633,0x10,0}, {0x3634,0x10,0}, {0x3635,0x10,0}, {0x3636,0x10,0}, {0x3641,0x55,0}, {0x3646,0x86,0}, {0x3647,0x27,0}, {0x364a,0x1b,0}, {0x3500,0x0,0}, {0x3501,0x4c,0}, {0x3502,0x0,0}, {0x3503,0x0,0}, {0x3508,0x2,0},
-{0x3509,0x0,0}, // AEC GAIN
-{0x3700,0x24,0}, {0x3701,0xc,0}, {0x3702,0x28,0}, {0x3703,0x19,0}, {0x3704,0x14,0}, {0x3705,0x0,0}, {0x3706,0x38,0}, {0x3707,0x4,0}, {0x3708,0x24,0}, {0x3709,0x40,0}, {0x370a,0x0,0}, {0x370b,0xb8,0}, {0x370c,0x4,0}, {0x3718,0x12,0}, {0x3719,0x31,0}, {0x3712,0x42,0}, {0x3714,0x12,0}, {0x371e,0x19,0}, {0x371f,0x40,0}, {0x3720,0x5,0}, {0x3721,0x5,0}, {0x3724,0x2,0}, {0x3725,0x2,0}, {0x3726,0x6,0}, {0x3728,0x5,0}, {0x3729,0x2,0}, {0x372a,0x3,0}, {0x372b,0x53,0}, {0x372c,0xa3,0}, {0x372d,0x53,0}, {0x372e,0x6,0}, {0x372f,0x10,0}, {0x3730,0x1,0}, {0x3731,0x6,0}, {0x3732,0x14,0}, {0x3733,0x10,0}, {0x3734,0x40,0}, {0x3736,0x20,0}, {0x373a,0x2,0}, {0x373b,0xc,0}, {0x373c,0xa,0}, {0x373e,0x3,0}, {0x3755,0x40,0}, {0x3758,0x0,0}, {0x3759,0x4c,0}, {0x375a,0x6,0}, {0x375b,0x13,0}, {0x375c,0x40,0}, {0x375d,0x2,0}, {0x375e,0x0,0}, {0x375f,0x14,0}, {0x3767,0x1c,0}, {0x3768,0x4,0}, {0x3769,0x20,0}, {0x376c,0xc0,0}, {0x376d,0xc0,0}, {0x376a,0x8,0}, {0x3761,0x0,0}, {0x3762,0x0,0}, {0x3763,0x0,0}, {0x3766,0xff,0}, {0x376b,0x42,0}, {0x3772,0x23,0}, {0x3773,0x2,0}, {0x3774,0x16,0}, {0x3775,0x12,0}, {0x3776,0x8,0}, {0x37a0,0x44,0}, {0x37a1,0x3d,0}, {0x37a2,0x3d,0}, {0x37a3,0x1,0}, {0x37a4,0x0,0}, {0x37a5,0x8,0}, {0x37a6,0x0,0}, {0x37a7,0x44,0}, {0x37a8,0x58,0}, {0x37a9,0x58,0}, {0x3760,0x0,0}, {0x376f,0x1,0}, {0x37aa,0x44,0}, {0x37ab,0x2e,0}, {0x37ac,0x2e,0}, {0x37ad,0x33,0}, {0x37ae,0xd,0}, {0x37af,0xd,0}, {0x37b0,0x0,0}, {0x37b1,0x0,0}, {0x37b2,0x0,0}, {0x37b3,0x42,0}, {0x37b4,0x42,0}, {0x37b5,0x33,0}, {0x37b6,0x0,0}, {0x37b7,0x0,0}, {0x37b8,0x0,0}, {0x37b9,0xff,0}, {0x3800,0x0,0}, {0x3801,0xc,0}, {0x3802,0x0,0}, {0x3803,0xc,0},
-{0x3804,0xc,0}, {0x3805,0xd3,0}, // 3283
-{0x3806,0x9,0}, {0x3807,0xa3,0}, // 2467
-{0x3808,0x6,0}, {0x3809,0x60,0}, // 0x660 = 1632 (width)
-{0x380a,0x4,0}, {0x380b,0xc8,0}, // 0x4c8 = 1224 (height)
-{0x380c,0x6,0}, {0x380d,0x42,0}, // line_length_pck
-{0x380e,0x5,0}, {0x380f,0xda,0}, // frame_length_lines
-{0x3810,0x0,0}, {0x3811,0x4,0}, {0x3813,0x4,0},
-{0x3814,0x3,0}, {0x3815,0x1,0}, // H-binning
-{0x3820,0x6,0}, // format1
-{0x3821,0x40,0}, // format2
-{0x382a,0x3,0}, {0x382b,0x1,0}, // V-binning
-{0x382d,0x7f,0}, {0x3830,0x8,0}, {0x3836,0x2,0}, {0x3837,0x18,0},
-{0x3841,0xff,0},
-{0x3846,0x88,0}, {0x3d85,0x6,0}, {0x3d8c,0x75,0}, {0x3d8d,0xef,0}, {0x3f08,0xb,0}, {0x4000,0xf1,0}, {0x4001,0x14,0}, {0x4005,0x10,0}, {0x4006,0x1,0}, {0x4007,0x1,0}, {0x400b,0xc,0}, {0x400d,0x10,0}, {0x401b,0x0,0}, {0x401d,0x0,0}, {0x4020,0x0,0}, {0x4021,0x0,0}, {0x4022,0x4,0}, {0x4023,0x1f,0}, {0x4024,0x6,0}, {0x4025,0x20,0}, {0x4026,0x6,0}, {0x4027,0x4f,0}, {0x4028,0x0,0}, {0x4029,0x2,0}, {0x402a,0x4,0}, {0x402b,0x4,0}, {0x402c,0x2,0}, {0x402d,0x2,0}, {0x402e,0x8,0}, {0x402f,0x2,0}, {0x401f,0x0,0}, {0x4034,0x3f,0}, {0x4300,0xff,0}, {0x4301,0x0,0}, {0x4302,0xf,0}, {0x4500,0x40,0}, {0x4503,0x10,0}, {0x4601,0x74,0}, {0x481f,0x32,0}, {0x4837,0x15,0}, {0x4850,0x10,0}, {0x4851,0x32,0}, {0x4b00,0x2a,0}, {0x4b0d,0x0,0}, {0x4d00,0x4,0}, {0x4d01,0x18,0}, {0x4d02,0xc3,0}, {0x4d03,0xff,0}, {0x4d04,0xff,0}, {0x4d05,0xff,0}, {0x5000,0x96,0}, {0x5001,0x1,0}, {0x5002,0x8,0}, {0x5901,0x0,0},
-{0x5e00,0x0,0},
-//{0x5e00,0x80,0},
-{0x5e01,0x41,0}, {0x5b00,0x2,0}, {0x5b01,0xd0,0}, {0x5b02,0x3,0}, {0x5b03,0xff,0}, {0x5b05,0x6c,0}, {0x5780,0xfc,0}, {0x5781,0xdf,0}, {0x5782,0x3f,0}, {0x5783,0x8,0}, {0x5784,0xc,0}, {0x5786,0x20,0}, {0x5787,0x40,0}, {0x5788,0x8,0}, {0x5789,0x8,0}, {0x578a,0x2,0}, {0x578b,0x1,0}, {0x578c,0x1,0}, {0x578d,0xc,0}, {0x578e,0x2,0}, {0x578f,0x1,0}, {0x5790,0x1,0}, {0x5800,0x1d,0}, {0x5801,0xe,0}, {0x5802,0xc,0}, {0x5803,0xc,0}, {0x5804,0xf,0}, {0x5805,0x22,0}, {0x5806,0xa,0}, {0x5807,0x6,0}, {0x5808,0x5,0}, {0x5809,0x5,0}, {0x580a,0x7,0}, {0x580b,0xa,0}, {0x580c,0x6,0}, {0x580d,0x2,0}, {0x580e,0x0,0}, {0x580f,0x0,0}, {0x5810,0x3,0}, {0x5811,0x7,0}, {0x5812,0x6,0}, {0x5813,0x2,0}, {0x5814,0x0,0}, {0x5815,0x0,0}, {0x5816,0x3,0}, {0x5817,0x7,0}, {0x5818,0x9,0}, {0x5819,0x6,0}, {0x581a,0x4,0}, {0x581b,0x4,0}, {0x581c,0x6,0}, {0x581d,0xa,0}, {0x581e,0x19,0}, {0x581f,0xd,0}, {0x5820,0xb,0}, {0x5821,0xb,0}, {0x5822,0xe,0}, {0x5823,0x22,0}, {0x5824,0x23,0}, {0x5825,0x28,0}, {0x5826,0x29,0}, {0x5827,0x27,0}, {0x5828,0x13,0}, {0x5829,0x26,0}, {0x582a,0x33,0}, {0x582b,0x32,0}, {0x582c,0x33,0}, {0x582d,0x16,0}, {0x582e,0x14,0}, {0x582f,0x30,0}, {0x5830,0x31,0}, {0x5831,0x30,0}, {0x5832,0x15,0}, {0x5833,0x26,0}, {0x5834,0x23,0}, {0x5835,0x21,0}, {0x5836,0x23,0}, {0x5837,0x5,0}, {0x5838,0x36,0}, {0x5839,0x27,0}, {0x583a,0x28,0}, {0x583b,0x26,0}, {0x583c,0x24,0}, {0x583d,0xdf,0},
-//{0x100,0x1,0},
-// round 2 (color calibration)
-{0x7010,0x0,0}, {0x7011,0x0,0}, {0x7012,0x0,0}, {0x7013,0x0,0}, {0x7014,0x0,0}, {0x7015,0x0,0}, {0x7016,0x0,0}, {0x7017,0x0,0}, {0x7018,0x0,0}, {0x7019,0x0,0}, {0x701a,0x0,0}, {0x701b,0x0,0}, {0x701c,0x0,0}, {0x701d,0x0,0}, {0x701e,0x0,0}, {0x701f,0x0,0}, {0x7020,0x0,0}, {0x7021,0x0,0}, {0x7022,0x0,0}, {0x7023,0x0,0}, {0x7024,0x0,0}, {0x7025,0x0,0}, {0x7026,0x0,0}, {0x7027,0x0,0}, {0x7028,0x0,0}, {0x7029,0x0,0}, {0x702a,0x0,0}, {0x702b,0x0,0}, {0x702c,0x0,0}, {0x702d,0x0,0}, {0x702e,0x0,0}, {0x702f,0x0,0}, {0x7030,0x0,0}, {0x7031,0x0,0}, {0x7032,0x0,0}, {0x7033,0x0,0}, {0x7034,0x0,0}, {0x7035,0x0,0}, {0x7036,0x0,0}, {0x7037,0x0,0}, {0x7038,0x0,0}, {0x7039,0x0,0}, {0x703a,0x0,0}, {0x703b,0x0,0}, {0x703c,0x0,0}, {0x703d,0x0,0}, {0x703e,0x0,0}, {0x703f,0x0,0}, {0x7040,0x0,0}, {0x7041,0x0,0}, {0x7042,0x0,0}, {0x7043,0x0,0}, {0x7044,0x0,0}, {0x7045,0x0,0}, {0x7046,0x0,0}, {0x7047,0x0,0}, {0x7048,0x0,0}, {0x7049,0x0,0}, {0x704a,0x0,0}, {0x704b,0x0,0}, {0x704c,0x0,0}, {0x704d,0x0,0}, {0x704e,0x0,0}, {0x704f,0x0,0}, {0x7050,0x0,0}, {0x7051,0x0,0}, {0x7052,0x0,0}, {0x7053,0x0,0}, {0x7054,0x0,0}, {0x7055,0x0,0}, {0x7056,0x0,0}, {0x7057,0x0,0}, {0x7058,0x0,0}, {0x7059,0x0,0}, {0x705a,0x0,0}, {0x705b,0x0,0}, {0x705c,0x0,0}, {0x705d,0x0,0}, {0x705e,0x0,0}, {0x705f,0x0,0}, {0x7060,0x0,0}, {0x7061,0x0,0}, {0x7062,0x0,0}, {0x7063,0x0,0}, {0x7064,0x0,0}, {0x7065,0x0,0}, {0x7066,0x0,0}, {0x7067,0x0,0}, {0x7068,0x0,0}, {0x7069,0x0,0}, {0x706a,0x0,0}, {0x706b,0x0,0}, {0x706c,0x0,0}, {0x706d,0x0,0}, {0x706e,0x0,0}, {0x706f,0x0,0}, {0x7070,0x0,0}, {0x7071,0x0,0}, {0x7072,0x0,0}, {0x7073,0x0,0}, {0x7074,0x0,0}, {0x7075,0x0,0}, {0x7076,0x0,0}, {0x7077,0x0,0}, {0x7078,0x0,0}, {0x7079,0x0,0}, {0x707a,0x0,0}, {0x707b,0x0,0}, {0x707c,0x0,0}, {0x707d,0x0,0}, {0x707e,0x0,0}, {0x707f,0x0,0}, {0x7080,0x0,0}, {0x7081,0x0,0}, {0x7082,0x0,0}, {0x7083,0x0,0}, {0x7084,0x0,0}, {0x7085,0x0,0}, {0x7086,0x0,0}, {0x7087,0x0,0}, {0x7088,0x0,0}, {0x7089,0x0,0}, {0x708a,0x0,0}, {0x708b,0x0,0}, {0x708c,0x0,0}, {0x708d,0x0,0}, {0x708e,0x0,0}, {0x708f,0x0,0}, {0x7090,0x0,0}, {0x7091,0x0,0}, {0x7092,0x0,0}, {0x7093,0x0,0}, {0x7094,0x0,0}, {0x7095,0x0,0}, {0x7096,0x0,0}, {0x7097,0x0,0}, {0x7098,0x0,0}, {0x7099,0x0,0}, {0x709a,0x0,0}, {0x709b,0x0,0}, {0x709c,0x0,0}, {0x709d,0x0,0}, {0x709e,0x0,0}, {0x709f,0x0,0}, {0x70a0,0x0,0}, {0x70a1,0x0,0}, {0x70a2,0x0,0}, {0x70a3,0x0,0}, {0x70a4,0x0,0}, {0x70a5,0x0,0}, {0x70a6,0x0,0}, {0x70a7,0x0,0}, {0x70a8,0x0,0}, {0x70a9,0x0,0}, {0x70aa,0x0,0}, {0x70ab,0x0,0}, {0x70ac,0x0,0}, {0x70ad,0x0,0}, {0x70ae,0x0,0}, {0x70af,0x0,0}, {0x70b0,0x0,0}, {0x70b1,0x0,0}, {0x70b2,0x0,0}, {0x70b3,0x0,0}, {0x70b4,0x0,0}, {0x70b5,0x0,0}, {0x70b6,0x0,0}, {0x70b7,0x0,0}, {0x70b8,0x0,0}, {0x70b9,0x0,0}, {0x70ba,0x0,0}, {0x70bb,0x0,0}, {0x70bc,0x0,0}, {0x70bd,0x0,0}, {0x70be,0x0,0}, {0x70bf,0x0,0}, {0x70c0,0x0,0}, {0x70c1,0x0,0}, {0x70c2,0x0,0}, {0x70c3,0x0,0}, {0x70c4,0x0,0}, {0x70c5,0x0,0}, {0x70c6,0x0,0}, {0x70c7,0x0,0}, {0x70c8,0x0,0}, {0x70c9,0x0,0}, {0x70ca,0x0,0}, {0x70cb,0x0,0}, {0x70cc,0x0,0}, {0x70cd,0x0,0}, {0x70ce,0x0,0}, {0x70cf,0x0,0}, {0x70d0,0x0,0}, {0x70d1,0x0,0}, {0x70d2,0x0,0}, {0x70d3,0x0,0}, {0x70d4,0x0,0}, {0x70d5,0x0,0}, {0x70d6,0x0,0}, {0x70d7,0x0,0}, {0x70d8,0x0,0}, {0x70d9,0x0,0}, {0x70da,0x0,0}, {0x70db,0x0,0}, {0x70dc,0x0,0}, {0x70dd,0x0,0}, {0x70de,0x0,0}, {0x70df,0x0,0}, {0x70e0,0x0,0}, {0x70e1,0x0,0}, {0x70e2,0x0,0}, {0x70e3,0x0,0}, {0x70e4,0x0,0}, {0x70e5,0x0,0}, {0x70e6,0x0,0}, {0x70e7,0x0,0}, {0x70e8,0x0,0}, {0x70e9,0x0,0}, {0x70ea,0x0,0}, {0x70eb,0x0,0}, {0x70ec,0x0,0}, {0x70ed,0x0,0}, {0x70ee,0x0,0}, {0x70ef,0x0,0}, {0x70f0,0x0,0}, {0x70f1,0x0,0}, {0x70f2,0x0,0}, {0x70f3,0x0,0}, {0x70f4,0x0,0}, {0x70f5,0x0,0}, {0x70f6,0x0,0}, {0x70f7,0x0,0}, {0x70f8,0x0,0}, {0x70f9,0x0,0}, {0x70fa,0x0,0}, {0x70fb,0x0,0}, {0x70fc,0x0,0}, {0x70fd,0x0,0}, {0x70fe,0x0,0}, {0x70ff,0x0,0}, {0x7100,0x0,0}, {0x7101,0x0,0}, {0x7102,0x0,0}, {0x7103,0x0,0}, {0x7104,0x0,0}, {0x7105,0x0,0}, {0x7106,0x0,0}, {0x7107,0x0,0}, {0x7108,0x0,0}, {0x7109,0x0,0}, {0x710a,0x0,0}, {0x710b,0x0,0}, {0x710c,0x0,0}, {0x710d,0x0,0}, {0x710e,0x0,0}, {0x710f,0x0,0}, {0x7110,0x0,0}, {0x7111,0x0,0}, {0x7112,0x0,0}, {0x7113,0x0,0}, {0x7114,0x0,0}, {0x7115,0x0,0}, {0x7116,0x0,0}, {0x7117,0x0,0}, {0x7118,0x0,0}, {0x7119,0x0,0}, {0x711a,0x0,0}, {0x711b,0x0,0}, {0x711c,0x0,0}, {0x711d,0x0,0}, {0x711e,0x0,0}, {0x711f,0x0,0}, {0x7120,0x0,0}, {0x7121,0x0,0}, {0x7122,0x0,0}, {0x7123,0x0,0}, {0x7124,0x0,0}, {0x7125,0x0,0}, {0x7126,0x0,0}, {0x7127,0x0,0}, {0x7128,0x0,0}, {0x7129,0x0,0}, {0x712a,0x0,0}, {0x712b,0x0,0}, {0x712c,0x0,0}, {0x712d,0x0,0}, {0x712e,0x0,0}, {0x712f,0x0,0}, {0x7130,0x0,0}, {0x7131,0x0,0}, {0x7132,0x0,0}, {0x501a,0x10,0}, {0x501b,0xd,0}, {0x501c,0x10,0}, {0x501d,0x13,0}, {0x5000,0x96,0}, {0x5800,0x14,0}, {0x5801,0xd,0}, {0x5802,0xa,0}, {0x5803,0xa,0}, {0x5804,0xd,0}, {0x5805,0x13,0}, {0x5806,0xa,0}, {0x5807,0x5,0}, {0x5808,0x3,0}, {0x5809,0x3,0}, {0x580a,0x5,0}, {0x580b,0x9,0}, {0x580c,0x6,0}, {0x580d,0x2,0}, {0x580e,0x0,0}, {0x580f,0x0,0}, {0x5810,0x2,0}, {0x5811,0x5,0}, {0x5812,0x6,0}, {0x5813,0x2,0}, {0x5814,0x0,0}, {0x5815,0x0,0}, {0x5816,0x2,0}, {0x5817,0x5,0}, {0x5818,0xb,0}, {0x5819,0x6,0}, {0x581a,0x3,0}, {0x581b,0x3,0}, {0x581c,0x5,0}, {0x581d,0xa,0}, {0x581e,0x16,0}, {0x581f,0xf,0}, {0x5820,0xb,0}, {0x5821,0xb,0}, {0x5822,0xf,0}, {0x5823,0x15,0}, {0x5824,0x32,0}, {0x5825,0x23,0}, {0x5826,0x23,0}, {0x5827,0x23,0}, {0x5828,0x22,0}, {0x5829,0x21,0}, {0x582a,0x21,0}, {0x582b,0x22,0}, {0x582c,0x21,0},{0x582d,0x11,0}, {0x582e,0x22,0}, {0x582f,0x31,0}, {0x5830,0x41,0}, {0x5831,0x31,0}, {0x5832,0x1,0}, {0x5833,0x21,0}, {0x5834,0x21,0}, {0x5835,0x21,0}, {0x5836,0x11,0}, {0x5837,0x11,0}, {0x5838,0x22,0}, {0x5839,0x22,0}, {0x583a,0x12,0}, {0x583b,0x22,0}, {0x583c,0x22,0}, {0x583d,0xdf,0},
-};
-
diff --git a/selfdrive/camerad/main.cc b/selfdrive/camerad/main.cc
deleted file mode 100644
index 668410d6f7..0000000000
--- a/selfdrive/camerad/main.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "selfdrive/camerad/cameras/camera_common.h"
-
-#include
-
-#include "selfdrive/common/params.h"
-#include "selfdrive/common/util.h"
-#include "selfdrive/hardware/hw.h"
-
-int main(int argc, char *argv[]) {
- if (!Hardware::PC()) {
- int ret;
- ret = util::set_realtime_priority(53);
- assert(ret == 0);
- ret = util::set_core_affinity({Hardware::EON() ? 2 : 6});
- assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores
- }
-
- camerad_thread();
- return 0;
-}
diff --git a/selfdrive/camerad/test/test_camerad.py b/selfdrive/camerad/test/test_camerad.py
deleted file mode 100755
index ff37ad1f31..0000000000
--- a/selfdrive/camerad/test/test_camerad.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python3
-
-import time
-import unittest
-
-import cereal.messaging as messaging
-from selfdrive.test.helpers import with_processes
-
-# only tests for EON and TICI
-from selfdrive.hardware import EON, TICI
-
-TEST_TIMESPAN = 30 # random.randint(60, 180) # seconds
-SKIP_FRAME_TOLERANCE = 0
-LAG_FRAME_TOLERANCE = 2 # ms
-
-FPS_BASELINE = 20
-CAMERAS = {
- "roadCameraState": FPS_BASELINE,
- "driverCameraState": FPS_BASELINE // 2,
-}
-
-if TICI:
- CAMERAS["driverCameraState"] = FPS_BASELINE
- CAMERAS["wideRoadCameraState"] = FPS_BASELINE
-
-class TestCamerad(unittest.TestCase):
- @classmethod
- def setUpClass(cls):
- if not (EON or TICI):
- raise unittest.SkipTest
-
- @with_processes(['camerad'])
- def test_frame_packets(self):
- print("checking frame pkts continuity")
- print(TEST_TIMESPAN)
-
- sm = messaging.SubMaster([socket_name for socket_name in CAMERAS])
-
- last_frame_id = dict.fromkeys(CAMERAS, None)
- last_ts = dict.fromkeys(CAMERAS, None)
- start_time_sec = time.time()
- while time.time()- start_time_sec < TEST_TIMESPAN:
- sm.update()
-
- for camera in CAMERAS:
- if sm.updated[camera]:
- ct = (sm[camera].timestampEof if not TICI else sm[camera].timestampSof) / 1e6
- if last_frame_id[camera] is None:
- last_frame_id[camera] = sm[camera].frameId
- last_ts[camera] = ct
- continue
-
- dfid = sm[camera].frameId - last_frame_id[camera]
- self.assertTrue(abs(dfid - 1) <= SKIP_FRAME_TOLERANCE, "%s frame id diff is %d" % (camera, dfid))
-
- dts = ct - last_ts[camera]
- self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, f"{camera} frame t(ms) diff is {dts:f}")
-
- last_frame_id[camera] = sm[camera].frameId
- last_ts[camera] = ct
-
- time.sleep(0.01)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/selfdrive/camerad/transforms/rgb_to_yuv.cc b/selfdrive/camerad/transforms/rgb_to_yuv.cc
deleted file mode 100644
index 63e032e2dc..0000000000
--- a/selfdrive/camerad/transforms/rgb_to_yuv.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "selfdrive/camerad/transforms/rgb_to_yuv.h"
-
-#include
-#include
-
-Rgb2Yuv::Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride) {
- assert(width % 2 == 0 && height % 2 == 0);
- char args[1024];
- snprintf(args, sizeof(args),
- "-cl-fast-relaxed-math -cl-denorms-are-zero "
-#ifdef CL_DEBUG
- "-DCL_DEBUG "
-#endif
- "-DWIDTH=%d -DHEIGHT=%d -DUV_WIDTH=%d -DUV_HEIGHT=%d -DRGB_STRIDE=%d -DRGB_SIZE=%d",
- width, height, width / 2, height / 2, rgb_stride, width * height);
-
- cl_program prg = cl_program_from_file(ctx, device_id, "transforms/rgb_to_yuv.cl", args);
- krnl = CL_CHECK_ERR(clCreateKernel(prg, "rgb_to_yuv", &err));
- CL_CHECK(clReleaseProgram(prg));
-
- work_size[0] = (width + (width % 4 == 0 ? 0 : (4 - width % 4))) / 4;
- work_size[1] = (height + (height % 4 == 0 ? 0 : (4 - height % 4))) / 4;
-}
-
-Rgb2Yuv::~Rgb2Yuv() {
- CL_CHECK(clReleaseKernel(krnl));
-}
-
-void Rgb2Yuv::queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl) {
- CL_CHECK(clSetKernelArg(krnl, 0, sizeof(cl_mem), &rgb_cl));
- CL_CHECK(clSetKernelArg(krnl, 1, sizeof(cl_mem), &yuv_cl));
- cl_event event;
- CL_CHECK(clEnqueueNDRangeKernel(q, krnl, 2, NULL, &work_size[0], NULL, 0, 0, &event));
- CL_CHECK(clWaitForEvents(1, &event));
- CL_CHECK(clReleaseEvent(event));
-}
diff --git a/selfdrive/camerad/transforms/rgb_to_yuv.h b/selfdrive/camerad/transforms/rgb_to_yuv.h
deleted file mode 100644
index 3bb50669ef..0000000000
--- a/selfdrive/camerad/transforms/rgb_to_yuv.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include "selfdrive/common/clutil.h"
-
-class Rgb2Yuv {
-public:
- Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride);
- ~Rgb2Yuv();
- void queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl);
-private:
- size_t work_size[2];
- cl_kernel krnl;
-};
-
diff --git a/selfdrive/camerad/transforms/rgb_to_yuv_test.cc b/selfdrive/camerad/transforms/rgb_to_yuv_test.cc
deleted file mode 100644
index 74db2bc1c2..0000000000
--- a/selfdrive/camerad/transforms/rgb_to_yuv_test.cc
+++ /dev/null
@@ -1,191 +0,0 @@
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifdef ANDROID
-
-#define MAXE 0
-#include
-
-#else
-// The libyuv implementation on ARM is slightly different than on x86
-// Our implementation matches the ARM version, so accept errors of 1
-#define MAXE 1
-
-#endif
-
-#include
-
-#include "libyuv.h"
-#include "selfdrive/camerad/transforms/rgb_to_yuv.h"
-#include "selfdrive/common/clutil.h"
-
-static inline double millis_since_boot() {
- struct timespec t;
- clock_gettime(CLOCK_BOOTTIME, &t);
- return t.tv_sec * 1000.0 + t.tv_nsec * 1e-6;
-}
-
-void cl_init(cl_device_id &device_id, cl_context &context) {
- device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT);
- context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
-}
-
-
-bool compare_results(uint8_t *a, uint8_t *b, int len, int stride, int width, int height, uint8_t *rgb) {
- int min_diff = 0., max_diff = 0., max_e = 0.;
- int e1 = 0, e0 = 0;
- int e0y = 0, e0u = 0, e0v = 0, e1y = 0, e1u = 0, e1v = 0;
- int max_e_i = 0;
- for (int i = 0;i < len;i++) {
- int e = ((int)a[i]) - ((int)b[i]);
- if(e < min_diff) {
- min_diff = e;
- }
- if(e > max_diff) {
- max_diff = e;
- }
- int e_abs = std::abs(e);
- if(e_abs > max_e) {
- max_e = e_abs;
- max_e_i = i;
- }
- if(e_abs < 1) {
- e0++;
- if(i < stride * height)
- e0y++;
- else if(i < stride * height + stride * height / 4)
- e0u++;
- else
- e0v++;
- } else {
- e1++;
- if(i < stride * height)
- e1y++;
- else if(i < stride * height + stride * height / 4)
- e1u++;
- else
- e1v++;
- }
- }
- //printf("max diff : %d, min diff : %d, e < 1: %d, e >= 1: %d\n", max_diff, min_diff, e0, e1);
- //printf("Y: e < 1: %d, e >= 1: %d, U: e < 1: %d, e >= 1: %d, V: e < 1: %d, e >= 1: %d\n", e0y, e1y, e0u, e1u, e0v, e1v);
- if(max_e <= MAXE) {
- return true;
- }
- int row = max_e_i / stride;
- if(row < height) {
- printf("max error is Y: %d = (libyuv: %u - cl: %u), row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], row, max_e_i % stride);
- } else if(row >= height && row < (height + height / 4)) {
- printf("max error is U: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height) / 2, max_e_i % stride / 2);
- } else {
- printf("max error is V: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height - height / 4) / 2, max_e_i % stride / 2);
- }
- return false;
-}
-
-int main(int argc, char** argv) {
- srand(1337);
-
- cl_device_id device_id;
- cl_context context;
- cl_init(device_id, context) ;
-
- int err;
- const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0};
- cl_command_queue q = clCreateCommandQueueWithProperties(context, device_id, props, &err);
- if(err != 0) {
- std::cout << "clCreateCommandQueueWithProperties error: " << err << std::endl;
- }
-
- int width = 1164;
- int height = 874;
-
- int opt = 0;
- while ((opt = getopt(argc, argv, "f")) != -1)
- {
- switch (opt)
- {
- case 'f':
- std::cout << "Using front camera dimensions" << std::endl;
- int width = 1152;
- int height = 846;
- }
- }
-
- std::cout << "Width: " << width << " Height: " << height << std::endl;
- uint8_t *rgb_frame = new uint8_t[width * height * 3];
-
-
- RGBToYUVState rgb_to_yuv_state;
- rgb_to_yuv_init(&rgb_to_yuv_state, context, device_id, width, height, width * 3);
-
- int frame_yuv_buf_size = width * height * 3 / 2;
- cl_mem yuv_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, frame_yuv_buf_size, (void*)NULL, &err));
- uint8_t *frame_yuv_buf = new uint8_t[frame_yuv_buf_size];
- uint8_t *frame_yuv_ptr_y = frame_yuv_buf;
- uint8_t *frame_yuv_ptr_u = frame_yuv_buf + (width * height);
- uint8_t *frame_yuv_ptr_v = frame_yuv_ptr_u + ((width/2) * (height/2));
-
- cl_mem rgb_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, width * height * 3, (void*)NULL, &err));
- int mismatched = 0;
- int counter = 0;
- srand (time(NULL));
-
- for (int i = 0; i < 100; i++) {
- for (int i = 0; i < width * height * 3; i++) {
- rgb_frame[i] = (uint8_t)rand();
- }
-
- double t1 = millis_since_boot();
- libyuv::RGB24ToI420((uint8_t*)rgb_frame, width * 3,
- frame_yuv_ptr_y, width,
- frame_yuv_ptr_u, width/2,
- frame_yuv_ptr_v, width/2,
- width, height);
- double t2 = millis_since_boot();
- //printf("Libyuv: rgb to yuv: %.2fms\n", t2-t1);
-
- clEnqueueWriteBuffer(q, rgb_cl, CL_TRUE, 0, width * height * 3, (void *)rgb_frame, 0, NULL, NULL);
- t1 = millis_since_boot();
- rgb_to_yuv_queue(&rgb_to_yuv_state, q, rgb_cl, yuv_cl);
- t2 = millis_since_boot();
-
- //printf("OpenCL: rgb to yuv: %.2fms\n", t2-t1);
- uint8_t *yyy = (uint8_t *)clEnqueueMapBuffer(q, yuv_cl, CL_TRUE,
- CL_MAP_READ, 0, frame_yuv_buf_size,
- 0, NULL, NULL, &err);
- if(!compare_results(frame_yuv_ptr_y, yyy, frame_yuv_buf_size, width, width, height, (uint8_t*)rgb_frame))
- mismatched++;
- clEnqueueUnmapMemObject(q, yuv_cl, yyy, 0, NULL, NULL);
-
- // std::this_thread::sleep_for(std::chrono::milliseconds(20));
- if(counter++ % 100 == 0)
- printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched);
-
- }
- printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched);
-
- delete[] frame_yuv_buf;
- rgb_to_yuv_destroy(&rgb_to_yuv_state);
- clReleaseContext(context);
- delete[] rgb_frame;
-
- if (mismatched == 0)
- return 0;
- else
- return -1;
-}
diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md
new file mode 100644
index 0000000000..2fce0f9036
--- /dev/null
+++ b/selfdrive/car/CARS_template.md
@@ -0,0 +1,66 @@
+{% set footnote_tag = '[{}](#footnotes)' -%}
+{% set star_icon = '[](##)' -%}
+
+
+
+# Supported Cars
+
+A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system.
+
+# {{all_car_info | length}} Supported Cars
+
+|{{Column | map(attribute='value') | join('|')}}|
+|---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%}
+{% for car_info in all_car_info %}
+|{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %}
+
+{% endfor %}
+
+
+{% for footnote in footnotes %}
+{{loop.index}}{{footnote}}
+{% endfor %}
+
+## Community Maintained Cars
+Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).
+
+# Don't see your car here?
+
+**openpilot can support many more cars than it currently does.** There are a few reasons your car may not be supported.
+If your car doesn't fit into any of the incompatibility criteria here, then there's a good chance it can be supported! We're adding support for new cars all the time. **We don't have a roadmap for car support**, and in fact, most car support comes from users like you!
+
+### Which cars are able to be supported?
+
+openpilot uses the existing steering, gas, and brake interfaces in your car. If your car lacks any one of these interfaces, openpilot will not be able to control the car. If your car has [ACC](https://en.wikipedia.org/wiki/Adaptive_cruise_control) and any form of [LKAS](https://en.wikipedia.org/wiki/Automated_Lane_Keeping_Systems)/[LCA](https://en.wikipedia.org/wiki/Lane_centering), then it almost certainly has these interfaces. These features generally started shipping on cars around 2016. Note that manufacturers will often make their own [marketing terms](https://en.wikipedia.org/wiki/Adaptive_cruise_control#Vehicle_models_supporting_adaptive_cruise_control) for these features, such as Hyundai's "Smart Cruise Control" branding of Adaptive Cruise Control.
+
+If your car has the following packages or features, then it's a good candidate for support.
+
+| Make | Required Package/Features |
+| ---- | ------------------------- |
+| Acura | Any car with AcuraWatch Plus will work. AcuraWatch Plus comes standard on many newer models. |
+| Honda | Any car with Honda Sensing will work. Honda Sensing comes standard on many newer models. |
+| Subaru | Any car with EyeSight will work. EyeSight comes standard on many newer models. |
+| Nissan | Any car with ProPILOT will likely work. |
+| Toyota & Lexus | Any car that has Toyota/Lexus Safety Sense with "Lane Departure Alert with Steering Assist (LDA w/SA)" and/or "Lane Tracing Assist (LTA)" will work. Note that LDA without Steering Assist will not work. These features come standard on most newer models. |
+| Hyundai, Kia, & Genesis | Any car with Smart Cruise Control (SCC) and Lane Following Assist (LFA) or Lane Keeping Assist (LKAS) will work. LKAS/LFA comes standard on most newer models. Any form of SCC will work, such as NSCC. |
+| Chrysler, Jeep, & Ram | Any car with LaneSense and Adaptive Cruise Control will likely work. These come standard on many newer models. |
+
+### FlexRay
+
+All the cars that openpilot supports use a [CAN bus](https://en.wikipedia.org/wiki/CAN_bus) for communication between all the car's computers, however a CAN bus isn't the only way that the cars in your computer can communicate. Most, if not all, vehicles from the following manufacturers use [FlexRay](https://en.wikipedia.org/wiki/FlexRay) instead of a CAN bus: **BMW, Mercedes, Audi, Land Rover, and some Volvo**. These cars may one day be supported, but we have no immediate plans to support FlexRay.
+
+### Toyota Security
+
+openpilot does not yet support these Toyota models due to a new message authentication method.
+[Vote](https://comma.ai/shop/products/vote) if you'd like to see openpilot support on these models.
+
+* Toyota RAV4 Prime 2021+
+* Toyota Sienna 2021+
+* Toyota Venza 2021+
+* Toyota Sequoia 2023+
+* Toyota Tundra 2022+
+* Toyota Corolla Cross 2022+ (only US model)
+* Lexus NX 2022+
+* Toyota bZ4x 2023+
+* Subaru Solterra 2023+
+
diff --git a/selfdrive/car/README.MD b/selfdrive/car/README.MD
index 244757c136..de4db0ee50 100644
--- a/selfdrive/car/README.MD
+++ b/selfdrive/car/README.MD
@@ -1,5 +1,5 @@
## Port structure
-##### interace.py
+##### interface.py
Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car)
##### carcontroller.py
Builds CAN messages to send to car
diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py
index 408e0d075f..491c551b1b 100644
--- a/selfdrive/car/__init__.py
+++ b/selfdrive/car/__init__.py
@@ -1,13 +1,39 @@
# functions common among cars
+import capnp
+
from cereal import car
from common.numpy_fast import clip
+from typing import Dict
# kg of standard extra cargo to count for drive, gas, etc...
STD_CARGO_KG = 136.
+ButtonType = car.CarState.ButtonEvent.Type
+EventName = car.CarEvent.EventName
+
+
+def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float:
+ if val > val_steady + hyst_gap:
+ val_steady = val - hyst_gap
+ elif val < val_steady - hyst_gap:
+ val_steady = val + hyst_gap
+ return val_steady
+
+
+def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule],
+ unpressed: int = 0) -> capnp.lib.capnp._DynamicStructBuilder:
+ if cur_but != unpressed:
+ be = car.CarState.ButtonEvent(pressed=True)
+ but = cur_but
+ else:
+ be = car.CarState.ButtonEvent(pressed=False)
+ but = prev_but
+ be.type = buttons_dict.get(but, ButtonType.unknown)
+ return be
+
def gen_empty_fingerprint():
- return {i: {} for i in range(0, 4)}
+ return {i: {} for i in range(0, 8)}
# FIXME: hardcoding honda civic 2016 touring params so they can be used to
@@ -41,7 +67,7 @@ def scale_tire_stiffness(mass, wheelbase, center_to_front, tire_stiffness_factor
return tire_stiffness_front, tire_stiffness_rear
-def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None):
+def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> Dict[str, str]:
return {'pt': pt_dbc, 'radar': radar_dbc, 'chassis': chassis_dbc, 'body': body_dbc}
diff --git a/selfdrive/camerad/__init__.py b/selfdrive/car/body/__init__.py
similarity index 100%
rename from selfdrive/camerad/__init__.py
rename to selfdrive/car/body/__init__.py
diff --git a/selfdrive/car/body/bodycan.py b/selfdrive/car/body/bodycan.py
new file mode 100644
index 0000000000..580e5025ad
--- /dev/null
+++ b/selfdrive/car/body/bodycan.py
@@ -0,0 +1,7 @@
+def create_control(packer, torque_l, torque_r):
+ values = {
+ "TORQUE_L": torque_l,
+ "TORQUE_R": torque_r,
+ }
+
+ return packer.make_can_msg("TORQUE_CMD", 0, values)
diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py
new file mode 100644
index 0000000000..0d5d780bd3
--- /dev/null
+++ b/selfdrive/car/body/carcontroller.py
@@ -0,0 +1,90 @@
+import numpy as np
+
+from common.realtime import DT_CTRL
+from opendbc.can.packer import CANPacker
+from selfdrive.car.body import bodycan
+from selfdrive.car.body.values import SPEED_FROM_RPM
+from selfdrive.controls.lib.pid import PIDController
+
+
+MAX_TORQUE = 500
+MAX_TORQUE_RATE = 50
+MAX_ANGLE_ERROR = np.radians(7)
+MAX_POS_INTEGRATOR = 0.2 # meters
+MAX_TURN_INTEGRATOR = 0.1 # meters
+
+
+class CarController:
+ def __init__(self, dbc_name, CP, VM):
+ self.frame = 0
+ self.packer = CANPacker(dbc_name)
+
+ # Speed, balance and turn PIDs
+ self.speed_pid = PIDController(0.115, k_i=0.23, rate=1/DT_CTRL)
+ self.balance_pid = PIDController(1300, k_i=0, k_d=280, rate=1/DT_CTRL)
+ self.turn_pid = PIDController(110, k_i=11.5, rate=1/DT_CTRL)
+
+ self.torque_r_filtered = 0.
+ self.torque_l_filtered = 0.
+
+ @staticmethod
+ def deadband_filter(torque, deadband):
+ if torque > 0:
+ torque += deadband
+ else:
+ torque -= deadband
+ return torque
+
+ def update(self, CC, CS):
+
+ torque_l = 0
+ torque_r = 0
+
+ llk_valid = len(CC.orientationNED) > 0 and len(CC.angularVelocity) > 0
+ if CC.enabled and llk_valid:
+ # Read these from the joystick
+ # TODO: this isn't acceleration, okay?
+ speed_desired = CC.actuators.accel / 5.
+ speed_diff_desired = -CC.actuators.steer
+
+ speed_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl + CS.out.wheelSpeeds.fr) / 2.
+ speed_error = speed_desired - speed_measured
+
+ freeze_integrator = ((speed_error < 0 and self.speed_pid.error_integral <= -MAX_POS_INTEGRATOR) or
+ (speed_error > 0 and self.speed_pid.error_integral >= MAX_POS_INTEGRATOR))
+ angle_setpoint = self.speed_pid.update(speed_error, freeze_integrator=freeze_integrator)
+
+ # Clip angle error, this is enough to get up from stands
+ angle_error = np.clip((-CC.orientationNED[1]) - angle_setpoint, -MAX_ANGLE_ERROR, MAX_ANGLE_ERROR)
+ angle_error_rate = np.clip(-CC.angularVelocity[1], -1., 1.)
+ torque = self.balance_pid.update(angle_error, error_rate=angle_error_rate)
+
+ speed_diff_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl - CS.out.wheelSpeeds.fr)
+ turn_error = speed_diff_measured - speed_diff_desired
+ freeze_integrator = ((turn_error < 0 and self.turn_pid.error_integral <= -MAX_TURN_INTEGRATOR) or
+ (turn_error > 0 and self.turn_pid.error_integral >= MAX_TURN_INTEGRATOR))
+ torque_diff = self.turn_pid.update(turn_error, freeze_integrator=freeze_integrator)
+
+ # Combine 2 PIDs outputs
+ torque_r = torque + torque_diff
+ torque_l = torque - torque_diff
+
+ # Torque rate limits
+ self.torque_r_filtered = np.clip(self.deadband_filter(torque_r, 10),
+ self.torque_r_filtered - MAX_TORQUE_RATE,
+ self.torque_r_filtered + MAX_TORQUE_RATE)
+ self.torque_l_filtered = np.clip(self.deadband_filter(torque_l, 10),
+ self.torque_l_filtered - MAX_TORQUE_RATE,
+ self.torque_l_filtered + MAX_TORQUE_RATE)
+ torque_r = int(np.clip(self.torque_r_filtered, -MAX_TORQUE, MAX_TORQUE))
+ torque_l = int(np.clip(self.torque_l_filtered, -MAX_TORQUE, MAX_TORQUE))
+
+ can_sends = []
+ can_sends.append(bodycan.create_control(self.packer, torque_l, torque_r))
+
+ new_actuators = CC.actuators.copy()
+ new_actuators.accel = torque_l
+ new_actuators.steer = torque_r
+
+ self.frame += 1
+ return new_actuators, can_sends
diff --git a/selfdrive/car/body/carstate.py b/selfdrive/car/body/carstate.py
new file mode 100644
index 0000000000..dbbd85950d
--- /dev/null
+++ b/selfdrive/car/body/carstate.py
@@ -0,0 +1,60 @@
+from cereal import car
+from opendbc.can.parser import CANParser
+from selfdrive.car.interfaces import CarStateBase
+from selfdrive.car.body.values import DBC
+
+STARTUP_TICKS = 100
+
+class CarState(CarStateBase):
+ def update(self, cp):
+ ret = car.CarState.new_message()
+
+ ret.wheelSpeeds.fl = cp.vl['MOTORS_DATA']['SPEED_L']
+ ret.wheelSpeeds.fr = cp.vl['MOTORS_DATA']['SPEED_R']
+
+ ret.vEgoRaw = ((ret.wheelSpeeds.fl + ret.wheelSpeeds.fr) / 2.) * self.CP.wheelSpeedFactor
+
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.standstill = False
+
+ ret.steerFaultPermanent = any([cp.vl['VAR_VALUES']['MOTOR_ERR_L'], cp.vl['VAR_VALUES']['MOTOR_ERR_R'],
+ cp.vl['VAR_VALUES']['FAULT']])
+
+ ret.charging = cp.vl["BODY_DATA"]["CHARGER_CONNECTED"] == 1
+ ret.fuelGauge = cp.vl["BODY_DATA"]["BATT_PERCENTAGE"] / 100
+
+ # irrelevant for non-car
+ ret.gearShifter = car.CarState.GearShifter.drive
+ ret.cruiseState.enabled = True
+ ret.cruiseState.available = True
+
+ return ret
+
+ @staticmethod
+ def get_can_parser(CP):
+ signals = [
+ # sig_name, sig_address
+ ("SPEED_L", "MOTORS_DATA"),
+ ("SPEED_R", "MOTORS_DATA"),
+ ("ELEC_ANGLE_L", "MOTORS_DATA"),
+ ("ELEC_ANGLE_R", "MOTORS_DATA"),
+ ("COUNTER", "MOTORS_DATA"),
+ ("CHECKSUM", "MOTORS_DATA"),
+ ("IGNITION", "VAR_VALUES"),
+ ("ENABLE_MOTORS", "VAR_VALUES"),
+ ("FAULT", "VAR_VALUES"),
+ ("MOTOR_ERR_L", "VAR_VALUES"),
+ ("MOTOR_ERR_R", "VAR_VALUES"),
+ ("MCU_TEMP", "BODY_DATA"),
+ ("BATT_VOLTAGE", "BODY_DATA"),
+ ("BATT_PERCENTAGE", "BODY_DATA"),
+ ("CHARGER_CONNECTED", "BODY_DATA"),
+ ]
+
+ checks = [
+ ("MOTORS_DATA", 100),
+ ("VAR_VALUES", 10),
+ ("BODY_DATA", 1),
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)
diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py
new file mode 100644
index 0000000000..bc5b36e2ee
--- /dev/null
+++ b/selfdrive/car/body/interface.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+import math
+from cereal import car
+from common.realtime import DT_CTRL
+from selfdrive.car import get_safety_config
+from selfdrive.car.interfaces import CarInterfaceBase
+from selfdrive.car.body.values import SPEED_FROM_RPM
+
+class CarInterface(CarInterfaceBase):
+ @staticmethod
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
+ ret.notCar = True
+ ret.carName = "body"
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)]
+
+ ret.minSteerSpeed = -math.inf
+ ret.maxLateralAccel = math.inf # TODO: set to a reasonable value
+ ret.steerRatio = 0.5
+ ret.steerLimitTimer = 1.0
+ ret.steerActuatorDelay = 0.
+
+ ret.mass = 9
+ ret.wheelbase = 0.406
+ ret.wheelSpeedFactor = SPEED_FROM_RPM
+ ret.centerToFront = ret.wheelbase * 0.44
+
+ ret.radarOffCan = True
+ ret.openpilotLongitudinalControl = True
+ ret.steerControlType = car.CarParams.SteerControlType.angle
+
+ return ret
+
+ def _update(self, c):
+ ret = self.CS.update(self.cp)
+
+ # wait for everything to init first
+ if self.frame > int(5. / DT_CTRL):
+ # body always wants to enable
+ ret.init('events', 1)
+ ret.events[0].name = car.CarEvent.EventName.pcmEnable
+ ret.events[0].enable = True
+ self.frame += 1
+
+ return ret
+
+ def apply(self, c):
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/body/radar_interface.py b/selfdrive/car/body/radar_interface.py
new file mode 100644
index 0000000000..b2f7651136
--- /dev/null
+++ b/selfdrive/car/body/radar_interface.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+from selfdrive.car.interfaces import RadarInterfaceBase
+
+class RadarInterface(RadarInterfaceBase):
+ pass
diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py
new file mode 100644
index 0000000000..548039bc70
--- /dev/null
+++ b/selfdrive/car/body/values.py
@@ -0,0 +1,62 @@
+from typing import Dict
+
+from cereal import car
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
+Ecu = car.CarParams.Ecu
+
+SPEED_FROM_RPM = 0.008587
+
+
+class CarControllerParams:
+ ANGLE_DELTA_BP = [0., 5., 15.]
+ ANGLE_DELTA_V = [5., .8, .15] # windup limit
+ ANGLE_DELTA_VU = [5., 3.5, 0.4] # unwind limit
+ LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower
+ STEER_THRESHOLD = 1.0
+
+
+class CAR:
+ BODY = "COMMA BODY"
+
+
+CAR_INFO: Dict[str, CarInfo] = {
+ CAR.BODY: CarInfo("comma body", package="All"),
+}
+
+FINGERPRINTS = {
+ CAR.BODY: [{
+ 513: 8, 516: 8, 514: 3, 515: 4,
+ }],
+}
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
+ bus=0,
+ ),
+ ],
+)
+
+FW_VERSIONS = {
+ CAR.BODY: {
+ (Ecu.engine, 0x720, None): [
+ b'0.0.01',
+ b'02/27/2022',
+ b'0.3.00a',
+ ],
+ # git hash of the firmware used
+ (Ecu.debug, 0x721, None): [
+ b'166bd860',
+ b'dc780f85',
+ ],
+ },
+}
+
+DBC = {
+ CAR.BODY: dbc_dict('comma_body', None),
+}
diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py
index 90bfdd8a93..61e7a3d55d 100644
--- a/selfdrive/car/car_helpers.py
+++ b/selfdrive/car/car_helpers.py
@@ -1,15 +1,18 @@
import os
+from typing import Dict, List
+
+from cereal import car
from common.params import Params
from common.basedir import BASEDIR
-from selfdrive.version import is_comma_remote, is_tested_branch
+from system.version import is_comma_remote, is_tested_branch
+from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
-from selfdrive.car.vin import get_vin, VIN_UNKNOWN
-from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car
-from selfdrive.swaglog import cloudlog
+from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
+from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus
+from system.swaglog import cloudlog
import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint
-from cereal import car
EventName = car.CarEvent.EventName
@@ -57,19 +60,12 @@ def load_interfaces(brand_names):
return ret
-def _get_interface_names():
- # read all the folders in selfdrive/car and return a dict where:
- # - keys are all the car names that which we have an interface for
- # - values are lists of spefic car models for a given car
+def _get_interface_names() -> Dict[str, List[str]]:
+ # returns a dict of brand name and its respective models
brand_names = {}
- for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]:
- try:
- brand_name = car_folder.split('/')[-1]
- model_names = __import__(f'selfdrive.car.{brand_name}.values', fromlist=['CAR']).CAR
- model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")]
- brand_names[brand_name] = model_names
- except (ImportError, OSError):
- pass
+ for brand_name, model_names in get_interface_attr("CAR").items():
+ model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")]
+ brand_names[brand_name] = model_names
return brand_names
@@ -80,12 +76,13 @@ interfaces = load_interfaces(interface_names)
# **** for use live only ****
-def fingerprint(logcan, sendcan):
+def fingerprint(logcan, sendcan, num_pandas):
fixed_fingerprint = os.environ.get('FINGERPRINT', "")
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
+ ecu_rx_addrs = set()
- if not fixed_fingerprint and not skip_fw_query:
- # Vin query only reliably works thorugh OBDII
+ if not skip_fw_query:
+ # Vin query only reliably works through OBDII
bus = 1
cached_params = Params().get("CarParamsCache")
@@ -96,28 +93,38 @@ def fingerprint(logcan, sendcan):
if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN:
cloudlog.warning("Using cached CarParams")
- vin = cached_params.carVin
+ vin, vin_rx_addr = cached_params.carVin, 0
car_fw = list(cached_params.carFw)
+ cached = True
else:
cloudlog.warning("Getting VIN & FW versions")
- _, vin = get_vin(logcan, sendcan, bus)
- car_fw = get_fw_versions(logcan, sendcan, bus)
+ vin_rx_addr, vin = get_vin(logcan, sendcan, bus)
+ ecu_rx_addrs = get_present_ecus(logcan, sendcan)
+ car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas)
+ cached = False
exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
else:
- vin = VIN_UNKNOWN
+ vin, vin_rx_addr = VIN_UNKNOWN, 0
exact_fw_match, fw_candidates, car_fw = True, set(), []
+ cached = False
+ if not is_valid_vin(vin):
+ cloudlog.event("Malformed VIN", vin=vin, error=True)
+ vin = VIN_UNKNOWN
cloudlog.warning("VIN %s", vin)
Params().put("CarVin", vin)
finger = gen_empty_fingerprint()
candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1
frame = 0
- frame_fingerprint = 10 # 0.1s
+ frame_fingerprint = 100 # 1s
car_fingerprint = None
done = False
+ # drain CAN socket so we always get the latest messages
+ messaging.drain_sock_raw(logcan)
+
while not done:
a = get_one_can(logcan)
@@ -161,23 +168,25 @@ def fingerprint(logcan, sendcan):
car_fingerprint = fixed_fingerprint
source = car.CarParams.FingerprintSource.fixed
- cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint,
- source=source, fuzzy=not exact_match, fw_count=len(car_fw))
+ cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached,
+ fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, error=True)
return car_fingerprint, finger, vin, car_fw, source, exact_match
-def get_car(logcan, sendcan):
- candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan)
+def get_car(logcan, sendcan, num_pandas=1):
+ candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas)
if candidate is None:
cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints)
candidate = "mock"
+ experimental_long = Params().get_bool("ExperimentalLongitudinalEnabled")
+
CarInterface, CarController, CarState = interfaces[candidate]
- car_params = CarInterface.get_params(candidate, fingerprints, car_fw)
- car_params.carVin = vin
- car_params.carFw = car_fw
- car_params.fingerprintSource = source
- car_params.fuzzyFingerprint = not exact_match
+ CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long)
+ CP.carVin = vin
+ CP.carFw = car_fw
+ CP.fingerprintSource = source
+ CP.fuzzyFingerprint = not exact_match
- return CarInterface(car_params, CarController, CarState), car_params
+ return CarInterface(CP, CarController, CarState), CP
diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py
index d491ccd4b9..879da88123 100644
--- a/selfdrive/car/chrysler/carcontroller.py
+++ b/selfdrive/car/chrysler/carcontroller.py
@@ -1,73 +1,82 @@
-from cereal import car
-from selfdrive.car import apply_toyota_steer_torque_limits
-from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, \
- create_wheel_buttons
-from selfdrive.car.chrysler.values import CAR, CarControllerParams
from opendbc.can.packer import CANPacker
+from common.realtime import DT_CTRL
+from selfdrive.car import apply_toyota_steer_torque_limits
+from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons
+from selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags
+
-class CarController():
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
self.apply_steer_last = 0
- self.ccframe = 0
- self.prev_frame = -1
+ self.frame = 0
+
self.hud_count = 0
- self.car_fingerprint = CP.carFingerprint
- self.gone_fast_yet = False
- self.steer_rate_limited = False
+ self.last_lkas_falling_edge = 0
+ self.lkas_control_bit_prev = False
+ self.last_button_frame = 0
self.packer = CANPacker(dbc_name)
+ self.params = CarControllerParams(CP)
- def update(self, enabled, CS, actuators, pcm_cancel_cmd, hud_alert):
- # this seems needed to avoid steering faults and to force the sync with the EPS counter
- frame = CS.lkas_counter
- if self.prev_frame == frame:
- return car.CarControl.Actuators.new_message(), []
-
- # steer torque
- new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX))
- apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last,
- CS.out.steeringTorqueEps, CarControllerParams)
- self.steer_rate_limited = new_steer != apply_steer
+ def update(self, CC, CS):
+ can_sends = []
- moving_fast = CS.out.vEgo > CS.CP.minSteerSpeed # for status message
- if CS.out.vEgo > (CS.CP.minSteerSpeed - 0.5): # for command high bit
- self.gone_fast_yet = True
- elif self.car_fingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019):
- if CS.out.vEgo < (CS.CP.minSteerSpeed - 3.0):
- self.gone_fast_yet = False # < 14.5m/s stock turns off this bit, but fine down to 13.5
- lkas_active = moving_fast and enabled
+ lkas_active = CC.latActive and self.lkas_control_bit_prev
- if not lkas_active:
- apply_steer = 0
+ # cruise buttons
+ if (self.frame - self.last_button_frame)*DT_CTRL > 0.05:
+ das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0
- self.apply_steer_last = apply_steer
+ # ACC cancellation
+ if CC.cruiseControl.cancel:
+ self.last_button_frame = self.frame
+ can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True))
- can_sends = []
+ # ACC resume from standstill
+ elif CC.cruiseControl.resume:
+ self.last_button_frame = self.frame
+ can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True))
- #*** control msgs ***
-
- if pcm_cancel_cmd:
- # TODO: would be better to start from frame_2b3
- new_msg = create_wheel_buttons(self.packer, self.ccframe, cancel=True)
- can_sends.append(new_msg)
-
- # LKAS_HEARTBIT is forwarded by Panda so no need to send it here.
- # frame is 100Hz (0.01s period)
- if (self.ccframe % 25 == 0): # 0.25s period
- if (CS.lkas_car_model != -1):
- new_msg = create_lkas_hud(
- self.packer, CS.out.gearShifter, lkas_active, hud_alert,
- self.hud_count, CS.lkas_car_model)
- can_sends.append(new_msg)
+ # HUD alerts
+ if self.frame % 25 == 0:
+ if CS.lkas_car_model != -1:
+ can_sends.append(create_lkas_hud(self.packer, self.CP, lkas_active, CC.hudControl.visualAlert, self.hud_count, CS.lkas_car_model, CS.auto_high_beam))
self.hud_count += 1
- new_msg = create_lkas_command(self.packer, int(apply_steer), self.gone_fast_yet, frame)
- can_sends.append(new_msg)
+ # steering
+ if self.frame % 2 == 0:
+
+ # TODO: can we make this more sane? why is it different for all the cars?
+ lkas_control_bit = self.lkas_control_bit_prev
+ if CS.out.vEgo > self.CP.minSteerSpeed:
+ lkas_control_bit = True
+ elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED:
+ if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0):
+ lkas_control_bit = False
+ elif self.CP.carFingerprint in RAM_CARS:
+ if CS.out.vEgo < (self.CP.minSteerSpeed - 0.5):
+ lkas_control_bit = False
+
+ # EPS faults if LKAS re-enables too quickly
+ lkas_control_bit = lkas_control_bit and (self.frame - self.last_lkas_falling_edge > 200)
+
+ if not lkas_control_bit and self.lkas_control_bit_prev:
+ self.last_lkas_falling_edge = self.frame
+ self.lkas_control_bit_prev = lkas_control_bit
+
+ # steer torque
+ new_steer = int(round(CC.actuators.steer * self.params.STEER_MAX))
+ apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, self.params)
+ if not lkas_active or not lkas_control_bit:
+ apply_steer = 0
+ self.apply_steer_last = apply_steer
+
+ can_sends.append(create_lkas_command(self.packer, self.CP, int(apply_steer), lkas_control_bit))
- self.ccframe += 1
- self.prev_frame = frame
+ self.frame += 1
- new_actuators = actuators.copy()
- new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX
+ new_actuators = CC.actuators.copy()
+ new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX
return new_actuators, can_sends
diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py
index 30a0bf6a3a..0f0d30782a 100644
--- a/selfdrive/car/chrysler/carstate.py
+++ b/selfdrive/car/chrysler/carstate.py
@@ -1,147 +1,205 @@
from cereal import car
+from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
from opendbc.can.can_define import CANDefine
-from selfdrive.config import Conversions as CV
from selfdrive.car.interfaces import CarStateBase
-from selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD
+from selfdrive.car.chrysler.values import DBC, STEER_THRESHOLD, RAM_CARS
class CarState(CarStateBase):
def __init__(self, CP):
super().__init__(CP)
+ self.CP = CP
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
- self.shifter_values = can_define.dv["GEAR"]["PRNDL"]
+
+ self.auto_high_beam = 0
+ self.button_counter = 0
+ self.lkas_car_model = -1
+
+ if CP.carFingerprint in RAM_CARS:
+ self.shifter_values = can_define.dv["Transmission_Status"]["Gear_State"]
+ else:
+ self.shifter_values = can_define.dv["GEAR"]["PRNDL"]
def update(self, cp, cp_cam):
ret = car.CarState.new_message()
- self.frame = int(cp.vl["EPS_STATUS"]["COUNTER"])
+ # lock info
+ ret.doorOpen = any([cp.vl["BCM_1"]["DOOR_OPEN_FL"],
+ cp.vl["BCM_1"]["DOOR_OPEN_FR"],
+ cp.vl["BCM_1"]["DOOR_OPEN_RL"],
+ cp.vl["BCM_1"]["DOOR_OPEN_RR"]])
+ ret.seatbeltUnlatched = cp.vl["ORC_1"]["SEATBELT_DRIVER_UNLATCHED"] == 1
- ret.doorOpen = any([cp.vl["DOORS"]["DOOR_OPEN_FL"],
- cp.vl["DOORS"]["DOOR_OPEN_FR"],
- cp.vl["DOORS"]["DOOR_OPEN_RL"],
- cp.vl["DOORS"]["DOOR_OPEN_RR"]])
- ret.seatbeltUnlatched = cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_UNLATCHED"] == 1
-
- ret.brakePressed = cp.vl["BRAKE_2"]["BRAKE_PRESSED_2"] == 5 # human-only
+ # brake pedal
ret.brake = 0
- ret.gas = cp.vl["ACCEL_GAS_134"]["ACCEL_134"]
- ret.gasPressed = ret.gas > 1e-5
+ ret.brakePressed = cp.vl["ESP_1"]['Brake_Pedal_State'] == 1 # Physical brake pedal switch
- ret.espDisabled = (cp.vl["TRACTION_BUTTON"]["TRACTION_OFF"] == 1)
+ # gas pedal
+ ret.gas = cp.vl["ECM_5"]["Accelerator_Position"]
+ ret.gasPressed = ret.gas > 1e-5
+ # car speed
+ if self.CP.carFingerprint in RAM_CARS:
+ ret.vEgoRaw = cp.vl["ESP_8"]["Vehicle_Speed"] * CV.KPH_TO_MS
+ ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["Transmission_Status"]["Gear_State"], None))
+ else:
+ ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2.
+ ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None))
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.standstill = not ret.vEgoRaw > 0.001
ret.wheelSpeeds = self.get_wheel_speeds(
- cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"],
- cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"],
- cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"],
- cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"],
+ cp.vl["ESP_6"]["WHEEL_SPEED_FL"],
+ cp.vl["ESP_6"]["WHEEL_SPEED_FR"],
+ cp.vl["ESP_6"]["WHEEL_SPEED_RL"],
+ cp.vl["ESP_6"]["WHEEL_SPEED_RR"],
unit=1,
)
- ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2.
- ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
- ret.standstill = not ret.vEgoRaw > 0.001
- ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1
- ret.rightBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2
- ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"]
+ # button presses
+ ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(200, cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1,
+ cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2)
+ ret.genericToggle = cp.vl["STEERING_LEVERS"]["HIGH_BEAM_PRESSED"] == 1
+
+ # steering wheel
+ ret.steeringAngleDeg = cp.vl["STEERING"]["STEERING_ANGLE"] + cp.vl["STEERING"]["STEERING_ANGLE_HP"]
ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"]
- ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None))
+ ret.steeringTorque = cp.vl["EPS_2"]["COLUMN_TORQUE"]
+ ret.steeringTorqueEps = cp.vl["EPS_2"]["EPS_TORQUE_MOTOR"]
+ ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
- ret.cruiseState.enabled = cp.vl["ACC_2"]["ACC_STATUS_2"] == 7 # ACC is green.
- ret.cruiseState.available = ret.cruiseState.enabled # FIXME: for now same as enabled
- ret.cruiseState.speed = cp.vl["DASHBOARD"]["ACC_SPEED_CONFIG_KPH"] * CV.KPH_TO_MS
- # CRUISE_STATE is a three bit msg, 0 is off, 1 and 2 are Non-ACC mode, 3 and 4 are ACC mode, find if there are other states too
- ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2)
+ # cruise state
+ cp_cruise = cp_cam if self.CP.carFingerprint in RAM_CARS else cp
- ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"]
- ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"]
- ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
- steer_state = cp.vl["EPS_STATUS"]["LKAS_STATE"]
- ret.steerError = steer_state == 4 or (steer_state == 0 and ret.vEgo > self.CP.minSteerSpeed)
+ ret.cruiseState.available = cp_cruise.vl["DAS_3"]["ACC_AVAILABLE"] == 1
+ ret.cruiseState.enabled = cp_cruise.vl["DAS_3"]["ACC_ACTIVE"] == 1
+ ret.cruiseState.speed = cp_cruise.vl["DAS_4"]["ACC_SET_SPEED_KPH"] * CV.KPH_TO_MS
+ ret.cruiseState.nonAdaptive = cp_cruise.vl["DAS_4"]["ACC_STATE"] in (1, 2) # 1 NormalCCOn and 2 NormalCCSet
+ ret.cruiseState.standstill = cp_cruise.vl["DAS_3"]["ACC_STANDSTILL"] == 1
+ ret.accFaulted = cp_cruise.vl["DAS_3"]["ACC_FAULTED"] != 0
- ret.genericToggle = bool(cp.vl["STEERING_LEVERS"]["HIGH_BEAM_FLASH"])
+ if self.CP.carFingerprint in RAM_CARS:
+ self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON'] # Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message
+ ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1
+ else:
+ ret.steerFaultPermanent = cp.vl["EPS_2"]["LKAS_STATE"] == 4
+ # blindspot sensors
if self.CP.enableBsm:
- ret.leftBlindspot = cp.vl["BLIND_SPOT_WARNINGS"]["BLIND_SPOT_LEFT"] == 1
- ret.rightBlindspot = cp.vl["BLIND_SPOT_WARNINGS"]["BLIND_SPOT_RIGHT"] == 1
+ ret.leftBlindspot = cp.vl["BSM_1"]["LEFT_STATUS"] == 1
+ ret.rightBlindspot = cp.vl["BSM_1"]["RIGHT_STATUS"] == 1
- self.lkas_counter = cp_cam.vl["LKAS_COMMAND"]["COUNTER"]
- self.lkas_car_model = cp_cam.vl["LKAS_HUD"]["CAR_MODEL"]
- self.lkas_status_ok = cp_cam.vl["LKAS_HEARTBIT"]["LKAS_STATUS_OK"]
+ self.lkas_car_model = cp_cam.vl["DAS_6"]["CAR_MODEL"]
+ self.button_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"]
return ret
+ @staticmethod
+ def get_cruise_signals():
+ signals = [
+ ("ACC_AVAILABLE", "DAS_3"),
+ ("ACC_ACTIVE", "DAS_3"),
+ ("ACC_FAULTED", "DAS_3"),
+ ("ACC_STANDSTILL", "DAS_3"),
+ ("COUNTER", "DAS_3"),
+ ("ACC_SET_SPEED_KPH", "DAS_4"),
+ ("ACC_STATE", "DAS_4"),
+ ]
+ checks = [
+ ("DAS_3", 50),
+ ("DAS_4", 50),
+ ]
+ return signals, checks
+
@staticmethod
def get_can_parser(CP):
signals = [
# sig_name, sig_address
- ("PRNDL", "GEAR"),
- ("DOOR_OPEN_FL", "DOORS"),
- ("DOOR_OPEN_FR", "DOORS"),
- ("DOOR_OPEN_RL", "DOORS"),
- ("DOOR_OPEN_RR", "DOORS"),
- ("BRAKE_PRESSED_2", "BRAKE_2"),
- ("ACCEL_134", "ACCEL_GAS_134"),
- ("SPEED_LEFT", "SPEED_1"),
- ("SPEED_RIGHT", "SPEED_1"),
- ("WHEEL_SPEED_FL", "WHEEL_SPEEDS"),
- ("WHEEL_SPEED_RR", "WHEEL_SPEEDS"),
- ("WHEEL_SPEED_RL", "WHEEL_SPEEDS"),
- ("WHEEL_SPEED_FR", "WHEEL_SPEEDS"),
- ("STEER_ANGLE", "STEERING"),
+ ("DOOR_OPEN_FL", "BCM_1"),
+ ("DOOR_OPEN_FR", "BCM_1"),
+ ("DOOR_OPEN_RL", "BCM_1"),
+ ("DOOR_OPEN_RR", "BCM_1"),
+ ("Brake_Pedal_State", "ESP_1"),
+ ("Accelerator_Position", "ECM_5"),
+ ("WHEEL_SPEED_FL", "ESP_6"),
+ ("WHEEL_SPEED_RR", "ESP_6"),
+ ("WHEEL_SPEED_RL", "ESP_6"),
+ ("WHEEL_SPEED_FR", "ESP_6"),
+ ("STEERING_ANGLE", "STEERING"),
+ ("STEERING_ANGLE_HP", "STEERING"),
("STEERING_RATE", "STEERING"),
("TURN_SIGNALS", "STEERING_LEVERS"),
- ("ACC_STATUS_2", "ACC_2"),
- ("HIGH_BEAM_FLASH", "STEERING_LEVERS"),
- ("ACC_SPEED_CONFIG_KPH", "DASHBOARD"),
- ("CRUISE_STATE", "DASHBOARD"),
- ("TORQUE_DRIVER", "EPS_STATUS"),
- ("TORQUE_MOTOR", "EPS_STATUS"),
- ("LKAS_STATE", "EPS_STATUS"),
- ("COUNTER", "EPS_STATUS",),
- ("TRACTION_OFF", "TRACTION_BUTTON"),
- ("SEATBELT_DRIVER_UNLATCHED", "SEATBELT_STATUS"),
+ ("HIGH_BEAM_PRESSED", "STEERING_LEVERS"),
+ ("SEATBELT_DRIVER_UNLATCHED", "ORC_1"),
+ ("COUNTER", "EPS_2",),
+ ("COLUMN_TORQUE", "EPS_2"),
+ ("EPS_TORQUE_MOTOR", "EPS_2"),
+ ("LKAS_STATE", "EPS_2"),
+ ("COUNTER", "CRUISE_BUTTONS"),
]
checks = [
# sig_address, frequency
- ("BRAKE_2", 50),
- ("EPS_STATUS", 100),
- ("SPEED_1", 100),
- ("WHEEL_SPEEDS", 50),
+ ("ESP_1", 50),
+ ("EPS_2", 100),
+ ("ESP_6", 50),
("STEERING", 100),
- ("ACC_2", 50),
- ("GEAR", 50),
- ("ACCEL_GAS_134", 50),
- ("DASHBOARD", 15),
+ ("ECM_5", 50),
+ ("CRUISE_BUTTONS", 50),
("STEERING_LEVERS", 10),
- ("SEATBELT_STATUS", 2),
- ("DOORS", 1),
- ("TRACTION_BUTTON", 1),
+ ("ORC_1", 2),
+ ("BCM_1", 1),
]
if CP.enableBsm:
signals += [
- ("BLIND_SPOT_RIGHT", "BLIND_SPOT_WARNINGS"),
- ("BLIND_SPOT_LEFT", "BLIND_SPOT_WARNINGS"),
+ ("RIGHT_STATUS", "BSM_1"),
+ ("LEFT_STATUS", "BSM_1"),
+ ]
+ checks.append(("BSM_1", 2))
+
+ if CP.carFingerprint in RAM_CARS:
+ signals += [
+ ("DASM_FAULT", "EPS_3"),
+ ("Vehicle_Speed", "ESP_8"),
+ ("Gear_State", "Transmission_Status"),
+ ]
+ checks += [
+ ("ESP_8", 50),
+ ("EPS_3", 50),
+ ("Transmission_Status", 50),
+ ]
+ else:
+ signals += [
+ ("PRNDL", "GEAR"),
+ ("SPEED_LEFT", "SPEED_1"),
+ ("SPEED_RIGHT", "SPEED_1"),
]
- checks.append(("BLIND_SPOT_WARNINGS", 2))
+ checks += [
+ ("GEAR", 50),
+ ("SPEED_1", 100),
+ ]
+ signals += CarState.get_cruise_signals()[0]
+ checks += CarState.get_cruise_signals()[1]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)
@staticmethod
def get_cam_can_parser(CP):
signals = [
- # sig_name, sig_address
- ("COUNTER", "LKAS_COMMAND"),
- ("CAR_MODEL", "LKAS_HUD"),
- ("LKAS_STATUS_OK", "LKAS_HEARTBIT")
+ # sig_name, sig_address, default
+ ("CAR_MODEL", "DAS_6"),
]
checks = [
- ("LKAS_COMMAND", 100),
- ("LKAS_HEARTBIT", 10),
- ("LKAS_HUD", 4),
+ ("DAS_6", 4),
]
+ if CP.carFingerprint in RAM_CARS:
+ signals += [
+ ("AUTO_HIGH_BEAM_ON", "DAS_6"),
+ ]
+ signals += CarState.get_cruise_signals()[0]
+ checks += CarState.get_cruise_signals()[1]
+
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
diff --git a/selfdrive/car/chrysler/chryslercan.py b/selfdrive/car/chrysler/chryslercan.py
index 4d5226570c..10ed73e9f2 100644
--- a/selfdrive/car/chrysler/chryslercan.py
+++ b/selfdrive/car/chrysler/chryslercan.py
@@ -1,57 +1,71 @@
from cereal import car
-from selfdrive.car import make_can_msg
-
+from selfdrive.car.chrysler.values import RAM_CARS
GearShifter = car.CarState.GearShifter
VisualAlert = car.CarControl.HUDControl.VisualAlert
-def create_lkas_hud(packer, gear, lkas_active, hud_alert, hud_count, lkas_car_model):
- # LKAS_HUD 0x2a6 (678) Controls what lane-keeping icon is displayed.
+def create_lkas_hud(packer, CP, lkas_active, hud_alert, hud_count, car_model, auto_high_beam):
+ # LKAS_HUD - Controls what lane-keeping icon is displayed
+
+ # == Color ==
+ # 0 hidden?
+ # 1 white
+ # 2 green
+ # 3 ldw
- if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw):
- msg = b'\x00\x00\x00\x03\x00\x00\x00\x00'
- return make_can_msg(0x2a6, msg, 0)
+ # == Lines ==
+ # 03 white Lines
+ # 04 grey lines
+ # 09 left lane close
+ # 0A right lane close
+ # 0B left Lane very close
+ # 0C right Lane very close
+ # 0D left cross cross
+ # 0E right lane cross
- color = 1 # default values are for park or neutral in 2017 are 0 0, but trying 1 1 for 2019
- lines = 1
- alerts = 0
+ # == Alerts ==
+ # 7 Normal
+ # 6 lane departure place hands on wheel
+
+ color = 2 if lkas_active else 1
+ lines = 3 if lkas_active else 0
+ alerts = 7 if lkas_active else 0
if hud_count < (1 * 4): # first 3 seconds, 4Hz
alerts = 1
- # CAR.PACIFICA_2018_HYBRID and CAR.PACIFICA_2019_HYBRID
- # had color = 1 and lines = 1 but trying 2017 hybrid style for now.
- if gear in (GearShifter.drive, GearShifter.reverse, GearShifter.low):
- if lkas_active:
- color = 2 # control active, display green.
- lines = 6
- else:
- color = 1 # control off, display white.
- lines = 1
+
+ if hud_alert in (VisualAlert.ldw, VisualAlert.steerRequired):
+ color = 4
+ lines = 0
+ alerts = 6
values = {
- "LKAS_ICON_COLOR": color, # byte 0, last 2 bits
- "CAR_MODEL": lkas_car_model, # byte 1
- "LKAS_LANE_LINES": lines, # byte 2, last 4 bits
- "LKAS_ALERTS": alerts, # byte 3, last 4 bits
- }
+ "LKAS_ICON_COLOR": color,
+ "CAR_MODEL": car_model,
+ "LKAS_LANE_LINES": lines,
+ "LKAS_ALERTS": alerts,
+ }
+
+ if CP.carFingerprint in RAM_CARS:
+ values['AUTO_HIGH_BEAM_ON'] = auto_high_beam
- return packer.make_can_msg("LKAS_HUD", 0, values) # 0x2a6
+ return packer.make_can_msg("DAS_6", 0, values)
-def create_lkas_command(packer, apply_steer, moving_fast, frame):
- # LKAS_COMMAND 0x292 (658) Lane-keeping signal to turn the wheel.
+def create_lkas_command(packer, CP, apply_steer, lkas_control_bit):
+ # LKAS_COMMAND Lane-keeping signal to turn the wheel
+ enabled_val = 2 if CP.carFingerprint in RAM_CARS else 1
values = {
- "LKAS_STEERING_TORQUE": apply_steer,
- "LKAS_HIGH_TORQUE": int(moving_fast),
- "COUNTER": frame % 0x10,
+ "STEERING_TORQUE": apply_steer,
+ "LKAS_CONTROL_BIT": enabled_val if lkas_control_bit else 0,
}
return packer.make_can_msg("LKAS_COMMAND", 0, values)
-def create_wheel_buttons(packer, frame, cancel=False):
- # WHEEL_BUTTONS (571) Message sent to cancel ACC.
+def create_cruise_buttons(packer, frame, bus, cancel=False, resume=False):
values = {
- "ACC_CANCEL": cancel,
- "COUNTER": frame % 10
+ "ACC_Cancel": cancel,
+ "ACC_Resume": resume,
+ "COUNTER": frame % 0x10,
}
- return packer.make_can_msg("WHEEL_BUTTONS", 0, values)
+ return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py
index aead037815..2f058165ac 100755
--- a/selfdrive/car/chrysler/interface.py
+++ b/selfdrive/car/chrysler/interface.py
@@ -1,82 +1,104 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.car.chrysler.values import CAR
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from panda import Panda
+from selfdrive.car import STD_CARGO_KG, get_safety_config
+from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags
from selfdrive.car.interfaces import CarInterfaceBase
class CarInterface(CarInterfaceBase):
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "chrysler"
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.chrysler)]
+ ret.dashcamOnly = candidate in RAM_HD
- # Speed conversion: 20, 45 mph
- ret.wheelbase = 3.089 # in meters for Pacifica Hybrid 2017
- ret.steerRatio = 16.2 # Pacifica Hybrid 2017
- ret.mass = 2242. + STD_CARGO_KG # kg curb weight Pacifica Hybrid 2017
- ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
- ret.lateralTuning.pid.kf = 0.00006 # full torque for 10 deg at 80mph means 0.00007818594
+ ret.radarOffCan = DBC[candidate]['radar'] is None
ret.steerActuatorDelay = 0.1
- ret.steerRateCost = 0.7
ret.steerLimitTimer = 0.4
- if candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019):
- ret.wheelbase = 2.91 # in meters
- ret.steerRatio = 12.7
- ret.steerActuatorDelay = 0.2 # in seconds
-
- ret.centerToFront = ret.wheelbase * 0.44
+ # safety config
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.chrysler)]
+ if candidate in RAM_HD:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_HD
+ elif candidate in RAM_DT:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT
ret.minSteerSpeed = 3.8 # m/s
- if candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019):
- # TODO allow 2019 cars to steer down to 13 m/s if already engaged.
+ if candidate not in RAM_CARS:
+ # Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed.
+ new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019)
+ new_eps_firmware = any(fw.ecu == 'eps' and fw.fwVersion[:4] >= b"6841" for fw in car_fw)
+ if new_eps_platform or new_eps_firmware:
+ ret.flags |= ChryslerFlags.HIGHER_MIN_STEERING_SPEED.value
+
+ # Chrysler
+ if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020):
+ ret.mass = 2242. + STD_CARGO_KG
+ ret.wheelbase = 3.089
+ ret.steerRatio = 16.2 # Pacifica Hybrid 2017
+ ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
+ ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
+ ret.lateralTuning.pid.kf = 0.00006
+
+ # Jeep
+ elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019):
+ ret.mass = 1778 + STD_CARGO_KG
+ ret.wheelbase = 2.71
+ ret.steerRatio = 16.7
+ ret.steerActuatorDelay = 0.2
+ ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
+ ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
+ ret.lateralTuning.pid.kf = 0.00006
+
+ # Ram
+ elif candidate == CAR.RAM_1500:
+ ret.steerActuatorDelay = 0.2
+ ret.wheelbase = 3.88
+ ret.steerRatio = 16.3
+ ret.mass = 2493. + STD_CARGO_KG
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+ ret.minSteerSpeed = 14.5
+ for fw in car_fw:
+ if fw.ecu == 'eps' and fw.fwVersion.startswith((b"68312176", b"68273275")):
+ ret.minSteerSpeed = 0.
+
+ elif candidate == CAR.RAM_HD:
+ ret.steerActuatorDelay = 0.2
+ ret.wheelbase = 3.785
+ ret.steerRatio = 15.61
+ ret.mass = 3405. + STD_CARGO_KG
+ ret.minSteerSpeed = 16
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, 1.0, False)
+
+ else:
+ raise ValueError(f"Unsupported car: {candidate}")
+
+ if ret.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED:
+ # TODO: allow these cars to steer down to 13 m/s if already engaged.
ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged.
- # starting with reasonable value for civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
-
- # TODO: start from empirically derived lateral slip stiffness for the civic and scale by
- # mass and CG position, so all cars will have approximately similar dyn behaviors
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront)
-
+ ret.centerToFront = ret.wheelbase * 0.44
ret.enableBsm = 720 in fingerprint[0]
return ret
- # returns a car.CarState
- def update(self, c, can_strings):
- # ******************* do can recv *******************
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
-
- # speeds
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
-
# events
events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low])
- if ret.vEgo < self.CP.minSteerSpeed:
+ # Low speed steer alert hysteresis logic
+ if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 0.5):
+ self.low_speed_alert = True
+ elif ret.vEgo > (self.CP.minSteerSpeed + 1.):
+ self.low_speed_alert = False
+ if self.low_speed_alert:
events.add(car.CarEvent.EventName.belowSteerSpeed)
ret.events = events.to_msg()
- # copy back carState packet to CS
- self.CS.out = ret.as_reader()
-
- return self.CS.out
+ return ret
- # pass in a car.CarControl
- # to be called @ 100hz
def apply(self, c):
-
- if (self.CS.frame == -1):
- return car.CarControl.Actuators.new_message(), [] # if we haven't seen a frame 220, then do not update.
-
- return self.CC.update(c.enabled, self.CS, c.actuators, c.cruiseControl.cancel, c.hudControl.visualAlert)
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py
index 8882dc2d91..348e3c3632 100755
--- a/selfdrive/car/chrysler/radar_interface.py
+++ b/selfdrive/car/chrysler/radar_interface.py
@@ -10,6 +10,10 @@ LAST_MSG = max(RADAR_MSGS_C + RADAR_MSGS_D)
NUMBER_MSGS = len(RADAR_MSGS_C) + len(RADAR_MSGS_D)
def _create_radar_can_parser(car_fingerprint):
+ dbc = DBC[car_fingerprint]['radar']
+ if dbc is None:
+ return None
+
msg_n = len(RADAR_MSGS_C)
# list of [(signal name, message name or number), (...)]
# [('RADAR_STATE', 1024),
@@ -46,6 +50,9 @@ class RadarInterface(RadarInterfaceBase):
self.trigger_msg = LAST_MSG
def update(self, can_strings):
+ if self.rcp is None:
+ return super().update(None)
+
vls = self.rcp.update_strings(can_strings)
self.updated_messages.update(vls)
@@ -81,4 +88,4 @@ class RadarInterface(RadarInterfaceBase):
ret.points = [x for x in self.pts.values() if x.dRel != 0]
self.updated_messages.clear()
- return ret
+ return ret
\ No newline at end of file
diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py
index 3fd32e9bc7..02261a0b63 100644
--- a/selfdrive/car/chrysler/values.py
+++ b/selfdrive/car/chrysler/values.py
@@ -1,23 +1,83 @@
-from selfdrive.car import dbc_dict
+from enum import IntFlag
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Optional, Union
+
from cereal import car
+from panda.python import uds
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
+
Ecu = car.CarParams.Ecu
-class CarControllerParams:
- STEER_MAX = 261 # 262 faults
- STEER_DELTA_UP = 3 # 3 is stock. 100 is fine. 200 is too much it seems
- STEER_DELTA_DOWN = 3 # no faults on the way down it seems
- STEER_ERROR_MAX = 80
+
+class ChryslerFlags(IntFlag):
+ HIGHER_MIN_STEERING_SPEED = 1
class CAR:
+ # Chrysler
PACIFICA_2017_HYBRID = "CHRYSLER PACIFICA HYBRID 2017"
PACIFICA_2018_HYBRID = "CHRYSLER PACIFICA HYBRID 2018"
PACIFICA_2019_HYBRID = "CHRYSLER PACIFICA HYBRID 2019"
- PACIFICA_2018 = "CHRYSLER PACIFICA 2018" # includes 2017 Pacifica
+ PACIFICA_2018 = "CHRYSLER PACIFICA 2018"
PACIFICA_2020 = "CHRYSLER PACIFICA 2020"
- JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk
+
+ # Jeep
+ JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk
JEEP_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk
+ # Ram
+ RAM_1500 = "RAM 1500 5TH GEN"
+ RAM_HD = "RAM HD 5TH GEN"
+
+
+class CarControllerParams:
+ def __init__(self, CP):
+ self.STEER_ERROR_MAX = 80
+ if CP.carFingerprint in RAM_HD:
+ self.STEER_DELTA_UP = 14
+ self.STEER_DELTA_DOWN = 14
+ self.STEER_MAX = 361 # higher than this faults the EPS
+ elif CP.carFingerprint in RAM_DT:
+ self.STEER_DELTA_UP = 6
+ self.STEER_DELTA_DOWN = 6
+ self.STEER_MAX = 261 # EPS allows more, up to 350?
+ else:
+ self.STEER_DELTA_UP = 3
+ self.STEER_DELTA_DOWN = 3
+ self.STEER_MAX = 261 # higher than this faults the EPS
+
+STEER_THRESHOLD = 120
+
+RAM_DT = {CAR.RAM_1500, }
+RAM_HD = {CAR.RAM_HD, }
+RAM_CARS = RAM_DT | RAM_HD
+
+@dataclass
+class ChryslerCarInfo(CarInfo):
+ package: str = "Adaptive Cruise Control (ACC)"
+ harness: Enum = Harness.fca
+
+CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
+ CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017-18"),
+ CAR.PACIFICA_2018_HYBRID: None, # same platforms
+ CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-22"),
+ CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"),
+ CAR.PACIFICA_2020: [
+ ChryslerCarInfo("Chrysler Pacifica 2019-20"),
+ ChryslerCarInfo("Chrysler Pacifica 2021", package="All"),
+ ],
+ CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"),
+ CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"),
+ CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-22", harness=Harness.ram),
+ CAR.RAM_HD: [
+ ChryslerCarInfo("Ram 2500 2020-22", harness=Harness.ram),
+ ChryslerCarInfo("Ram 3500 2020-22", harness=Harness.ram),
+ ],
+}
+
# Unique CAN messages:
# Only the hybrids have 270: 8
# Only the gas have 55: 8, 416: 7
@@ -62,10 +122,14 @@ FINGERPRINTS = {
},
# Based on "8190c7275a24557b|2020-02-24--09-57-23"
{
- 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8
+ 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8
}],
CAR.JEEP_CHEROKEE: [{
55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
+ },
+ # Based on c88f65eeaee4003a|2022-08-04--15-37-16
+ {
+ 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
}],
CAR.JEEP_CHEROKEE_2019: [{
# Jeep Grand Cherokee 2019, including most 2020 models
@@ -73,15 +137,142 @@ FINGERPRINTS = {
}],
}
+CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(0xf132)
+CHRYSLER_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(0xf132)
-DBC = {
- CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.PACIFICA_2018: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.JEEP_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
- CAR.JEEP_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+CHRYSLER_SOFTWARE_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER)
+CHRYSLER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER)
+
+CHRYSLER_RX_OFFSET = -0x280
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [CHRYSLER_VERSION_REQUEST],
+ [CHRYSLER_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.srs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.combinationMeter],
+ rx_offset=CHRYSLER_RX_OFFSET,
+ bus=0,
+ ),
+ Request(
+ [CHRYSLER_VERSION_REQUEST],
+ [CHRYSLER_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.abs, Ecu.hcp, Ecu.engine, Ecu.transmission],
+ bus=0,
+ ),
+ Request(
+ [CHRYSLER_SOFTWARE_VERSION_REQUEST],
+ [CHRYSLER_SOFTWARE_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine, Ecu.transmission],
+ bus=0,
+ ),
+ ],
+ extra_ecus=[
+ (Ecu.hcp, 0x7e2, None), # manages transmission on hybrids
+ (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids
+ ],
+)
+
+FW_VERSIONS = {
+ CAR.RAM_1500: {
+ (Ecu.combinationMeter, 0x742, None): [
+ b'68294063AH',
+ b'68294063AG',
+ b'68434860AC',
+ b'68527375AD',
+ b'68453503AC',
+ ],
+ (Ecu.srs, 0x744, None): [
+ b'68441329AB',
+ b'68490898AA',
+ b'68428609AB',
+ b'68500728AA',
+ ],
+ (Ecu.abs, 0x747, None): [
+ b'68432418AD',
+ b'68432418AB',
+ b'68436004AE',
+ b'68438454AD',
+ b'68436004AD',
+ b'68535469AB',
+ b'68438454AC',
+ ],
+ (Ecu.fwdRadar, 0x753, None): [
+ b'68320950AL',
+ b'68320950AJ',
+ b'68454268AB',
+ b'68475160AG',
+ b'04672892AB',
+ b'68475160AE',
+ ],
+ (Ecu.eps, 0x75A, None): [
+ b'68273275AG',
+ b'68469901AA',
+ b'68552788AA',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'68448163AJ',
+ b'68500630AD',
+ b'68539650AD',
+ b'68378758AM ',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'68360078AL',
+ b'68384328AD',
+ b'68360085AL',
+ b'68360081AM',
+ b'68502994AD',
+ b'68445533AB',
+ b'68540431AB',
+ b'68484467AC',
+ ],
+ },
+
+ CAR.RAM_HD: {
+ (Ecu.combinationMeter, 0x742, None): [
+ b'68361606AH',
+ b'68492693AD',
+ ],
+ (Ecu.srs, 0x744, None): [
+ b'68399794AC',
+ b'68428503AA',
+ b'68428505AA',
+ ],
+ (Ecu.abs, 0x747, None): [
+ b'68334977AH',
+ b'68504022AB',
+ b'68530686AB',
+ b'68504022AC',
+ ],
+ (Ecu.fwdRadar, 0x753, None): [
+ b'04672895AB',
+ b'56029827AG',
+ b'68484694AE',
+ ],
+ (Ecu.eps, 0x761, None): [
+ b'68421036AC',
+ b'68507906AB',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'52421132AF',
+ b'M2370131MB',
+ b'M2421132MB',
+ ],
+ },
}
-STEER_THRESHOLD = 120
+DBC = {
+ CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.PACIFICA_2018: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.JEEP_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.JEEP_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'),
+ CAR.RAM_1500: dbc_dict('chrysler_ram_dt_generated', None),
+ CAR.RAM_HD: dbc_dict('chrysler_ram_hd_generated', None),
+}
diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py
old mode 100644
new mode 100755
index 3a06cc06f1..cd3e93fa80
--- a/selfdrive/car/disable_ecu.py
+++ b/selfdrive/car/disable_ecu.py
@@ -1,11 +1,12 @@
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
EXT_DIAG_REQUEST = b'\x10\x03'
EXT_DIAG_RESPONSE = b'\x50\x03'
COM_CONT_RESPONSE = b''
+
def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False):
"""Silence an ECU by disabling sending and receiving messages using UDS 0x28.
The ECU will stay silent as long as openpilot keeps sending Tester Present.
@@ -26,9 +27,22 @@ def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01'
cloudlog.warning("ecu disabled")
return True
+
except Exception:
cloudlog.exception("ecu disable exception")
print(f"ecu disable retry ({i+1}) ...")
cloudlog.warning("ecu disable failed")
return False
+
+
+if __name__ == "__main__":
+ import time
+ import cereal.messaging as messaging
+ sendcan = messaging.pub_sock('sendcan')
+ logcan = messaging.sub_sock('can')
+ time.sleep(1)
+
+ # honda bosch radar disable
+ disabled = disable_ecu(logcan, sendcan, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03', timeout=0.5, debug=False)
+ print(f"disabled: {disabled}")
diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py
new file mode 100755
index 0000000000..03313e2ff6
--- /dev/null
+++ b/selfdrive/car/docs.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+import argparse
+from collections import defaultdict
+import jinja2
+import os
+from enum import Enum
+from natsort import natsorted
+from typing import Dict, List
+
+from cereal import car
+from common.basedir import BASEDIR
+from selfdrive.car import gen_empty_fingerprint
+from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote
+from selfdrive.car.car_helpers import interfaces, get_interface_attr
+
+
+def get_all_footnotes() -> Dict[Enum, int]:
+ all_footnotes = list(CommonFootnote)
+ for footnotes in get_interface_attr("Footnote", ignore_none=True).values():
+ all_footnotes.extend(footnotes)
+ return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)}
+
+
+CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md")
+CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
+
+
+def get_all_car_info() -> List[CarInfo]:
+ all_car_info: List[CarInfo] = []
+ footnotes = get_all_footnotes()
+ for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items():
+ CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")])
+
+ if CP.dashcamOnly or car_info is None:
+ continue
+
+ # A platform can include multiple car models
+ if not isinstance(car_info, list):
+ car_info = (car_info,)
+
+ for _car_info in car_info:
+ if not hasattr(_car_info, "row"):
+ _car_info.init_make(CP)
+ _car_info.init(CP, footnotes)
+ all_car_info.append(_car_info)
+
+ # Sort cars by make and model + year
+ sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower())
+ return sorted_cars
+
+
+def group_by_make(all_car_info: List[CarInfo]) -> Dict[str, List[CarInfo]]:
+ sorted_car_info = defaultdict(list)
+ for car_info in all_car_info:
+ sorted_car_info[car_info.make].append(car_info)
+ return dict(sorted_car_info)
+
+
+def generate_cars_md(all_car_info: List[CarInfo], template_fn: str) -> str:
+ with open(template_fn, "r") as f:
+ template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
+
+ footnotes = [fn.value.text for fn in get_all_footnotes()]
+ cars_md: str = template.render(all_car_info=all_car_info, group_by_make=group_by_make,
+ footnotes=footnotes, Column=Column)
+ return cars_md
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+ parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename")
+ parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename")
+ args = parser.parse_args()
+
+ with open(args.out, 'w') as f:
+ f.write(generate_cars_md(get_all_car_info(), args.template))
+ print(f"Generated and written to {args.out}")
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py
new file mode 100644
index 0000000000..03e9e721b5
--- /dev/null
+++ b/selfdrive/car/docs_definitions.py
@@ -0,0 +1,217 @@
+import re
+from collections import namedtuple
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import Dict, List, Optional, Tuple, Union
+
+from cereal import car
+from common.conversions import Conversions as CV
+
+GOOD_TORQUE_THRESHOLD = 1.0 # m/s^2
+MODEL_YEARS_RE = r"(?<= )((\d{4}-\d{2})|(\d{4}))(,|$)"
+
+
+class Column(Enum):
+ MAKE = "Make"
+ MODEL = "Model"
+ PACKAGE = "Supported Package"
+ LONGITUDINAL = "ACC"
+ FSR_LONGITUDINAL = "No ACC accel below"
+ FSR_STEERING = "No ALC below"
+ STEERING_TORQUE = "Steering Torque"
+ AUTO_RESUME = "Resume from stop"
+ HARNESS = "Harness"
+
+
+class Star(Enum):
+ FULL = "full"
+ HALF = "half"
+ EMPTY = "empty"
+
+
+class Harness(Enum):
+ nidec = "Honda Nidec"
+ bosch_a = "Honda Bosch A"
+ bosch_b = "Honda Bosch B"
+ toyota = "Toyota"
+ subaru_a = "Subaru A"
+ subaru_b = "Subaru B"
+ fca = "FCA"
+ ram = "Ram"
+ vw = "VW"
+ j533 = "J533"
+ hyundai_a = "Hyundai A"
+ hyundai_b = "Hyundai B"
+ hyundai_c = "Hyundai C"
+ hyundai_d = "Hyundai D"
+ hyundai_e = "Hyundai E"
+ hyundai_f = "Hyundai F"
+ hyundai_g = "Hyundai G"
+ hyundai_h = "Hyundai H"
+ hyundai_i = "Hyundai I"
+ hyundai_j = "Hyundai J"
+ hyundai_k = "Hyundai K"
+ hyundai_l = "Hyundai L"
+ hyundai_m = "Hyundai M"
+ hyundai_n = "Hyundai N"
+ hyundai_o = "Hyundai O"
+ hyundai_p = "Hyundai P"
+ hyundai_q = "Hyundai Q"
+ custom = "Developer"
+ obd_ii = "OBD-II"
+ gm = "GM"
+ nissan_a = "Nissan A"
+ nissan_b = "Nissan B"
+ mazda = "Mazda"
+ ford_q3 = "Ford Q3"
+ ford_q4 = "Ford Q4"
+ none = "None"
+
+
+CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_footnote"], defaults=(False, False))
+
+
+class CommonFootnote(Enum):
+ EXP_LONG_AVAIL = CarFootnote(
+ "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. ",
+ Column.LONGITUDINAL, docs_only=True)
+ EXP_LONG_DSU = CarFootnote(
+ "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace " +
+ "stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).",
+ Column.LONGITUDINAL)
+
+
+def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]:
+ # Returns applicable footnotes given current column
+ return [fn for fn in footnotes if fn.value.column == column]
+
+
+# TODO: store years as a list
+def get_year_list(years):
+ years_list = []
+ if len(years) == 0:
+ return years_list
+
+ for year in years.split(','):
+ year = year.strip()
+ if len(year) == 4:
+ years_list.append(str(year))
+ elif "-" in year and len(year) == 7:
+ start, end = year.split("-")
+ years_list.extend(map(str, range(int(start), int(f"20{end}") + 1)))
+ else:
+ raise Exception(f"Malformed year string: {years}")
+ return years_list
+
+
+def split_name(name: str) -> Tuple[str, str, str]:
+ make, model = name.split(" ", 1)
+ years = ""
+ match = re.search(MODEL_YEARS_RE, model)
+ if match is not None:
+ years = model[match.start():]
+ model = model[:match.start() - 1]
+ return make, model, years
+
+
+@dataclass
+class CarInfo:
+ name: str
+ package: str
+ video_link: Optional[str] = None
+ footnotes: List[Enum] = field(default_factory=list)
+ min_steer_speed: Optional[float] = None
+ min_enable_speed: Optional[float] = None
+ harness: Enum = Harness.none
+
+ def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]):
+ # TODO: set all the min steer speeds in carParams and remove this
+ if self.min_steer_speed is not None:
+ assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams"
+ else:
+ self.min_steer_speed = CP.minSteerSpeed
+
+ # TODO: set all the min enable speeds in carParams correctly and remove this
+ if self.min_enable_speed is None:
+ self.min_enable_speed = CP.minEnableSpeed
+
+ self.car_name = CP.carName
+ self.car_fingerprint = CP.carFingerprint
+ self.make, self.model, self.years = split_name(self.name)
+
+ op_long = "Stock"
+ if CP.openpilotLongitudinalControl and not CP.enableDsu:
+ op_long = "openpilot"
+ elif CP.experimentalLongitudinalAvailable or CP.enableDsu:
+ op_long = "openpilot available"
+ if CP.enableDsu:
+ self.footnotes.append(CommonFootnote.EXP_LONG_DSU)
+ else:
+ self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL)
+
+ self.row = {
+ Column.MAKE: self.make,
+ Column.MODEL: self.model,
+ Column.PACKAGE: self.package,
+ Column.LONGITUDINAL: op_long,
+ Column.FSR_LONGITUDINAL: f"{max(self.min_enable_speed * CV.MS_TO_MPH, 0):.0f} mph",
+ Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph",
+ Column.STEERING_TORQUE: Star.EMPTY,
+ Column.AUTO_RESUME: Star.FULL if CP.autoResumeSng else Star.EMPTY,
+ Column.HARNESS: self.harness.value,
+ }
+
+ # Set steering torque star from max lateral acceleration
+ assert CP.maxLateralAccel > 0.1
+ if CP.maxLateralAccel >= GOOD_TORQUE_THRESHOLD:
+ self.row[Column.STEERING_TORQUE] = Star.FULL
+
+ self.all_footnotes = all_footnotes
+ self.year_list = get_year_list(self.years)
+ self.detail_sentence = self.get_detail_sentence(CP)
+
+ return self
+
+ def init_make(self, CP: car.CarParams):
+ """CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc."""
+
+ def get_detail_sentence(self, CP):
+ if not CP.notCar:
+ sentence_builder = "openpilot upgrades your {car_model} with automated lane centering{alc} and adaptive cruise control{acc}."
+
+ if self.min_steer_speed > self.min_enable_speed:
+ alc = f" above {self.min_steer_speed * CV.MS_TO_MPH:.0f} mph," if self.min_steer_speed > 0 else " at all speeds,"
+ else:
+ alc = ""
+
+ # Exception for cars which do not auto-resume yet
+ acc = ""
+ if self.min_enable_speed > 0:
+ acc = f" while driving above {self.min_enable_speed * CV.MS_TO_MPH:.0f} mph"
+ elif CP.autoResumeSng:
+ acc = " that automatically resumes from a stop"
+
+ if self.row[Column.STEERING_TORQUE] != Star.FULL:
+ sentence_builder += " This car may not be able to take tight turns on its own."
+
+ return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc)
+
+ else:
+ if CP.carFingerprint == "COMMA BODY":
+ return "The body is a robotics dev kit that can run openpilot. Learn more."
+ else:
+ raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}")
+
+ def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str:
+ item: Union[str, Star] = self.row[column]
+ if isinstance(item, Star):
+ item = star_icon.format(item.value)
+ elif column == Column.MODEL and len(self.years):
+ item += f" {self.years}"
+
+ footnotes = get_footnotes(self.footnotes, column)
+ if len(footnotes):
+ sups = sorted([self.all_footnotes[fn] for fn in footnotes])
+ item += footnote_tag.format(f'{",".join(map(str, sups))}')
+
+ return item
diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py
new file mode 100755
index 0000000000..9f6ace2b5f
--- /dev/null
+++ b/selfdrive/car/ecu_addrs.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+import capnp
+import time
+from typing import Optional, Set, Tuple
+
+import cereal.messaging as messaging
+from panda.python.uds import SERVICE_TYPE
+from selfdrive.car import make_can_msg
+from selfdrive.boardd.boardd import can_list_to_can_capnp
+from system.swaglog import cloudlog
+
+
+def make_tester_present_msg(addr, bus, subaddr=None):
+ dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0]
+ if subaddr is not None:
+ dat.insert(0, subaddr)
+
+ dat.extend([0x0] * (8 - len(dat)))
+ return make_can_msg(addr, bytes(dat), bus)
+
+
+def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: Optional[int] = None) -> bool:
+ # ISO-TP messages are always padded to 8 bytes
+ # tester present response is always a single frame
+ dat_offset = 1 if subaddr is not None else 0
+ if len(msg.dat) == 8 and 1 <= msg.dat[dat_offset] <= 7:
+ # success response
+ if msg.dat[dat_offset + 1] == (SERVICE_TYPE.TESTER_PRESENT + 0x40):
+ return True
+ # error response
+ if msg.dat[dat_offset + 1] == 0x7F and msg.dat[dat_offset + 2] == SERVICE_TYPE.TESTER_PRESENT:
+ return True
+ return False
+
+
+def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[Tuple[int, Optional[int], int]]:
+ addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)]
+ queries: Set[Tuple[int, Optional[int], int]] = {(addr, None, bus) for addr in addr_list}
+ responses = queries
+ return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug)
+
+
+def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[Tuple[int, Optional[int], int]],
+ responses: Set[Tuple[int, Optional[int], int]], timeout: float = 1, debug: bool = False) -> Set[Tuple[int, Optional[int], int]]:
+ ecu_responses: Set[Tuple[int, Optional[int], int]] = set() # set((addr, subaddr, bus),)
+ try:
+ msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries]
+
+ messaging.drain_sock_raw(logcan)
+ sendcan.send(can_list_to_can_capnp(msgs, msgtype='sendcan'))
+ start_time = time.monotonic()
+ while time.monotonic() - start_time < timeout:
+ can_packets = messaging.drain_sock(logcan, wait_for_one=True)
+ for packet in can_packets:
+ for msg in packet.can:
+ subaddr = None if (msg.address, None, msg.src) in responses else msg.dat[0]
+ if (msg.address, subaddr, msg.src) in responses and is_tester_present_response(msg, subaddr):
+ if debug:
+ print(f"CAN-RX: {hex(msg.address)} - 0x{bytes.hex(msg.dat)}")
+ if (msg.address, subaddr, msg.src) in ecu_responses:
+ print(f"Duplicate ECU address: {hex(msg.address)}")
+ ecu_responses.add((msg.address, subaddr, msg.src))
+ except Exception:
+ cloudlog.exception("ECU addr scan exception")
+ return ecu_responses
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Get addresses of all ECUs')
+ parser.add_argument('--debug', action='store_true')
+ parser.add_argument('--bus', type=int, default=1)
+ parser.add_argument('--timeout', type=float, default=1.0)
+ args = parser.parse_args()
+
+ logcan = messaging.sub_sock('can')
+ sendcan = messaging.pub_sock('sendcan')
+
+ time.sleep(1.0)
+
+ print("Getting ECU addresses ...")
+ ecu_addrs = get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug)
+
+ print()
+ print("Found ECUs on addresses:")
+ for addr, subaddr, bus in ecu_addrs:
+ msg = f" 0x{hex(addr)}"
+ if subaddr is not None:
+ msg += f" (sub-address: 0x{hex(subaddr)})"
+ print(msg)
diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py
index 9b280f3b45..1a9bb8c4e7 100644
--- a/selfdrive/car/fingerprints.py
+++ b/selfdrive/car/fingerprints.py
@@ -1,44 +1,12 @@
-import os
-from common.basedir import BASEDIR
+from selfdrive.car.interfaces import get_interface_attr
-def get_attr_from_cars(attr, result=dict, combine_brands=True):
- # read all the folders in selfdrive/car and return a dict where:
- # - keys are all the car models
- # - values are attr values from all car folders
- result = result()
-
- for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]:
- try:
- car_name = car_folder.split('/')[-1]
- values = __import__(f'selfdrive.car.{car_name}.values', 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 combine_brands:
- result[f] = v
- else:
- if car_name not in result:
- result[car_name] = {}
- result[car_name][f] = v
- elif isinstance(attr_values, list):
- result += attr_values
-
- except (ImportError, OSError):
- pass
-
- return result
-
-
-FW_VERSIONS = get_attr_from_cars('FW_VERSIONS')
-_FINGERPRINTS = get_attr_from_cars('FINGERPRINTS')
+FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True)
+_FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True)
_DEBUG_ADDRESS = {1880: 8} # reserved for debug purposes
+
def is_valid_for_fingerprint(msg, car_fingerprint):
adr = msg.address
# ignore addresses that are more than 11 bits
diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py
index 389d3d8d8b..f18014601c 100644
--- a/selfdrive/car/ford/carcontroller.py
+++ b/selfdrive/car/ford/carcontroller.py
@@ -1,86 +1,111 @@
import math
from cereal import car
-from selfdrive.car import make_can_msg
-from selfdrive.car.ford.fordcan import create_steer_command, create_lkas_ui, spam_cancel_button
+from common.numpy_fast import clip, interp
from opendbc.can.packer import CANPacker
+from selfdrive.car.ford import fordcan
+from selfdrive.car.ford.values import CANBUS, CarControllerParams
VisualAlert = car.CarControl.HUDControl.VisualAlert
-MAX_STEER_DELTA = 1
-TOGGLE_DEBUG = False
-class CarController():
- def __init__(self, dbc_name, CP, VM):
- self.packer = CANPacker(dbc_name)
- self.enabled_last = False
- self.main_on_last = False
- self.vehicle_model = VM
- self.generic_toggle_last = 0
- self.steer_alert_last = False
- self.lkas_action = 0
+def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo):
+ # rate limit
+ steer_up = apply_angle_last * apply_angle > 0. and abs(apply_angle) > abs(apply_angle_last)
+ rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN
+ max_angle_diff = interp(vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points)
+ apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff))
- def update(self, enabled, CS, frame, actuators, visual_alert, pcm_cancel):
+ # absolute limit (LatCtlPath_An_Actl)
+ apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
+ apply_path_angle = clip(apply_path_angle, -0.5, 0.5235)
+ apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO
- can_sends = []
- steer_alert = visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw)
+ return apply_angle
- apply_steer = actuators.steer
- if pcm_cancel:
- #print "CANCELING!!!!"
- can_sends.append(spam_cancel_button(self.packer))
+class CarController:
+ def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
+ self.VM = VM
+ self.packer = CANPacker(dbc_name)
+ self.frame = 0
- if (frame % 3) == 0:
+ self.apply_angle_last = 0
+ self.main_on_last = False
+ self.lkas_enabled_last = False
+ self.steer_alert_last = False
- curvature = self.vehicle_model.calc_curvature(math.radians(actuators.steeringAngleDeg), CS.out.vEgo, 0.0)
+ def update(self, CC, CS):
+ can_sends = []
- # The use of the toggle below is handy for trying out the various LKAS modes
- if TOGGLE_DEBUG:
- self.lkas_action += int(CS.out.genericToggle and not self.generic_toggle_last)
- self.lkas_action &= 0xf
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+
+ main_on = CS.out.cruiseState.available
+ steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)
+
+ ### acc buttons ###
+ if CC.cruiseControl.cancel:
+ can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True))
+ can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main))
+ elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
+ can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True))
+ can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main))
+ # if stock lane centering isn't off, send a button press to toggle it off
+ # the stock system checks for steering pressed, and eventually disengages cruise control
+ elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0:
+ can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True))
+
+
+ ### lateral control ###
+ if CC.latActive:
+ lca_rq = 1
+ apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo)
+ else:
+ lca_rq = 0
+ apply_angle = 0.
+
+ # send steering commands at 20Hz
+ if (self.frame % CarControllerParams.LKAS_STEER_STEP) == 0:
+ # use LatCtlPath_An_Actl to actuate steering
+ path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
+
+ # set slower ramp type when small steering angle change
+ # 0=Slow, 1=Medium, 2=Fast, 3=Immediately
+ steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg)
+ if steer_change < 2.0:
+ ramp_type = 0
+ elif steer_change < 4.0:
+ ramp_type = 1
+ elif steer_change < 6.0:
+ ramp_type = 2
else:
- self.lkas_action = 5 # 4 and 5 seem the best. 8 and 9 seem to aggressive and laggy
-
- can_sends.append(create_steer_command(self.packer, apply_steer, enabled,
- CS.lkas_state, CS.out.steeringAngleDeg, curvature, self.lkas_action))
- self.generic_toggle_last = CS.out.genericToggle
-
- if (frame % 100) == 0:
+ ramp_type = 3
+ precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable)
- can_sends.append(make_can_msg(973, b'\x00\x00\x00\x00\x00\x00\x00\x00', 0))
- #can_sends.append(make_can_msg(984, b'\x00\x00\x00\x00\x80\x45\x60\x30', 0))
+ self.apply_angle_last = apply_angle
+ can_sends.append(fordcan.create_lka_command(self.packer, 0, 0))
+ can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision,
+ 0, path_angle, 0, 0))
- if (frame % 100) == 0 or (self.enabled_last != enabled) or (self.main_on_last != CS.out.cruiseState.available) or \
- (self.steer_alert_last != steer_alert):
- can_sends.append(create_lkas_ui(self.packer, CS.out.cruiseState.available, enabled, steer_alert))
- if (frame % 200) == 0:
- can_sends.append(make_can_msg(1875, b'\x80\xb0\x55\x55\x78\x90\x00\x00', 1))
+ ### ui ###
+ send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert)
- if (frame % 10) == 0:
+ # send lkas ui command at 1Hz or if ui state changes
+ if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui:
+ can_sends.append(fordcan.create_lkas_ui_command(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values))
- can_sends.append(make_can_msg(1648, b'\x00\x00\x00\x40\x00\x00\x50\x00', 1))
- can_sends.append(make_can_msg(1649, b'\x10\x10\xf1\x70\x04\x00\x00\x00', 1))
+ # send acc ui command at 20Hz or if ui state changes
+ if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui:
+ can_sends.append(fordcan.create_acc_ui_command(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values))
- can_sends.append(make_can_msg(1664, b'\x00\x00\x03\xe8\x00\x01\xa9\xb2', 1))
- can_sends.append(make_can_msg(1674, b'\x08\x00\x00\xff\x0c\xfb\x6a\x08', 1))
- can_sends.append(make_can_msg(1675, b'\x00\x00\x3b\x60\x37\x00\x00\x00', 1))
- can_sends.append(make_can_msg(1690, b'\x70\x00\x00\x55\x86\x1c\xe0\x00', 1))
-
- can_sends.append(make_can_msg(1910, b'\x06\x4b\x06\x4b\x42\xd3\x11\x30', 1))
- can_sends.append(make_can_msg(1911, b'\x48\x53\x37\x54\x48\x53\x37\x54', 1))
- can_sends.append(make_can_msg(1912, b'\x31\x34\x47\x30\x38\x31\x43\x42', 1))
- can_sends.append(make_can_msg(1913, b'\x31\x34\x47\x30\x38\x32\x43\x42', 1))
- can_sends.append(make_can_msg(1969, b'\xf4\x40\x00\x00\x00\x00\x00\x00', 1))
- can_sends.append(make_can_msg(1971, b'\x0b\xc0\x00\x00\x00\x00\x00\x00', 1))
-
- static_msgs = range(1653, 1658)
- for addr in static_msgs:
- cnt = (frame % 10) + 1
- can_sends.append(make_can_msg(addr, (cnt << 4).to_bytes(1, 'little') + b'\x00\x00\x00\x00\x00\x00\x00', 1))
-
- self.enabled_last = enabled
- self.main_on_last = CS.out.cruiseState.available
+ self.main_on_last = main_on
+ self.lkas_enabled_last = CC.latActive
self.steer_alert_last = steer_alert
- return actuators, can_sends
+ new_actuators = actuators.copy()
+ new_actuators.steeringAngleDeg = self.apply_angle_last
+
+ self.frame += 1
+ return new_actuators, can_sends
diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py
index eba623f5ce..2276b1208a 100644
--- a/selfdrive/car/ford/carstate.py
+++ b/selfdrive/car/ford/carstate.py
@@ -1,38 +1,90 @@
from cereal import car
+from common.conversions import Conversions as CV
+from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
-from common.numpy_fast import mean
-from selfdrive.config import Conversions as CV
from selfdrive.car.interfaces import CarStateBase
-from selfdrive.car.ford.values import DBC
+from selfdrive.car.ford.values import CANBUS, DBC, CarControllerParams
+
+GearShifter = car.CarState.GearShifter
+TransmissionType = car.CarParams.TransmissionType
-WHEEL_RADIUS = 0.33
class CarState(CarStateBase):
- def update(self, cp):
+ def __init__(self, CP):
+ super().__init__(CP)
+ can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
+ if CP.transmissionType == TransmissionType.automatic:
+ self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"]
+
+ def update(self, cp, cp_cam):
ret = car.CarState.new_message()
- ret.wheelSpeeds = self.get_wheel_speeds(
- cp.vl["WheelSpeed_CG1"]["WhlFl_W_Meas"],
- cp.vl["WheelSpeed_CG1"]["WhlFr_W_Meas"],
- cp.vl["WheelSpeed_CG1"]["WhlRl_W_Meas"],
- cp.vl["WheelSpeed_CG1"]["WhlRr_W_Meas"],
- unit=WHEEL_RADIUS,
- )
- ret.vEgoRaw = mean([ret.wheelSpeeds.rr, ret.wheelSpeeds.rl, ret.wheelSpeeds.fr, ret.wheelSpeeds.fl])
+ # car speed
+ ret.vEgoRaw = cp.vl["BrakeSysFeatures"]["Veh_V_ActlBrk"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
- ret.standstill = not ret.vEgoRaw > 0.001
- ret.steeringAngleDeg = cp.vl["Steering_Wheel_Data_CG1"]["SteWhlRelInit_An_Sns"]
- ret.steeringPressed = not cp.vl["Lane_Keep_Assist_Status"]["LaHandsOff_B_Actl"]
- ret.steerError = cp.vl["Lane_Keep_Assist_Status"]["LaActDeny_B_Actl"] == 1
- ret.cruiseState.speed = cp.vl["Cruise_Status"]["Set_Speed"] * CV.MPH_TO_MS
- ret.cruiseState.enabled = not (cp.vl["Cruise_Status"]["Cruise_State"] in (0, 3))
- ret.cruiseState.available = cp.vl["Cruise_Status"]["Cruise_State"] != 0
- ret.gas = cp.vl["EngineData_14"]["ApedPosScal_Pc_Actl"] / 100.
+ ret.yawRate = cp.vl["Yaw_Data_FD1"]["VehYaw_W_Actl"]
+ ret.standstill = cp.vl["DesiredTorqBrk"]["VehStop_D_Stat"] == 1
+
+ # gas pedal
+ ret.gas = cp.vl["EngVehicleSpThrottle"]["ApedPos_Pc_ActlArb"] / 100.
ret.gasPressed = ret.gas > 1e-6
- ret.brakePressed = bool(cp.vl["Cruise_Status"]["Brake_Drv_Appl"])
- ret.genericToggle = bool(cp.vl["Steering_Buttons"]["Dist_Incr"])
- # TODO: we also need raw driver torque, needed for Assisted Lane Change
- self.lkas_state = cp.vl["Lane_Keep_Assist_Status"]["LaActAvail_D_Actl"]
+
+ # brake pedal
+ ret.brake = cp.vl["BrakeSnData_4"]["BrkTot_Tq_Actl"] / 32756. # torque in Nm
+ ret.brakePressed = cp.vl["EngBrakeData"]["BpedDrvAppl_D_Actl"] == 2
+ ret.parkingBrake = cp.vl["DesiredTorqBrk"]["PrkBrkStatus"] in (1, 2)
+
+ # steering wheel
+ ret.steeringAngleDeg = cp.vl["SteeringPinion_Data"]["StePinComp_An_Est"]
+ ret.steeringTorque = cp.vl["EPAS_INFO"]["SteeringColumnTorque"]
+ ret.steeringPressed = abs(ret.steeringTorque) > CarControllerParams.STEER_DRIVER_ALLOWANCE
+ ret.steerFaultTemporary = cp.vl["EPAS_INFO"]["EPAS_Failure"] == 1
+ ret.steerFaultPermanent = cp.vl["EPAS_INFO"]["EPAS_Failure"] in (2, 3)
+ # ret.espDisabled = False # TODO: find traction control signal
+
+ # cruise state
+ ret.cruiseState.speed = cp.vl["EngBrakeData"]["Veh_V_DsplyCcSet"] * CV.MPH_TO_MS
+ ret.cruiseState.enabled = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (4, 5)
+ ret.cruiseState.available = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (3, 4, 5)
+ ret.cruiseState.nonAdaptive = cp.vl["Cluster_Info1_FD1"]["AccEnbl_B_RqDrv"] == 0
+ ret.cruiseState.standstill = cp.vl["EngBrakeData"]["AccStopMde_D_Rq"] == 3
+
+ # gear
+ if self.CP.transmissionType == TransmissionType.automatic:
+ gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"], None)
+ ret.gearShifter = self.parse_gear_shifter(gear)
+ elif self.CP.transmissionType == TransmissionType.manual:
+ ret.clutchPressed = cp.vl["Engine_Clutch_Data"]["CluPdlPos_Pc_Meas"] > 0
+ if bool(cp.vl["BCM_Lamp_Stat_FD1"]["RvrseLghtOn_B_Stat"]):
+ ret.gearShifter = GearShifter.reverse
+ else:
+ ret.gearShifter = GearShifter.drive
+
+ # safety
+ ret.stockFcw = bool(cp_cam.vl["ACCDATA_3"]["FcwVisblWarn_B_Rq"])
+ ret.stockAeb = ret.stockFcw and ret.cruiseState.enabled
+
+ # button presses
+ ret.leftBlinker = cp.vl["Steering_Data_FD1"]["TurnLghtSwtch_D_Stat"] == 1
+ ret.rightBlinker = cp.vl["Steering_Data_FD1"]["TurnLghtSwtch_D_Stat"] == 2
+ # TODO: block this going to the camera otherwise it will enable stock TJA
+ ret.genericToggle = bool(cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"])
+
+ # lock info
+ ret.doorOpen = any([cp.vl["BodyInfo_3_FD1"]["DrStatDrv_B_Actl"], cp.vl["BodyInfo_3_FD1"]["DrStatPsngr_B_Actl"],
+ cp.vl["BodyInfo_3_FD1"]["DrStatRl_B_Actl"], cp.vl["BodyInfo_3_FD1"]["DrStatRr_B_Actl"]])
+ ret.seatbeltUnlatched = cp.vl["RCMStatusMessage2_FD1"]["FirstRowBuckleDriver"] == 2
+
+ # blindspot sensors
+ if self.CP.enableBsm:
+ ret.leftBlindspot = cp.vl["Side_Detect_L_Stat"]["SodDetctLeft_D_Stat"] != 0
+ ret.rightBlindspot = cp.vl["Side_Detect_R_Stat"]["SodDetctRight_D_Stat"] != 0
+
+ # Stock steering buttons so that we can passthru blinkers etc.
+ self.buttons_stock_values = cp.vl["Steering_Data_FD1"]
+ # Stock values from IPMA so that we can retain some stock functionality
+ self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"]
+ self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"]
return ret
@@ -40,19 +92,164 @@ class CarState(CarStateBase):
def get_can_parser(CP):
signals = [
# sig_name, sig_address
- ("WhlRr_W_Meas", "WheelSpeed_CG1"),
- ("WhlRl_W_Meas", "WheelSpeed_CG1"),
- ("WhlFr_W_Meas", "WheelSpeed_CG1"),
- ("WhlFl_W_Meas", "WheelSpeed_CG1"),
- ("SteWhlRelInit_An_Sns", "Steering_Wheel_Data_CG1"),
- ("Cruise_State", "Cruise_Status"),
- ("Set_Speed", "Cruise_Status"),
- ("LaActAvail_D_Actl", "Lane_Keep_Assist_Status"),
- ("LaHandsOff_B_Actl", "Lane_Keep_Assist_Status"),
- ("LaActDeny_B_Actl", "Lane_Keep_Assist_Status"),
- ("ApedPosScal_Pc_Actl", "EngineData_14"),
- ("Dist_Incr", "Steering_Buttons"),
- ("Brake_Drv_Appl", "Cruise_Status"),
+ ("Veh_V_ActlBrk", "BrakeSysFeatures"), # ABS vehicle speed (kph)
+ ("VehYaw_W_Actl", "Yaw_Data_FD1"), # ABS vehicle yaw rate (rad/s)
+ ("VehStop_D_Stat", "DesiredTorqBrk"), # ABS vehicle stopped
+ ("PrkBrkStatus", "DesiredTorqBrk"), # ABS park brake status
+ ("ApedPos_Pc_ActlArb", "EngVehicleSpThrottle"), # PCM throttle (pct)
+ ("BrkTot_Tq_Actl", "BrakeSnData_4"), # ABS brake torque (Nm)
+ ("BpedDrvAppl_D_Actl", "EngBrakeData"), # PCM driver brake pedal pressed
+ ("Veh_V_DsplyCcSet", "EngBrakeData"), # PCM ACC set speed (mph)
+ # The units might change with IPC settings?
+ ("CcStat_D_Actl", "EngBrakeData"), # PCM ACC status
+ ("AccStopMde_D_Rq", "EngBrakeData"), # PCM ACC standstill
+ ("AccEnbl_B_RqDrv", "Cluster_Info1_FD1"), # PCM ACC enable
+ ("StePinComp_An_Est", "SteeringPinion_Data"), # PSCM estimated steering angle (deg)
+ # Calculates steering angle (and offset) from pinion
+ # angle and driving measurements.
+ # StePinRelInit_An_Sns is the pinion angle, initialised
+ # to zero at the beginning of the drive.
+ ("SteeringColumnTorque", "EPAS_INFO"), # PSCM steering column torque (Nm)
+ ("EPAS_Failure", "EPAS_INFO"), # PSCM EPAS status
+ ("TurnLghtSwtch_D_Stat", "Steering_Data_FD1"), # SCCM Turn signal switch
+ ("TjaButtnOnOffPress", "Steering_Data_FD1"), # SCCM ACC button, lane-centering/traffic jam assist toggle
+ ("DrStatDrv_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, driver
+ ("DrStatPsngr_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, passenger
+ ("DrStatRl_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear left
+ ("DrStatRr_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear right
+ ("FirstRowBuckleDriver", "RCMStatusMessage2_FD1"), # RCM Seatbelt status, driver
+ ("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthru the remaining buttons
+ ("WiprFront_D_Stat", "Steering_Data_FD1"),
+ ("LghtAmb_D_Sns", "Steering_Data_FD1"),
+ ("AccButtnGapDecPress", "Steering_Data_FD1"),
+ ("AccButtnGapIncPress", "Steering_Data_FD1"),
+ ("AslButtnOnOffCnclPress", "Steering_Data_FD1"),
+ ("AslButtnOnOffPress", "Steering_Data_FD1"),
+ ("CcAslButtnCnclPress", "Steering_Data_FD1"),
+ ("LaSwtchPos_D_Stat", "Steering_Data_FD1"),
+ ("CcAslButtnCnclResPress", "Steering_Data_FD1"),
+ ("CcAslButtnDeny_B_Actl", "Steering_Data_FD1"),
+ ("CcAslButtnIndxDecPress", "Steering_Data_FD1"),
+ ("CcAslButtnIndxIncPress", "Steering_Data_FD1"),
+ ("CcAslButtnOffCnclPress", "Steering_Data_FD1"),
+ ("CcAslButtnOnOffCncl", "Steering_Data_FD1"),
+ ("CcAslButtnOnPress", "Steering_Data_FD1"),
+ ("CcAslButtnResDecPress", "Steering_Data_FD1"),
+ ("CcAslButtnResIncPress", "Steering_Data_FD1"),
+ ("CcAslButtnSetDecPress", "Steering_Data_FD1"),
+ ("CcAslButtnSetIncPress", "Steering_Data_FD1"),
+ ("CcAslButtnSetPress", "Steering_Data_FD1"),
+ ("CcAsllButtnResPress", "Steering_Data_FD1"),
+ ("CcButtnOffPress", "Steering_Data_FD1"),
+ ("CcButtnOnOffCnclPress", "Steering_Data_FD1"),
+ ("CcButtnOnOffPress", "Steering_Data_FD1"),
+ ("CcButtnOnPress", "Steering_Data_FD1"),
+ ("HeadLghtHiFlash_D_Actl", "Steering_Data_FD1"),
+ ("HeadLghtHiOn_B_StatAhb", "Steering_Data_FD1"),
+ ("AhbStat_B_Dsply", "Steering_Data_FD1"),
+ ("AccButtnGapTogglePress", "Steering_Data_FD1"),
+ ("WiprFrontSwtch_D_Stat", "Steering_Data_FD1"),
+ ("HeadLghtHiCtrl_D_RqAhb", "Steering_Data_FD1"),
+ ]
+
+ checks = [
+ # sig_address, frequency
+ ("BrakeSysFeatures", 50),
+ ("Yaw_Data_FD1", 100),
+ ("DesiredTorqBrk", 50),
+ ("EngVehicleSpThrottle", 100),
+ ("BrakeSnData_4", 50),
+ ("EngBrakeData", 10),
+ ("Cluster_Info1_FD1", 10),
+ ("SteeringPinion_Data", 100),
+ ("EPAS_INFO", 50),
+ ("Lane_Assist_Data3_FD1", 33),
+ ("Steering_Data_FD1", 10),
+ ("BodyInfo_3_FD1", 2),
+ ("RCMStatusMessage2_FD1", 10),
+ ]
+
+ if CP.transmissionType == TransmissionType.automatic:
+ signals += [
+ ("TrnGear_D_RqDrv", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position
+ ]
+ checks += [
+ ("Gear_Shift_by_Wire_FD1", 10),
+ ]
+ elif CP.transmissionType == TransmissionType.manual:
+ signals += [
+ ("CluPdlPos_Pc_Meas", "Engine_Clutch_Data"), # PCM clutch (pct)
+ ("RvrseLghtOn_B_Stat", "BCM_Lamp_Stat_FD1"), # BCM reverse light
+ ]
+ checks += [
+ ("Engine_Clutch_Data", 33),
+ ("BCM_Lamp_Stat_FD1", 1),
+ ]
+
+ if CP.enableBsm:
+ signals += [
+ ("SodDetctLeft_D_Stat", "Side_Detect_L_Stat"), # Blindspot sensor, left
+ ("SodDetctRight_D_Stat", "Side_Detect_R_Stat"), # Blindspot sensor, right
+ ]
+ checks += [
+ ("Side_Detect_L_Stat", 5),
+ ("Side_Detect_R_Stat", 5),
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.main)
+
+ @staticmethod
+ def get_cam_can_parser(CP):
+ signals = [
+ # sig_name, sig_address
+ ("HaDsply_No_Cs", "ACCDATA_3"),
+ ("HaDsply_No_Cnt", "ACCDATA_3"),
+ ("AccStopStat_D_Dsply", "ACCDATA_3"), # ACC stopped status message
+ ("AccTrgDist2_D_Dsply", "ACCDATA_3"), # ACC target distance
+ ("AccStopRes_B_Dsply", "ACCDATA_3"),
+ ("TjaWarn_D_Rq", "ACCDATA_3"), # TJA warning
+ ("Tja_D_Stat", "ACCDATA_3"), # TJA status
+ ("TjaMsgTxt_D_Dsply", "ACCDATA_3"), # TJA text
+ ("IaccLamp_D_Rq", "ACCDATA_3"), # iACC status icon
+ ("AccMsgTxt_D2_Rq", "ACCDATA_3"), # ACC text
+ ("FcwDeny_B_Dsply", "ACCDATA_3"), # FCW disabled
+ ("FcwMemStat_B_Actl", "ACCDATA_3"), # FCW enabled setting
+ ("AccTGap_B_Dsply", "ACCDATA_3"), # ACC time gap display setting
+ ("CadsAlignIncplt_B_Actl", "ACCDATA_3"),
+ ("AccFllwMde_B_Dsply", "ACCDATA_3"), # ACC follow mode display setting
+ ("CadsRadrBlck_B_Actl", "ACCDATA_3"),
+ ("CmbbPostEvnt_B_Dsply", "ACCDATA_3"), # AEB event status
+ ("AccStopMde_B_Dsply", "ACCDATA_3"), # ACC stop mode display setting
+ ("FcwMemSens_D_Actl", "ACCDATA_3"), # FCW sensitivity setting
+ ("FcwMsgTxt_D_Rq", "ACCDATA_3"), # FCW text
+ ("AccWarn_D_Dsply", "ACCDATA_3"), # ACC warning
+ ("FcwVisblWarn_B_Rq", "ACCDATA_3"), # FCW visible alert
+ ("FcwAudioWarn_B_Rq", "ACCDATA_3"), # FCW audio alert
+ ("AccTGap_D_Dsply", "ACCDATA_3"), # ACC time gap
+ ("AccMemEnbl_B_RqDrv", "ACCDATA_3"), # ACC adaptive/normal setting
+ ("FdaMem_B_Stat", "ACCDATA_3"), # FDA enabled setting
+
+ ("FeatConfigIpmaActl", "IPMA_Data"),
+ ("FeatNoIpmaActl", "IPMA_Data"),
+ ("PersIndexIpma_D_Actl", "IPMA_Data"),
+ ("AhbcRampingV_D_Rq", "IPMA_Data"), # AHB ramping
+ ("LaActvStats_D_Dsply", "IPMA_Data"), # LKAS status (lines)
+ ("LaDenyStats_B_Dsply", "IPMA_Data"), # LKAS error
+ ("LaHandsOff_D_Dsply", "IPMA_Data"), # LKAS hands on chime
+ ("CamraDefog_B_Req", "IPMA_Data"), # Windshield heater?
+ ("CamraStats_D_Dsply", "IPMA_Data"), # Camera status
+ ("DasAlrtLvl_D_Dsply", "IPMA_Data"), # DAS alert level
+ ("DasStats_D_Dsply", "IPMA_Data"), # DAS status
+ ("DasWarn_D_Dsply", "IPMA_Data"), # DAS warning
+ ("AhbHiBeam_D_Rq", "IPMA_Data"), # AHB status
+ ("Passthru_63", "IPMA_Data"),
+ ("Passthru_48", "IPMA_Data"),
]
- checks = []
- return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0, enforce_checks=False)
+
+ checks = [
+ # sig_address, frequency
+ ("ACCDATA_3", 5),
+ ("IPMA_Data", 1),
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.camera)
diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py
index 5a8b0b2dec..373ce096c6 100644
--- a/selfdrive/car/ford/fordcan.py
+++ b/selfdrive/car/ford/fordcan.py
@@ -1,50 +1,164 @@
-from common.numpy_fast import clip
-from selfdrive.car.ford.values import MAX_ANGLE
+from cereal import car
+from selfdrive.car.ford.values import CANBUS
+HUDControl = car.CarControl.HUDControl
-def create_steer_command(packer, angle_cmd, enabled, lkas_state, angle_steers, curvature, lkas_action):
- """Creates a CAN message for the Ford Steer Command."""
- #if enabled and lkas available:
- if enabled and lkas_state in (2, 3): # and (frame % 500) >= 3:
- action = lkas_action
+def create_lka_command(packer, angle_deg: float, curvature: float):
+ """
+ Creates a CAN message for the Ford LKAS Command.
+
+ This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout.
+
+ Frequency is 20Hz.
+ """
+
+ values = {
+ "LkaDrvOvrrd_D_Rq": 0, # driver override level? [0|3]
+ "LkaActvStats_D2_Req": 0, # action [0|7]
+ "LaRefAng_No_Req": angle_deg, # angle [-102.4|102.3] degrees
+ "LaRampType_B_Req": 0, # Ramp speed: 0=Smooth, 1=Quick
+ "LaCurvature_No_Calc": curvature, # curvature [-0.01024|0.01023] 1/meter
+ "LdwActvStats_D_Req": 0, # LDW status [0|7]
+ "LdwActvIntns_D_Req": 0, # LDW intensity [0|3], shake alert strength
+ }
+ return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, values)
+
+
+def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path_offset: float, path_angle: float, curvature_rate: float, curvature: float):
+ """
+ Creates a CAN message for the Ford TJA/LCA Command.
+
+ This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam
+ assist and highway driving. It is not subject to the PSCM lockout.
+
+ Ford lane centering command uses a third order polynomial to describe the road centerline. The
+ polynomial is defined by the following coefficients:
+ c0: lateral offset between the vehicle and the centerline
+ c1: heading angle between the vehicle and the centerline
+ c2: curvature of the centerline
+ c3: rate of change of curvature of the centerline
+ As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and
+ speed, the steering angle cannot be easily controlled.
+
+ The PSCM should be configured to accept TJA/LCA commands before these commands will be processed.
+ This can be done using tools such as Forscan.
+
+ Frequency is 20Hz.
+ """
+
+ values = {
+ "LatCtlRng_L_Max": 0, # Unknown [0|126] meter
+ "HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
+ "LatCtl_D_Rq": lca_rq, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, 3=InterventionRight, 4-7=NotUsed [0|7]
+ "LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
+ "LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
+ "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
+ "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
+ "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
+ "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
+ }
+ return packer.make_can_msg("LateralMotionControl", CANBUS.main, values)
+
+
+def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict):
+ """
+ Creates a CAN message for the Ford IPC IPMA/LKAS status.
+
+ Show the LKAS status with the "driver assist" lines in the IPC.
+
+ Stock functionality is maintained by passing through unmodified signals.
+
+ Frequency is 1Hz.
+ """
+
+ # LaActvStats_D_Dsply
+ # R Intvn Warn Supprs Avail No
+ # L
+ # Intvn 24 19 14 9 4
+ # Warn 23 18 13 8 3
+ # Supprs 22 17 12 7 2
+ # Avail 21 16 11 6 1
+ # No 20 15 10 5 0
+ #
+ # TODO: test suppress state
+ if enabled:
+ lines = 0 # NoLeft_NoRight
+ if hud_control.leftLaneDepart:
+ lines += 4
+ elif hud_control.leftLaneVisible:
+ lines += 1
+ if hud_control.rightLaneDepart:
+ lines += 20
+ elif hud_control.rightLaneVisible:
+ lines += 5
+ elif main_on:
+ lines = 0
else:
- action = 0xf
- angle_cmd = angle_steers/MAX_ANGLE
+ if hud_control.leftLaneDepart:
+ lines = 3 # WarnLeft_NoRight
+ elif hud_control.rightLaneDepart:
+ lines = 15 # NoLeft_WarnRight
+ else:
+ lines = 30 # LA_Off
- angle_cmd = clip(angle_cmd * MAX_ANGLE, - MAX_ANGLE, MAX_ANGLE)
+ # TODO: use level 1 for no sound when less severe?
+ hands_on_wheel_dsply = 2 if steer_alert else 0
values = {
- "Lkas_Action": action,
- "Lkas_Alert": 0xf, # no alerts
- "Lane_Curvature": clip(curvature, -0.01, 0.01), # is it just for debug?
- #"Lane_Curvature": 0, # is it just for debug?
- "Steer_Angle_Req": angle_cmd
+ **stock_values,
+ "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31]
+ "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed
}
- return packer.make_can_msg("Lane_Keep_Assist_Control", 0, values)
+ return packer.make_can_msg("IPMA_Data", CANBUS.main, values)
+
+
+def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict):
+ """
+ Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam
+ assist status.
+ Stock functionality is maintained by passing through unmodified signals.
-def create_lkas_ui(packer, main_on, enabled, steer_alert):
- """Creates a CAN message for the Ford Steer Ui."""
+ Frequency is 20Hz.
+ """
- if not main_on:
- lines = 0xf
- elif enabled:
- lines = 0x3
+ # Tja_D_Stat
+ if enabled:
+ if hud_control.leftLaneDepart:
+ status = 3 # ActiveInterventionLeft
+ elif hud_control.rightLaneDepart:
+ status = 4 # ActiveInterventionRight
+ else:
+ status = 2 # Active
+ elif main_on:
+ if hud_control.leftLaneDepart:
+ status = 5 # ActiveWarningLeft
+ elif hud_control.rightLaneDepart:
+ status = 6 # ActiveWarningRight
+ else:
+ status = 1 # Standby
else:
- lines = 0x6
+ status = 0 # Off
values = {
- "Set_Me_X80": 0x80,
- "Set_Me_X45": 0x45,
- "Set_Me_X30": 0x30,
- "Lines_Hud": lines,
- "Hands_Warning_W_Chime": steer_alert,
+ **stock_values,
+ "Tja_D_Stat": status,
}
- return packer.make_can_msg("Lane_Keep_Assist_Ui", 0, values)
+ return packer.make_can_msg("ACCDATA_3", CANBUS.main, values)
+
+
+def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera):
+ """
+ Creates a CAN message for the Ford SCCM buttons/switches.
+
+ Includes cruise control buttons, turn lights and more.
+ """
-def spam_cancel_button(packer):
values = {
- "Cancel": 1
+ **stock_values,
+ "CcAslButtnCnclPress": 1 if cancel else 0, # CC cancel button
+ "CcAsllButtnResPress": 1 if resume else 0, # CC resume button
+ "TjaButtnOnOffPress": 1 if tja_toggle else 0, # TJA toggle button
}
- return packer.make_can_msg("Steering_Buttons", 0, values)
+ return packer.make_can_msg("Steering_Data_FD1", bus, values)
diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py
old mode 100755
new mode 100644
index 0faaa3f669..f3d77bc05a
--- a/selfdrive/car/ford/interface.py
+++ b/selfdrive/car/ford/interface.py
@@ -1,70 +1,69 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.config import Conversions as CV
-from selfdrive.car.ford.values import MAX_ANGLE
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from common.conversions import Conversions as CV
+from selfdrive.car import STD_CARGO_KG, get_safety_config
+from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter
from selfdrive.car.interfaces import CarInterfaceBase
+CarParams = car.CarParams
+
class CarInterface(CarInterfaceBase):
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "ford"
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)]
ret.dashcamOnly = True
+ ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)]
- ret.wheelbase = 2.85
- ret.steerRatio = 14.8
- ret.mass = 3045. * CV.LB_TO_KG + STD_CARGO_KG
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01], [0.005]] # TODO: tune this
- ret.lateralTuning.pid.kf = 1. / MAX_ANGLE # MAX Steer angle to normalize FF
- ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
+ # Angle-based steering
+ ret.steerControlType = CarParams.SteerControlType.angle
+ ret.steerActuatorDelay = 0.4
ret.steerLimitTimer = 1.0
- ret.steerRateCost = 1.0
- ret.centerToFront = ret.wheelbase * 0.44
- tire_stiffness_factor = 0.5328
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+ if candidate == CAR.ESCAPE_MK4:
+ ret.wheelbase = 2.71
+ ret.steerRatio = 14.3 # Copied from Focus
+ ret.mass = 1750 + STD_CARGO_KG
- # TODO: start from empirically derived lateral slip stiffness for the civic and scale by
- # mass and CG position, so all cars will have approximately similar dyn behaviors
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
- tire_stiffness_factor=tire_stiffness_factor)
+ elif candidate == CAR.EXPLORER_MK6:
+ ret.wheelbase = 3.025
+ ret.steerRatio = 16.8 # learned
+ ret.mass = 2050 + STD_CARGO_KG
- ret.steerControlType = car.CarParams.SteerControlType.angle
+ elif candidate == CAR.FOCUS_MK4:
+ ret.wheelbase = 2.7
+ ret.steerRatio = 13.8 # learned
+ ret.mass = 1350 + STD_CARGO_KG
- return ret
+ else:
+ raise ValueError(f"Unsupported car: {candidate}")
- # returns a car.CarState
- def update(self, c, can_strings):
- # ******************* do can recv *******************
- self.cp.update_strings(can_strings)
+ # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1
+ found_ecus = [fw.ecu for fw in car_fw]
+ if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[0]:
+ ret.transmissionType = TransmissionType.automatic
+ else:
+ ret.transmissionType = TransmissionType.manual
+ ret.minEnableSpeed = 20.0 * CV.MPH_TO_MS
- ret = self.CS.update(self.cp)
+ # BSM: Side_Detect_L_Stat, Side_Detect_R_Stat
+ # TODO: detect bsm in car_fw?
+ ret.enableBsm = 0x3A6 in fingerprint[0] and 0x3A7 in fingerprint[0]
- ret.canValid = self.cp.can_valid
+ # LCA can steer down to zero
+ ret.minSteerSpeed = 0.
- # events
- events = self.create_common_events(ret)
+ ret.autoResumeSng = ret.minEnableSpeed == -1.
+ ret.centerToFront = ret.wheelbase * 0.44
+ return ret
- if self.CS.lkas_state not in (2, 3) and ret.vEgo > 13. * CV.MPH_TO_MS and ret.cruiseState.enabled:
- events.add(car.CarEvent.EventName.steerTempUnavailable)
+ def _update(self, c):
+ ret = self.CS.update(self.cp, self.cp_cam)
+ events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic])
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
- # pass in a car.CarControl
- # to be called @ 100hz
def apply(self, c):
-
- ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators,
- c.hudControl.visualAlert, c.cruiseControl.cancel)
-
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py
old mode 100755
new mode 100644
index 94eb8bb0cc..c942703002
--- a/selfdrive/car/ford/radar_interface.py
+++ b/selfdrive/car/ford/radar_interface.py
@@ -1,32 +1,68 @@
#!/usr/bin/env python3
+from math import cos, sin
from cereal import car
from opendbc.can.parser import CANParser
-from selfdrive.car.ford.values import DBC
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
+from selfdrive.car.ford.values import CANBUS, DBC, RADAR
from selfdrive.car.interfaces import RadarInterfaceBase
-RADAR_MSGS = list(range(0x500, 0x540))
+DELPHI_ESR_RADAR_MSGS = list(range(0x500, 0x540))
+DELPHI_MRR_RADAR_START_ADDR = 0x120
+DELPHI_MRR_RADAR_MSG_COUNT = 64
-def _create_radar_can_parser(car_fingerprint):
- msg_n = len(RADAR_MSGS)
+
+def _create_delphi_esr_radar_can_parser():
+ msg_n = len(DELPHI_ESR_RADAR_MSGS)
signals = list(zip(['X_Rel'] * msg_n + ['Angle'] * msg_n + ['V_Rel'] * msg_n,
- RADAR_MSGS * 3))
- checks = list(zip(RADAR_MSGS, [20] * msg_n))
+ DELPHI_ESR_RADAR_MSGS * 3))
+ checks = list(zip(DELPHI_ESR_RADAR_MSGS, [20] * msg_n))
+
+ return CANParser(RADAR.DELPHI_ESR, signals, checks, CANBUS.radar)
+
+
+def _create_delphi_mrr_radar_can_parser():
+ signals = []
+ checks = []
+
+ for i in range(1, DELPHI_MRR_RADAR_MSG_COUNT + 1):
+ msg = f"MRR_Detection_{i:03d}"
+ signals += [
+ (f"CAN_DET_VALID_LEVEL_{i:02d}", msg),
+ (f"CAN_DET_AZIMUTH_{i:02d}", msg),
+ (f"CAN_DET_RANGE_{i:02d}", msg),
+ (f"CAN_DET_RANGE_RATE_{i:02d}", msg),
+ (f"CAN_DET_AMPLITUDE_{i:02d}", msg),
+ (f"CAN_SCAN_INDEX_2LSB_{i:02d}", msg),
+ ]
+ checks += [(msg, 20)]
+
+ return CANParser(RADAR.DELPHI_MRR, signals, checks, CANBUS.radar)
- return CANParser(DBC[car_fingerprint]['radar'], signals, checks, 1)
class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
super().__init__(CP)
- self.validCnt = {key: 0 for key in RADAR_MSGS}
- self.track_id = 0
- self.rcp = _create_radar_can_parser(CP.carFingerprint)
- self.trigger_msg = 0x53f
self.updated_messages = set()
+ self.track_id = 0
+ self.radar = DBC[CP.carFingerprint]['radar']
+ if self.radar is None:
+ self.rcp = None
+ elif self.radar == RADAR.DELPHI_ESR:
+ self.rcp = _create_delphi_esr_radar_can_parser()
+ self.trigger_msg = DELPHI_ESR_RADAR_MSGS[-1]
+ self.valid_cnt = {key: 0 for key in DELPHI_ESR_RADAR_MSGS}
+ elif self.radar == RADAR.DELPHI_MRR:
+ self.rcp = _create_delphi_mrr_radar_can_parser()
+ self.trigger_msg = DELPHI_MRR_RADAR_START_ADDR + DELPHI_MRR_RADAR_MSG_COUNT - 1
+ else:
+ raise ValueError(f"Unsupported radar: {self.radar}")
def update(self, can_strings):
+ if self.rcp is None:
+ return super().update(None)
+
vls = self.rcp.update_strings(can_strings)
self.updated_messages.update(vls)
@@ -39,20 +75,30 @@ class RadarInterface(RadarInterfaceBase):
errors.append("canError")
ret.errors = errors
+ if self.radar == RADAR.DELPHI_ESR:
+ self._update_delphi_esr()
+ elif self.radar == RADAR.DELPHI_MRR:
+ self._update_delphi_mrr()
+
+ ret.points = list(self.pts.values())
+ self.updated_messages.clear()
+ return ret
+
+ def _update_delphi_esr(self):
for ii in sorted(self.updated_messages):
cpt = self.rcp.vl[ii]
if cpt['X_Rel'] > 0.00001:
- self.validCnt[ii] = 0 # reset counter
+ self.valid_cnt[ii] = 0 # reset counter
if cpt['X_Rel'] > 0.00001:
- self.validCnt[ii] += 1
+ self.valid_cnt[ii] += 1
else:
- self.validCnt[ii] = max(self.validCnt[ii] - 1, 0)
- #print ii, self.validCnt[ii], cpt['VALID'], cpt['X_Rel'], cpt['Angle']
+ self.valid_cnt[ii] = max(self.valid_cnt[ii] - 1, 0)
+ #print ii, self.valid_cnt[ii], cpt['VALID'], cpt['X_Rel'], cpt['Angle']
# radar point only valid if there have been enough valid measurements
- if self.validCnt[ii] > 0:
+ if self.valid_cnt[ii] > 0:
if ii not in self.pts:
self.pts[ii] = car.RadarData.RadarPoint.new_message()
self.pts[ii].trackId = self.track_id
@@ -67,6 +113,36 @@ class RadarInterface(RadarInterfaceBase):
if ii in self.pts:
del self.pts[ii]
- ret.points = list(self.pts.values())
- self.updated_messages.clear()
- return ret
+ def _update_delphi_mrr(self):
+ for ii in range(1, DELPHI_MRR_RADAR_MSG_COUNT + 1):
+ msg = self.rcp.vl[f"MRR_Detection_{ii:03d}"]
+
+ # SCAN_INDEX rotates through 0..3 on each message
+ # treat these as separate points
+ scanIndex = msg[f"CAN_SCAN_INDEX_2LSB_{ii:02d}"]
+ i = (ii - 1) * 4 + scanIndex
+
+ if i not in self.pts:
+ self.pts[i] = car.RadarData.RadarPoint.new_message()
+ self.pts[i].trackId = self.track_id
+ self.pts[i].aRel = float('nan')
+ self.pts[i].yvRel = float('nan')
+ self.track_id += 1
+
+ valid = bool(msg[f"CAN_DET_VALID_LEVEL_{ii:02d}"])
+ amplitude = msg[f"CAN_DET_AMPLITUDE_{ii:02d}"] # dBsm [-64|63]
+
+ if valid and 0 < amplitude <= 15:
+ azimuth = msg[f"CAN_DET_AZIMUTH_{ii:02d}"] # rad [-3.1416|3.13964]
+ dist = msg[f"CAN_DET_RANGE_{ii:02d}"] # m [0|255.984]
+ distRate = msg[f"CAN_DET_RANGE_RATE_{ii:02d}"] # m/s [-128|127.984]
+
+ # *** openpilot radar point ***
+ self.pts[i].dRel = cos(azimuth) * dist # m from front of car
+ self.pts[i].yRel = -sin(azimuth) * dist # in car frame's y axis, left is positive
+ self.pts[i].vRel = distRate # m/s
+
+ self.pts[i].measured = True
+
+ else:
+ del self.pts[i]
diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py
index e1e2206472..5114f8d065 100644
--- a/selfdrive/car/ford/values.py
+++ b/selfdrive/car/ford/values.py
@@ -1,12 +1,163 @@
-from selfdrive.car import dbc_dict
+from collections import defaultdict, namedtuple
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Union
+
from cereal import car
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
Ecu = car.CarParams.Ecu
+TransmissionType = car.CarParams.TransmissionType
+GearShifter = car.CarState.GearShifter
+
+AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points'])
+
+
+class CarControllerParams:
+ # Messages: Lane_Assist_Data1, LateralMotionControl
+ LKAS_STEER_STEP = 5
+ # Message: IPMA_Data
+ LKAS_UI_STEP = 100
+ # Message: ACCDATA_3
+ ACC_UI_STEP = 5
+ # Message: Steering_Data_FD1, but send twice as fast
+ BUTTONS_STEP = 10 / 2
+
+ LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians
+ # TODO: remove this once we understand how the EPS calculates the steering angle better
+ STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm
+
+ RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15])
+ RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4])
+
+
+class CANBUS:
+ main = 0
+ radar = 1
+ camera = 2
-MAX_ANGLE = 87. # make sure we never command the extremes (0xfff) which cause latching fault
class CAR:
- FUSION = "FORD FUSION 2018"
+ ESCAPE_MK4 = "FORD ESCAPE 4TH GEN"
+ EXPLORER_MK6 = "FORD EXPLORER 6TH GEN"
+ FOCUS_MK4 = "FORD FOCUS 4TH GEN"
+
+
+class RADAR:
+ DELPHI_ESR = 'ford_fusion_2018_adas'
+ DELPHI_MRR = 'FORD_CADS'
+
+
+DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR))
+
+
+@dataclass
+class FordCarInfo(CarInfo):
+ package: str = "Co-Pilot360 Assist+"
+ harness: Enum = Harness.ford_q3
+
+
+CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
+ CAR.ESCAPE_MK4: [
+ FordCarInfo("Ford Escape 2020-21"),
+ FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"),
+ ],
+ CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"),
+ CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
+}
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine],
+ ),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ bus=0,
+ whitelist_ecus=[Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.shiftByWire],
+ ),
+ ],
+)
-DBC = {
- CAR.FUSION: dbc_dict('ford_fusion_2018_pt', 'ford_fusion_2018_adas'),
+FW_VERSIONS = {
+ CAR.ESCAPE_MK4: {
+ (Ecu.eps, 0x730, None): [
+ b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x760, None): [
+ b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x764, None): [
+ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x706, None): [
+ b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x7E0, None): [
+ b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.shiftByWire, 0x732, None): [
+ b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ },
+ CAR.EXPLORER_MK6: {
+ (Ecu.eps, 0x730, None): [
+ b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x760, None): [
+ b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x764, None): [
+ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x706, None): [
+ b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x7E0, None): [
+ b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.shiftByWire, 0x732, None): [
+ b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ },
+ CAR.FOCUS_MK4: {
+ (Ecu.eps, 0x730, None): [
+ b'JX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x760, None): [
+ b'JX61-2D053-CJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x764, None): [
+ b'JX7T-14D049-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x706, None): [
+ b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x7E0, None): [
+ b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.shiftByWire, 0x732, None): [
+ ],
+ },
}
diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py
new file mode 100755
index 0000000000..c7e4d4eb30
--- /dev/null
+++ b/selfdrive/car/fw_query_definitions.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+import capnp
+from dataclasses import dataclass, field
+import struct
+from typing import Dict, List, Optional, Tuple
+
+import panda.python.uds as uds
+
+
+def p16(val):
+ return struct.pack("!H", val)
+
+
+class StdQueries:
+ # FW queries
+ TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT, 0x0])
+ TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40, 0x0])
+
+ SHORT_TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT])
+ SHORT_TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40])
+
+ DEFAULT_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
+ uds.SESSION_TYPE.DEFAULT])
+ DEFAULT_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
+ uds.SESSION_TYPE.DEFAULT, 0x0, 0x32, 0x1, 0xf4])
+
+ EXTENDED_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
+ uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC])
+ EXTENDED_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
+ uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC, 0x0, 0x32, 0x1, 0xf4])
+
+ MANUFACTURER_SOFTWARE_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
+ MANUFACTURER_SOFTWARE_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
+
+ UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
+ UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
+
+ OBD_VERSION_REQUEST = b'\x09\x04'
+ OBD_VERSION_RESPONSE = b'\x49\x04'
+
+ # VIN queries
+ OBD_VIN_REQUEST = b'\x09\x02'
+ OBD_VIN_RESPONSE = b'\x49\x02\x01'
+
+ UDS_VIN_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + p16(uds.DATA_IDENTIFIER_TYPE.VIN)
+ UDS_VIN_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + p16(uds.DATA_IDENTIFIER_TYPE.VIN)
+
+
+@dataclass
+class Request:
+ request: List[bytes]
+ response: List[bytes]
+ whitelist_ecus: List[int] = field(default_factory=list)
+ rx_offset: int = 0x8
+ bus: int = 1
+
+
+@dataclass
+class FwQueryConfig:
+ requests: List[Request]
+ # Overrides and removes from essential ecus for specific models and ecus (exact matching)
+ non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict)
+ # Ecus added for data collection, not to be fingerprinted on
+ extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list)
diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py
index 59fd4fe8a5..f4d92ab960 100755
--- a/selfdrive/car/fw_versions.py
+++ b/selfdrive/car/fw_versions.py
@@ -1,183 +1,24 @@
#!/usr/bin/env python3
-import struct
-import traceback
-from typing import Any
from collections import defaultdict
-
+from typing import Any, Optional, Set, Tuple
from tqdm import tqdm
import panda.python.uds as uds
from cereal import car
-from selfdrive.car.fingerprints import FW_VERSIONS, get_attr_from_cars
+from selfdrive.car.ecu_addrs import get_ecu_addrs
+from selfdrive.car.interfaces import get_interface_attr
+from selfdrive.car.fingerprints import FW_VERSIONS
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
-from selfdrive.car.toyota.values import CAR as TOYOTA
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
Ecu = car.CarParams.Ecu
+ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]
+FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True)
+VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True)
-def p16(val):
- return struct.pack("!H", val)
-
-
-TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT, 0x0])
-TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40, 0x0])
-
-SHORT_TESTER_PRESENT_REQUEST = bytes([uds.SERVICE_TYPE.TESTER_PRESENT])
-SHORT_TESTER_PRESENT_RESPONSE = bytes([uds.SERVICE_TYPE.TESTER_PRESENT + 0x40])
-
-DEFAULT_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
- uds.SESSION_TYPE.DEFAULT])
-DEFAULT_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
- uds.SESSION_TYPE.DEFAULT, 0x0, 0x32, 0x1, 0xf4])
-
-EXTENDED_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL,
- uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC])
-EXTENDED_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40,
- uds.SESSION_TYPE.EXTENDED_DIAGNOSTIC, 0x0, 0x32, 0x1, 0xf4])
-
-UDS_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
-UDS_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
-
-
-HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(0xf100) # Long description
-HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \
- p16(0xf100)
-HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
-
-
-TOYOTA_VERSION_REQUEST = b'\x1a\x88\x01'
-TOYOTA_VERSION_RESPONSE = b'\x5a\x88\x01'
-
-VOLKSWAGEN_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
-VOLKSWAGEN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
-
-OBD_VERSION_REQUEST = b'\x09\x04'
-OBD_VERSION_RESPONSE = b'\x49\x04'
-
-DEFAULT_RX_OFFSET = 0x8
-VOLKSWAGEN_RX_OFFSET = 0x6a
-
-MAZDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
-MAZDA_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
-
-NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0xc0])
-NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0xc0])
-
-NISSAN_VERSION_REQUEST_KWP = b'\x21\x83'
-NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83'
-
-NISSAN_VERSION_REQUEST_STANDARD = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
-NISSAN_VERSION_RESPONSE_STANDARD = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
-
-NISSAN_RX_OFFSET = 0x20
-
-SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
-SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
- p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
-
-
-# brand, request, response, response offset
-REQUESTS = [
- # Subaru
- (
- "subaru",
- [TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
- [TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Hyundai
- (
- "hyundai",
- [HYUNDAI_VERSION_REQUEST_LONG],
- [HYUNDAI_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- (
- "hyundai",
- [HYUNDAI_VERSION_REQUEST_MULTI],
- [HYUNDAI_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Honda
- (
- "honda",
- [UDS_VERSION_REQUEST],
- [UDS_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Toyota
- (
- "toyota",
- [SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST],
- [SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- (
- "toyota",
- [SHORT_TESTER_PRESENT_REQUEST, OBD_VERSION_REQUEST],
- [SHORT_TESTER_PRESENT_RESPONSE, OBD_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- (
- "toyota",
- [TESTER_PRESENT_REQUEST, DEFAULT_DIAGNOSTIC_REQUEST, EXTENDED_DIAGNOSTIC_REQUEST, UDS_VERSION_REQUEST],
- [TESTER_PRESENT_RESPONSE, DEFAULT_DIAGNOSTIC_RESPONSE, EXTENDED_DIAGNOSTIC_RESPONSE, UDS_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Volkswagen
- (
- "volkswagen",
- [VOLKSWAGEN_VERSION_REQUEST_MULTI],
- [VOLKSWAGEN_VERSION_RESPONSE],
- VOLKSWAGEN_RX_OFFSET,
- ),
- (
- "volkswagen",
- [VOLKSWAGEN_VERSION_REQUEST_MULTI],
- [VOLKSWAGEN_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Mazda
- (
- "mazda",
- [MAZDA_VERSION_REQUEST],
- [MAZDA_VERSION_RESPONSE],
- DEFAULT_RX_OFFSET,
- ),
- # Nissan
- (
- "nissan",
- [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
- [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
- DEFAULT_RX_OFFSET,
- ),
- (
- "nissan",
- [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
- [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
- NISSAN_RX_OFFSET,
- ),
- (
- "nissan",
- [NISSAN_VERSION_REQUEST_STANDARD],
- [NISSAN_VERSION_RESPONSE_STANDARD],
- NISSAN_RX_OFFSET,
- ),
-]
+MODEL_TO_BRAND = {c: b for b, e in VERSIONS.items() for c in e}
+REQUESTS = [(brand, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests]
def chunks(l, n=128):
@@ -185,13 +26,22 @@ def chunks(l, n=128):
yield l[i:i + n]
-def build_fw_dict(fw_versions):
- fw_versions_dict = {}
+def build_fw_dict(fw_versions, filter_brand=None):
+ fw_versions_dict = defaultdict(set)
for fw in fw_versions:
- addr = fw.address
- sub_addr = fw.subAddress if fw.subAddress != 0 else None
- fw_versions_dict[(addr, sub_addr)] = fw.fwVersion
- return fw_versions_dict
+ if filter_brand is None or fw.brand == filter_brand:
+ addr = fw.address
+ sub_addr = fw.subAddress if fw.subAddress != 0 else None
+ fw_versions_dict[(addr, sub_addr)].add(fw.fwVersion)
+ return dict(fw_versions_dict)
+
+
+def get_brand_addrs():
+ brand_addrs = defaultdict(set)
+ for brand, cars in VERSIONS.items():
+ for fw in cars.values():
+ brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()}
+ return brand_addrs
def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
@@ -203,9 +53,9 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
# Getting this exactly right isn't crucial, but excluding camera and radar makes it almost
# impossible to get 3 matching versions, even if two models with shared parts are released at the same
# time and only one is in our database.
- exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps]
+ exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug]
- # Build lookup table from (addr, subaddr, fw) to list of candidate cars
+ # Build lookup table from (addr, sub_addr, fw) to list of candidate cars
all_fw_versions = defaultdict(list)
for candidate, fw_by_addr in FW_VERSIONS.items():
if candidate == exclude:
@@ -219,17 +69,18 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
match_count = 0
candidate = None
- for addr, version in fw_versions_dict.items():
- # All cars that have this FW response on the specified address
- candidates = all_fw_versions[(addr[0], addr[1], version)]
-
- if len(candidates) == 1:
- match_count += 1
- if candidate is None:
- candidate = candidates[0]
- # We uniquely matched two different cars. No fuzzy match possible
- elif candidate != candidates[0]:
- return set()
+ for addr, versions in fw_versions_dict.items():
+ for version in versions:
+ # All cars that have this FW response on the specified address
+ candidates = all_fw_versions[(addr[0], addr[1], version)]
+
+ if len(candidates) == 1:
+ match_count += 1
+ if candidate is None:
+ candidate = candidates[0]
+ # We uniquely matched two different cars. No fuzzy match possible
+ elif candidate != candidates[0]:
+ return set()
if match_count >= 2:
if log:
@@ -249,61 +100,142 @@ def match_fw_to_car_exact(fw_versions_dict):
for candidate, fws in candidates.items():
for ecu, expected_versions in fws.items():
+ config = FW_QUERY_CONFIGS[MODEL_TO_BRAND[candidate]]
ecu_type = ecu[0]
addr = ecu[1:]
- found_version = fw_versions_dict.get(addr, None)
- ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]
- if ecu_type == Ecu.esp and candidate in (TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS) and found_version is None:
- continue
- # On some Toyota models, the engine can show on two different addresses
- if ecu_type == Ecu.engine and candidate in (TOYOTA.CAMRY, TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS) and found_version is None:
- continue
+ found_versions = fw_versions_dict.get(addr, set())
+ if not len(found_versions):
+ # Some models can sometimes miss an ecu, or show on two different addresses
+ if candidate in config.non_essential_ecus.get(ecu_type, []):
+ continue
- # Ignore non essential ecus
- if ecu_type not in ESSENTIAL_ECUS and found_version is None:
+ # Ignore non essential ecus
+ if ecu_type not in ESSENTIAL_ECUS:
+ continue
+
+ # Virtual debug ecu doesn't need to match the database
+ if ecu_type == Ecu.debug:
continue
- if found_version not in expected_versions:
+ if not any([found_version in expected_versions for found_version in found_versions]):
invalid.append(candidate)
break
return set(candidates.keys()) - set(invalid)
-def match_fw_to_car(fw_versions, allow_fuzzy=True):
- fw_versions_dict = build_fw_dict(fw_versions)
- matches = match_fw_to_car_exact(fw_versions_dict)
+def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True):
+ # Try exact matching first
+ exact_matches = []
+ if allow_exact:
+ exact_matches = [(True, match_fw_to_car_exact)]
+ if allow_fuzzy:
+ exact_matches.append((False, match_fw_to_car_fuzzy))
+
+ for exact_match, match_func in exact_matches:
+ # For each brand, attempt to fingerprint using all FW returned from its queries
+ matches = set()
+ for brand in VERSIONS.keys():
+ fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand)
+ matches |= match_func(fw_versions_dict)
+
+ if len(matches):
+ return exact_match, matches
+
+ return True, set()
+
+
+def get_present_ecus(logcan, sendcan):
+ queries = list()
+ parallel_queries = list()
+ responses = set()
+
+ for brand, r in REQUESTS:
+ for brand_versions in VERSIONS[brand].values():
+ for ecu_type, addr, sub_addr in brand_versions:
+ # Only query ecus in whitelist if whitelist is not empty
+ if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus:
+ a = (addr, sub_addr, r.bus)
+ # Build set of queries
+ if sub_addr is None:
+ if a not in parallel_queries:
+ parallel_queries.append(a)
+ else: # subaddresses must be queried one by one
+ if [a] not in queries:
+ queries.append([a])
+
+ # Build set of expected responses to filter
+ response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset)
+ responses.add((response_addr, sub_addr, r.bus))
+
+ queries.insert(0, parallel_queries)
+
+ ecu_responses: Set[Tuple[int, Optional[int], int]] = set()
+ for query in queries:
+ ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1))
+ return ecu_responses
+
+
+def get_brand_ecu_matches(ecu_rx_addrs):
+ """Returns dictionary of brands and matches with ECUs in their FW versions"""
+
+ brand_addrs = get_brand_addrs()
+ brand_matches = {brand: set() for brand, _ in REQUESTS}
+
+ brand_rx_offsets = set((brand, r.rx_offset) for brand, r in REQUESTS)
+ for addr, sub_addr, _ in ecu_rx_addrs:
+ # Since we can't know what request an ecu responded to, add matches for all possible rx offsets
+ for brand, rx_offset in brand_rx_offsets:
+ a = (uds.get_rx_addr_for_tx_addr(addr, -rx_offset), sub_addr)
+ if a in brand_addrs[brand]:
+ brand_matches[brand].add(a)
+
+ return brand_matches
- exact_match = True
- if allow_fuzzy and len(matches) == 0:
- matches = match_fw_to_car_fuzzy(fw_versions_dict)
- # Fuzzy match found
+def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False):
+ """Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
+
+ all_car_fw = []
+ brand_matches = get_brand_ecu_matches(ecu_rx_addrs)
+
+ for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True):
+ car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress)
+ all_car_fw.extend(car_fw)
+ # Try to match using FW returned from this brand only
+ matches = match_fw_to_car_exact(build_fw_dict(car_fw))
if len(matches) == 1:
- exact_match = False
+ break
- return exact_match, matches
+ return all_car_fw
-def get_fw_versions(logcan, sendcan, bus, extra=None, timeout=0.1, debug=False, progress=False):
- ecu_types = {}
+def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False):
+ versions = VERSIONS.copy()
- # Extract ECU addresses to query from fingerprints
- # ECUs using a subadress need be queried one by one, the rest can be done in parallel
- addrs = []
- parallel_addrs = []
+ # Each brand can define extra ECUs to query for data collection
+ for brand, config in FW_QUERY_CONFIGS.items():
+ versions[brand]["debug"] = {ecu: [] for ecu in config.extra_ecus}
+
+ if query_brand is not None:
+ versions = {query_brand: versions[query_brand]}
- versions = get_attr_from_cars('FW_VERSIONS', combine_brands=False)
if extra is not None:
versions.update(extra)
+ # Extract ECU addresses to query from fingerprints
+ # ECUs using a subaddress need be queried one by one, the rest can be done in parallel
+ addrs = []
+ parallel_addrs = []
+ ecu_types = {}
+
for brand, brand_versions in versions.items():
for c in brand_versions.values():
for ecu_type, addr, sub_addr in c.keys():
a = (brand, addr, sub_addr)
if a not in ecu_types:
- ecu_types[(addr, sub_addr)] = ecu_type
+ ecu_types[a] = ecu_type
if sub_addr is None:
if a not in parallel_addrs:
@@ -314,33 +246,39 @@ def get_fw_versions(logcan, sendcan, bus, extra=None, timeout=0.1, debug=False,
addrs.insert(0, parallel_addrs)
- fw_versions = {}
- for i, addr in enumerate(tqdm(addrs, disable=not progress)):
+ # Get versions and build capnp list to put into CarParams
+ car_fw = []
+ requests = [(brand, r) for brand, r in REQUESTS if query_brand is None or brand == query_brand]
+ for addr in tqdm(addrs, disable=not progress):
for addr_chunk in chunks(addr):
- for brand, request, response, response_offset in REQUESTS:
+ for brand, r in requests:
+ # Skip query if no panda available
+ if r.bus > num_pandas * 4 - 1:
+ continue
+
try:
- addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any')]
+ addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and
+ (len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)]
if addrs:
- query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, request, response, response_offset, debug=debug)
- t = 2 * timeout if i == 0 else timeout
- fw_versions.update(query.get_data(t))
+ query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug)
+ for (tx_addr, sub_addr), version in query.get_data(timeout).items():
+ f = car.CarParams.CarFw.new_message()
+
+ f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown)
+ f.fwVersion = version
+ f.address = tx_addr
+ f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset)
+ f.request = r.request
+ f.brand = brand
+ f.bus = r.bus
+
+ if sub_addr is not None:
+ f.subAddress = sub_addr
+
+ car_fw.append(f)
except Exception:
- cloudlog.warning(f"FW query exception: {traceback.format_exc()}")
-
- # Build capnp list to put into CarParams
- car_fw = []
- for addr, version in fw_versions.items():
- f = car.CarParams.CarFw.new_message()
-
- f.ecu = ecu_types[addr]
- f.fwVersion = version
- f.address = addr[0]
-
- if addr[1] is not None:
- f.subAddress = addr[1]
-
- car_fw.append(f)
+ cloudlog.exception("FW query exception")
return car_fw
@@ -354,9 +292,11 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Get firmware version of ECUs')
parser.add_argument('--scan', action='store_true')
parser.add_argument('--debug', action='store_true')
+ parser.add_argument('--brand', help='Only query addresses/with requests for this brand')
args = parser.parse_args()
logcan = messaging.sub_sock('can')
+ pandaStates_sock = messaging.sub_sock('pandaStates')
sendcan = messaging.pub_sock('sendcan')
extra: Any = None
@@ -370,24 +310,26 @@ if __name__ == "__main__":
extra = {"any": {"debug": extra}}
time.sleep(1.)
+ num_pandas = len(messaging.recv_one_retry(pandaStates_sock).pandaStates)
t = time.time()
print("Getting vin...")
- addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
- print(f"VIN: {vin}")
+ vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
+ print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}')
print(f"Getting VIN took {time.time() - t:.3f} s")
print()
t = time.time()
- fw_vers = get_fw_versions(logcan, sendcan, 1, extra=extra, debug=args.debug, progress=True)
+ fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, num_pandas=num_pandas, debug=args.debug, progress=True)
_, candidates = match_fw_to_car(fw_vers)
print()
print("Found FW versions")
print("{")
+ padding = max([len(fw.brand) for fw in fw_vers] or [0])
for version in fw_vers:
subaddr = None if version.subAddress == 0 else hex(version.subAddress)
- print(f" (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]")
+ print(f" Brand: {version.brand:{padding}}, bus: {version.bus} - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}]")
print("}")
print()
diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py
index 7ad1e7cf88..a6cd2f19b9 100644
--- a/selfdrive/car/gm/carcontroller.py
+++ b/selfdrive/car/gm/carcontroller.py
@@ -1,101 +1,146 @@
from cereal import car
-from common.realtime import DT_CTRL
+from common.conversions import Conversions as CV
from common.numpy_fast import interp
-from selfdrive.config import Conversions as CV
+from common.realtime import DT_CTRL
+from opendbc.can.packer import CANPacker
from selfdrive.car import apply_std_steer_torque_limits
from selfdrive.car.gm import gmcan
-from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams
-from opendbc.can.packer import CANPacker
+from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons
VisualAlert = car.CarControl.HUDControl.VisualAlert
+NetworkLocation = car.CarParams.NetworkLocation
+LongCtrlState = car.CarControl.Actuators.LongControlState
+# Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s
+CAMERA_CANCEL_DELAY_FRAMES = 10
-class CarController():
+
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
self.start_time = 0.
self.apply_steer_last = 0
self.apply_gas = 0
self.apply_brake = 0
+ self.frame = 0
+ self.last_steer_frame = 0
+ self.last_button_frame = 0
+ self.cancel_counter = 0
- self.lka_steering_cmd_counter_last = -1
+ self.lka_steering_cmd_counter = 0
+ self.sent_lka_steering_cmd = False
self.lka_icon_status_last = (False, False)
- self.steer_rate_limited = False
-
- self.params = CarControllerParams()
- self.packer_pt = CANPacker(DBC[CP.carFingerprint]['pt'])
- self.packer_obj = CANPacker(DBC[CP.carFingerprint]['radar'])
- self.packer_ch = CANPacker(DBC[CP.carFingerprint]['chassis'])
+ self.params = CarControllerParams(self.CP)
- def update(self, c, enabled, CS, frame, actuators,
- hud_v_cruise, hud_show_lanes, hud_show_car, hud_alert):
+ self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt'])
+ self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar'])
+ self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis'])
- P = self.params
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+ hud_alert = hud_control.visualAlert
+ hud_v_cruise = hud_control.setSpeed
+ if hud_v_cruise > 70:
+ hud_v_cruise = 0
# Send CAN commands.
can_sends = []
- # Steering (50Hz)
+ # Steering (Active: 50Hz, inactive: 10Hz)
+ # Attempt to sync with camera on startup at 50Hz, first few msgs are blocked
+ init_lka_counter = not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera
+ steer_step = self.params.INACTIVE_STEER_STEP
+ if CC.latActive or init_lka_counter:
+ steer_step = self.params.ACTIVE_STEER_STEP
+
# Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the
# next Panda loopback confirmation in the current CS frame.
- if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last:
- self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter
- elif (frame % P.STEER_STEP) == 0:
- lkas_enabled = c.active and not (CS.out.steerWarning or CS.out.steerError) and CS.out.vEgo > P.MIN_STEER_SPEED
- if lkas_enabled:
- new_steer = int(round(actuators.steer * P.STEER_MAX))
- apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, P)
- self.steer_rate_limited = new_steer != apply_steer
+ if CS.loopback_lka_steering_cmd_updated:
+ self.lka_steering_cmd_counter += 1
+ self.sent_lka_steering_cmd = True
+ elif (self.frame - self.last_steer_frame) >= steer_step:
+ # Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus
+ if init_lka_counter:
+ self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1
+
+ if CC.latActive:
+ new_steer = int(round(actuators.steer * self.params.STEER_MAX))
+ apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params)
else:
apply_steer = 0
+ self.last_steer_frame = self.frame
self.apply_steer_last = apply_steer
- # GM EPS faults on any gap in received message counters. To handle transient OP/Panda safety sync issues at the
- # moment of disengaging, increment the counter based on the last message known to pass Panda safety checks.
- idx = (CS.lka_steering_cmd_counter + 1) % 4
-
- can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, lkas_enabled))
-
- # Gas/regen and brakes - all at 25Hz
- if (frame % 4) == 0:
- if not c.active:
- # Stock ECU sends max regen when not enabled.
- self.apply_gas = P.MAX_ACC_REGEN
- self.apply_brake = 0
- else:
- self.apply_gas = int(round(interp(actuators.accel, P.GAS_LOOKUP_BP, P.GAS_LOOKUP_V)))
- self.apply_brake = int(round(interp(actuators.accel, P.BRAKE_LOOKUP_BP, P.BRAKE_LOOKUP_V)))
-
- idx = (frame // 4) % 4
-
- at_full_stop = enabled and CS.out.standstill
- near_stop = enabled and (CS.out.vEgo < P.NEAR_STOP_BRAKE_PHASE)
- can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop))
- can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, enabled, at_full_stop))
-
- # Send dashboard UI commands (ACC status), 25hz
- if (frame % 4) == 0:
- send_fcw = hud_alert == VisualAlert.fcw
- can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, enabled, hud_v_cruise * CV.MS_TO_KPH, hud_show_car, send_fcw))
-
- # Radar needs to know current speed and yaw rate (50hz),
- # and that ADAS is alive (10hz)
- time_and_headlights_step = 10
- tt = frame * DT_CTRL
-
- if frame % time_and_headlights_step == 0:
- idx = (frame // time_and_headlights_step) % 4
- can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
- can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
-
- speed_and_accelerometer_step = 2
- if frame % speed_and_accelerometer_step == 0:
- idx = (frame // speed_and_accelerometer_step) % 4
- can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
- can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
-
- if frame % P.ADAS_KEEPALIVE_STEP == 0:
- can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
+ idx = self.lka_steering_cmd_counter % 4
+ can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive))
+
+ if self.CP.openpilotLongitudinalControl:
+ # Gas/regen, brakes, and UI commands - all at 25Hz
+ if self.frame % 4 == 0:
+ if not CC.longActive:
+ # ASCM sends max regen when not enabled
+ self.apply_gas = self.params.INACTIVE_REGEN
+ self.apply_brake = 0
+ else:
+ self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
+ self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
+
+ idx = (self.frame // 4) % 4
+
+ at_full_stop = CC.longActive and CS.out.standstill
+ near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE)
+ friction_brake_bus = CanBus.CHASSIS
+ # GM Camera exceptions
+ # TODO: can we always check the longControlState?
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping
+ friction_brake_bus = CanBus.POWERTRAIN
+
+ # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
+ can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop))
+ can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP))
+
+ # Send dashboard UI commands (ACC status)
+ send_fcw = hud_alert == VisualAlert.fcw
+ can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
+ hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw))
+
+ # Radar needs to know current speed and yaw rate (50hz),
+ # and that ADAS is alive (10hz)
+ if not self.CP.radarOffCan:
+ tt = self.frame * DT_CTRL
+ time_and_headlights_step = 10
+ if self.frame % time_and_headlights_step == 0:
+ idx = (self.frame // time_and_headlights_step) % 4
+ can_sends.append(gmcan.create_adas_time_status(CanBus.OBSTACLE, int((tt - self.start_time) * 60), idx))
+ can_sends.append(gmcan.create_adas_headlights_status(self.packer_obj, CanBus.OBSTACLE))
+
+ speed_and_accelerometer_step = 2
+ if self.frame % speed_and_accelerometer_step == 0:
+ idx = (self.frame // speed_and_accelerometer_step) % 4
+ can_sends.append(gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
+ can_sends.append(gmcan.create_adas_accelerometer_speed_status(CanBus.OBSTACLE, CS.out.vEgo, idx))
+
+ if self.CP.networkLocation == NetworkLocation.gateway and self.frame % self.params.ADAS_KEEPALIVE_STEP == 0:
+ can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
+
+ else:
+ # While car is braking, cancel button causes ECM to enter a soft disable state with a fault status.
+ # A delayed cancellation allows camera to cancel and avoids a fault when user depresses brake quickly
+ self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
+
+ # Stock longitudinal, integrated at camera
+ if (self.frame - self.last_button_frame) * DT_CTRL > 0.04:
+ if self.cancel_counter > CAMERA_CANCEL_DELAY_FRAMES:
+ self.last_button_frame = self.frame
+ can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL))
+
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1
+ if self.frame % 10 == 0:
+ can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status))
# Show green icon when LKA torque is applied, and
# alarming orange icon when approaching torque limit.
@@ -104,14 +149,17 @@ class CarController():
lka_active = CS.lkas_status == 1
lka_critical = lka_active and abs(actuators.steer) > 0.9
lka_icon_status = (lka_active, lka_critical)
- if frame % P.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last:
+
+ # SW_GMLAN not yet on cam harness, no HUD alerts
+ if self.CP.networkLocation != NetworkLocation.fwdCamera and (self.frame % self.params.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last):
steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw)
can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert))
self.lka_icon_status_last = lka_icon_status
new_actuators = actuators.copy()
- new_actuators.steer = self.apply_steer_last / P.STEER_MAX
+ new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX
new_actuators.gas = self.apply_gas
new_actuators.brake = self.apply_brake
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py
index 4a6b75fa3f..de0fd2eed6 100644
--- a/selfdrive/car/gm/carstate.py
+++ b/selfdrive/car/gm/carstate.py
@@ -1,23 +1,39 @@
+import copy
from cereal import car
+from common.conversions import Conversions as CV
from common.numpy_fast import mean
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
from selfdrive.car.interfaces import CarStateBase
-from selfdrive.car.gm.values import DBC, CAR, AccState, CanBus, STEER_THRESHOLD
+from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD
+
+TransmissionType = car.CarParams.TransmissionType
+NetworkLocation = car.CarParams.NetworkLocation
+STANDSTILL_THRESHOLD = 10 * 0.0311 * CV.KPH_TO_MS
class CarState(CarStateBase):
def __init__(self, CP):
super().__init__(CP)
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
- self.shifter_values = can_define.dv["ECMPRDNL"]["PRNDL"]
- self.lka_steering_cmd_counter = 0
+ self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"]
+ self.loopback_lka_steering_cmd_updated = False
+ self.camera_lka_steering_cmd_counter = 0
+ self.buttons_counter = 0
- def update(self, pt_cp, loopback_cp):
+ def update(self, pt_cp, cam_cp, loopback_cp):
ret = car.CarState.new_message()
self.prev_cruise_buttons = self.cruise_buttons
self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"]
+ self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"]
+ self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
+ self.moving_backward = pt_cp.vl["EBCMWheelSpdRear"]["MovingBackward"] != 0
+
+ # Variables used for avoiding LKAS faults
+ self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
ret.wheelSpeeds = self.get_wheel_speeds(
pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"],
@@ -27,11 +43,27 @@ class CarState(CarStateBase):
)
ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
- ret.standstill = ret.vEgoRaw < 0.01
+ # sample rear wheel speeds, standstill=True if ECM allows engagement with brake
+ ret.standstill = ret.wheelSpeeds.rl <= STANDSTILL_THRESHOLD and ret.wheelSpeeds.rr <= STANDSTILL_THRESHOLD
+
+ if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
+ ret.gearShifter = self.parse_gear_shifter("T")
+ else:
+ ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))
+
+ ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
+ else:
+ # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
+ # that the brake is being intermittently pressed without user interaction.
+ # To avoid a cruise fault we need to use a conservative brake position threshold
+ # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf
+ ret.brakePressed = ret.brake >= 8
- ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL"]["PRNDL"], None))
- ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["Brake_Pressed"] != 0
- ret.brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xd0
+ # Regen braking is braking
+ if self.CP.transmissionType == TransmissionType.direct:
+ ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
ret.gasPressed = ret.gas > 1e-5
@@ -41,12 +73,11 @@ class CarState(CarStateBase):
ret.steeringTorque = pt_cp.vl["PSCMStatus"]["LKADriverAppldTrq"]
ret.steeringTorqueEps = pt_cp.vl["PSCMStatus"]["LKATorqueDelivered"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
- self.lka_steering_cmd_counter = loopback_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
# 0 inactive, 1 active, 2 temporarily limited, 3 failed
self.lkas_status = pt_cp.vl["PSCMStatus"]["LKATorqueDeliveredStatus"]
- ret.steerWarning = self.lkas_status == 2
- ret.steerError = self.lkas_status == 3
+ ret.steerFaultTemporary = self.lkas_status == 2
+ ret.steerFaultPermanent = self.lkas_status == 3
# 1 - open, 0 - closed
ret.doorOpen = (pt_cp.vl["BCMDoorBeltStatus"]["FrontLeftDoor"] == 1 or
@@ -59,25 +90,46 @@ class CarState(CarStateBase):
ret.leftBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 1
ret.rightBlinker = pt_cp.vl["BCMTurnSignals"]["TurnSignals"] == 2
- self.park_brake = pt_cp.vl["EPBStatus"]["EPBClosed"]
+ ret.parkingBrake = pt_cp.vl["VehicleIgnitionAlt"]["ParkBrake"] == 1
ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
- self.pcm_acc_status = pt_cp.vl["AcceleratorPedal2"]["CruiseState"]
+ ret.accFaulted = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED
- # Regen braking is braking
- if self.car_fingerprint == CAR.VOLT:
- ret.brakePressed = ret.brakePressed or pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
-
- ret.cruiseState.enabled = self.pcm_acc_status != AccState.OFF
- ret.cruiseState.standstill = self.pcm_acc_status == AccState.STANDSTILL
+ ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
+ ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
+ if self.CP.networkLocation == NetworkLocation.fwdCamera:
+ ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS
+ ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0
+ # openpilot controls nonAdaptive when not pcmCruise
+ if self.CP.pcmCruise:
+ ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3)
return ret
+ @staticmethod
+ def get_cam_can_parser(CP):
+ signals = []
+ checks = []
+ if CP.networkLocation == NetworkLocation.fwdCamera:
+ signals += [
+ ("AEBCmdActive", "AEBCmd"),
+ ("RollingCounter", "ASCMLKASteeringCmd"),
+ ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"),
+ ("ACCCruiseState", "ASCMActiveCruiseControlStatus"),
+ ]
+ checks += [
+ ("AEBCmd", 10),
+ ("ASCMLKASteeringCmd", 10),
+ ("ASCMActiveCruiseControlStatus", 25),
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA)
+
@staticmethod
def get_can_parser(CP):
signals = [
# sig_name, sig_address
- ("BrakePedalPosition", "EBCMBrakePedalPosition"),
+ ("BrakePedalPos", "ECMAcceleratorPos"),
("FrontLeftDoor", "BCMDoorBeltStatus"),
("FrontRightDoor", "BCMDoorBeltStatus"),
("RearLeftDoor", "BCMDoorBeltStatus"),
@@ -88,39 +140,47 @@ class CarState(CarStateBase):
("AcceleratorPedal2", "AcceleratorPedal2"),
("CruiseState", "AcceleratorPedal2"),
("ACCButtons", "ASCMSteeringButton"),
+ ("RollingCounter", "ASCMSteeringButton"),
("SteeringWheelAngle", "PSCMSteeringAngle"),
("SteeringWheelRate", "PSCMSteeringAngle"),
("FLWheelSpd", "EBCMWheelSpdFront"),
("FRWheelSpd", "EBCMWheelSpdFront"),
("RLWheelSpd", "EBCMWheelSpdRear"),
("RRWheelSpd", "EBCMWheelSpdRear"),
- ("PRNDL", "ECMPRDNL"),
+ ("MovingBackward", "EBCMWheelSpdRear"),
+ ("PRNDL2", "ECMPRDNL2"),
+ ("ManualMode", "ECMPRDNL2"),
("LKADriverAppldTrq", "PSCMStatus"),
("LKATorqueDelivered", "PSCMStatus"),
("LKATorqueDeliveredStatus", "PSCMStatus"),
+ ("HandsOffSWlDetectionStatus", "PSCMStatus"),
+ ("HandsOffSWDetectionMode", "PSCMStatus"),
+ ("LKATotalTorqueDelivered", "PSCMStatus"),
+ ("PSCMStatusChecksum", "PSCMStatus"),
+ ("RollingCounter", "PSCMStatus"),
("TractionControlOn", "ESPStatus"),
- ("EPBClosed", "EPBStatus"),
+ ("ParkBrake", "VehicleIgnitionAlt"),
("CruiseMainOn", "ECMEngineStatus"),
- ("Brake_Pressed", "ECMEngineStatus"),
+ ("BrakePressed", "ECMEngineStatus"),
]
checks = [
("BCMTurnSignals", 1),
- ("ECMPRDNL", 10),
+ ("ECMPRDNL2", 10),
("PSCMStatus", 10),
("ESPStatus", 10),
("BCMDoorBeltStatus", 10),
- ("EPBStatus", 20),
+ ("VehicleIgnitionAlt", 10),
("EBCMWheelSpdFront", 20),
("EBCMWheelSpdRear", 20),
("AcceleratorPedal2", 33),
("ASCMSteeringButton", 33),
("ECMEngineStatus", 100),
("PSCMSteeringAngle", 100),
- ("EBCMBrakePedalPosition", 100),
+ ("ECMAcceleratorPos", 80),
]
- if CP.carFingerprint == CAR.VOLT:
+ if CP.transmissionType == TransmissionType.direct:
signals.append(("RegenPaddle", "EBCMRegenPaddle"))
checks.append(("EBCMRegenPaddle", 50))
@@ -133,7 +193,7 @@ class CarState(CarStateBase):
]
checks = [
- ("ASCMLKASteeringCmd", 50),
+ ("ASCMLKASteeringCmd", 0),
]
- return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK)
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK, enforce_checks=False)
diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py
index 47e6090a03..56c981326f 100644
--- a/selfdrive/car/gm/gmcan.py
+++ b/selfdrive/car/gm/gmcan.py
@@ -1,7 +1,23 @@
from selfdrive.car import make_can_msg
+from selfdrive.car.gm.values import CAR
-def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
+def create_buttons(packer, bus, idx, button):
+ values = {
+ "ACCButtons": button,
+ "RollingCounter": idx,
+ }
+ return packer.make_can_msg("ASCMSteeringButton", bus, values)
+
+
+def create_pscm_status(packer, bus, pscm_status):
+ checksum_mod = int(1 - pscm_status["HandsOffSWlDetectionStatus"]) << 5
+ pscm_status["HandsOffSWlDetectionStatus"] = 1
+ pscm_status["PSCMStatusChecksum"] += checksum_mod
+ return packer.make_can_msg("PSCMStatus", bus, pscm_status)
+
+
+def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
values = {
"LKASteeringCmdActive": lkas_active,
"LKASteeringCmd": apply_steer,
@@ -11,15 +27,17 @@ def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
return packer.make_can_msg("ASCMLKASteeringCmd", bus, values)
+
def create_adas_keepalive(bus):
dat = b"\x00\x00\x00\x00\x00\x00\x00"
return [make_can_msg(0x409, dat, bus), make_can_msg(0x40a, dat, bus)]
-def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_stop):
+
+def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop):
values = {
- "GasRegenCmdActive": acc_engaged,
+ "GasRegenCmdActive": enabled,
"RollingCounter": idx,
- "GasRegenCmdActiveInv": 1 - acc_engaged,
+ "GasRegenCmdActiveInv": 1 - enabled,
"GasRegenCmd": throttle,
"GasRegenFullStopActive": at_full_stop,
"GasRegenAlwaysOne": 1,
@@ -34,15 +52,21 @@ def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_st
return packer.make_can_msg("ASCMGasRegenCmd", bus, values)
-def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_full_stop):
+
+def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP):
mode = 0x1
+
+ # TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake
+ if enabled and CP.carFingerprint in (CAR.BOLT_EUV,):
+ mode = 0x9
+
if apply_brake > 0:
mode = 0xa
if at_full_stop:
mode = 0xd
# TODO: this is to have GM bringing the car to complete stop,
- # but currently it conflicts with OP controls, so turned off.
+ # but currently it conflicts with OP controls, so turned off. Not set by all cars
#elif near_stop:
# mode = 0xb
@@ -50,45 +74,48 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_f
checksum = (0x10000 - (mode << 12) - brake - idx) & 0xffff
values = {
- "RollingCounter" : idx,
- "FrictionBrakeMode" : mode,
+ "RollingCounter": idx,
+ "FrictionBrakeMode": mode,
"FrictionBrakeChecksum": checksum,
- "FrictionBrakeCmd" : -apply_brake
+ "FrictionBrakeCmd": -apply_brake
}
return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values)
-def create_acc_dashboard_command(packer, bus, acc_engaged, target_speed_kph, lead_car_in_sight, fcw):
- # Not a bit shift, dash can round up based on low 4 bits.
- target_speed = int(target_speed_kph * 16) & 0xfff
+
+def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, lead_car_in_sight, fcw):
+ target_speed = min(target_speed_kph, 255)
values = {
- "ACCAlwaysOne" : 1,
- "ACCResumeButton" : 0,
- "ACCSpeedSetpoint" : target_speed,
- "ACCGapLevel" : 3 * acc_engaged, # 3 "far", 0 "inactive"
- "ACCCmdActive" : acc_engaged,
- "ACCAlwaysOne2" : 1,
- "ACCLeadCar" : lead_car_in_sight,
+ "ACCAlwaysOne": 1,
+ "ACCResumeButton": 0,
+ "ACCSpeedSetpoint": target_speed,
+ "ACCGapLevel": 3 * enabled, # 3 "far", 0 "inactive"
+ "ACCCmdActive": enabled,
+ "ACCAlwaysOne2": 1,
+ "ACCLeadCar": lead_car_in_sight,
"FCWAlert": 0x3 if fcw else 0
}
return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values)
+
def create_adas_time_status(bus, tt, idx):
dat = [(tt >> 20) & 0xff, (tt >> 12) & 0xff, (tt >> 4) & 0xff,
- ((tt & 0xf) << 4) + (idx << 2)]
+ ((tt & 0xf) << 4) + (idx << 2)]
chksum = 0x1000 - dat[0] - dat[1] - dat[2] - dat[3]
chksum = chksum & 0xfff
dat += [0x40 + (chksum >> 8), chksum & 0xff, 0x12]
return make_can_msg(0xa1, bytes(dat), bus)
+
def create_adas_steering_status(bus, idx):
dat = [idx << 6, 0xf0, 0x20, 0, 0, 0]
chksum = 0x60 + sum(dat)
dat += [chksum >> 8, chksum & 0xff]
return make_can_msg(0x306, bytes(dat), bus)
+
def create_adas_accelerometer_speed_status(bus, speed_ms, idx):
spd = int(speed_ms * 16) & 0xfff
accel = 0 & 0xfff
@@ -102,6 +129,7 @@ def create_adas_accelerometer_speed_status(bus, speed_ms, idx):
dat += [(idx << 5) + (far_range_mode << 4) + (near_range_mode << 3) + (chksum >> 8), chksum & 0xff]
return make_can_msg(0x308, bytes(dat), bus)
+
def create_adas_headlights_status(packer, bus):
values = {
"Always42": 0x42,
@@ -109,6 +137,7 @@ def create_adas_headlights_status(packer, bus):
}
return packer.make_can_msg("ASCMHeadlight", bus, values)
+
def create_lka_icon_command(bus, active, critical, steer):
if active and steer == 1:
if critical:
diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py
index d6a2d3cfee..195df36a7f 100755
--- a/selfdrive/car/gm/interface.py
+++ b/selfdrive/car/gm/interface.py
@@ -1,20 +1,26 @@
#!/usr/bin/env python3
from cereal import car
from math import fabs
-from selfdrive.config import Conversions as CV
-from selfdrive.car.gm.values import CAR, CruiseButtons, \
- AccState, CarControllerParams
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from panda import Panda
+
+from common.conversions import Conversions as CV
+from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config
+from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR
from selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
+GearShifter = car.CarState.GearShifter
+TransmissionType = car.CarParams.TransmissionType
+NetworkLocation = car.CarParams.NetworkLocation
+BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
+ CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
+
class CarInterface(CarInterfaceBase):
@staticmethod
def get_pid_accel_limits(CP, current_speed, cruise_speed):
- params = CarControllerParams()
- return params.ACCEL_MIN, params.ACCEL_MAX
+ return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX
# Determined by iteratively plotting and minimizing error for f(angle, speed) = steer.
@staticmethod
@@ -38,55 +44,91 @@ class CarInterface(CarInterfaceBase):
return CarInterfaceBase.get_steer_feedforward_default
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "gm"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)]
- ret.pcmCruise = False # stock cruise control is kept off
+ ret.autoResumeSng = False
+
+ if candidate in EV_CAR:
+ ret.transmissionType = TransmissionType.direct
+ else:
+ ret.transmissionType = TransmissionType.automatic
+
+ ret.longitudinalTuning.deadzoneBP = [0.]
+ ret.longitudinalTuning.deadzoneV = [0.15]
+
+ ret.longitudinalTuning.kpBP = [5., 35.]
+ ret.longitudinalTuning.kiBP = [0.]
+
+ if candidate in CAMERA_ACC_CAR:
+ ret.experimentalLongitudinalAvailable = True
+ ret.networkLocation = NetworkLocation.fwdCamera
+ ret.radarOffCan = True # no radar
+ ret.pcmCruise = True
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
+ ret.minEnableSpeed = 5 * CV.KPH_TO_MS
+
+ if experimental_long:
+ ret.pcmCruise = False
+ ret.openpilotLongitudinalControl = True
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG
+
+ # Tuning
+ ret.longitudinalTuning.kpV = [2.0, 1.5]
+ ret.longitudinalTuning.kiV = [0.72]
+ ret.stopAccel = -2.0
+ ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling
+ ret.vEgoStopping = 0.25
+ ret.vEgoStarting = 0.25
+ ret.longitudinalActuatorDelayUpperBound = 0.5
+
+ else: # ASCM, OBD-II harness
+ ret.openpilotLongitudinalControl = True
+ ret.networkLocation = NetworkLocation.gateway
+ ret.radarOffCan = False
+ ret.pcmCruise = False # stock non-adaptive cruise control is kept off
+ # supports stop and go, but initial engage must (conservatively) be above 18mph
+ ret.minEnableSpeed = 18 * CV.MPH_TO_MS
+
+ # Tuning
+ ret.longitudinalTuning.kpV = [2.4, 1.5]
+ ret.longitudinalTuning.kiV = [0.36]
# These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is
- # added to selfdrive/test/test_routes, we can remove it from this list.
- ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL}
-
- # Presence of a camera on the object bus is ok.
- # Have to go to read_only if ASCM is online (ACC-enabled cars),
- # or camera is on powertrain bus (LKA cars without ACC).
- ret.openpilotLongitudinalControl = True
- tire_stiffness_factor = 0.444 # not optimized yet
+ # added to selfdrive/car/tests/routes.py, we can remove it from this list.
+ ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX}
- # Start with a baseline lateral tuning for all GM vehicles. Override tuning as needed in each model section below.
- ret.minSteerSpeed = 7 * CV.MPH_TO_MS
+ # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below.
+ # Some GMs need some tolerance above 10 kph to avoid a fault
+ ret.minSteerSpeed = 10.1 * CV.KPH_TO_MS
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]]
ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594
- ret.steerRateCost = 1.0
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
+ tire_stiffness_factor = 0.444 # not optimized yet
+
+ ret.steerLimitTimer = 0.4
+ ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
if candidate == CAR.VOLT:
- # supports stop and go, but initial engage must be above 18mph (which include conservatism)
- ret.minEnableSpeed = 18 * CV.MPH_TO_MS
ret.mass = 1607. + STD_CARGO_KG
ret.wheelbase = 2.69
ret.steerRatio = 17.7 # Stock 15.7, LiveParameters
- tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters
- ret.steerRatioRear = 0.
- ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh
+ tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters
+ ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh
ret.lateralTuning.pid.kpBP = [0., 40.]
ret.lateralTuning.pid.kpV = [0., 0.17]
ret.lateralTuning.pid.kiBP = [0.]
ret.lateralTuning.pid.kiV = [0.]
- ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt()
+ ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt()
ret.steerActuatorDelay = 0.2
elif candidate == CAR.MALIBU:
- # supports stop and go, but initial engage must be above 18mph (which include conservatism)
- ret.minEnableSpeed = 18 * CV.MPH_TO_MS
ret.mass = 1496. + STD_CARGO_KG
ret.wheelbase = 2.83
ret.steerRatio = 15.8
- ret.steerRatioRear = 0.
ret.centerToFront = ret.wheelbase * 0.4 # wild guess
elif candidate == CAR.HOLDEN_ASTRA:
@@ -94,142 +136,105 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.662
# Remaining parameters copied from Volt for now
ret.centerToFront = ret.wheelbase * 0.4
- ret.minEnableSpeed = 18 * CV.MPH_TO_MS
ret.steerRatio = 15.7
- ret.steerRatioRear = 0.
elif candidate == CAR.ACADIA:
ret.minEnableSpeed = -1. # engage speed is decided by pcm
ret.mass = 4353. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.86
ret.steerRatio = 14.4 # end to end is 13.46
- ret.steerRatioRear = 0.
ret.centerToFront = ret.wheelbase * 0.4
- ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia()
+ ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia()
+ ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking
elif candidate == CAR.BUICK_REGAL:
- ret.minEnableSpeed = 18 * CV.MPH_TO_MS
ret.mass = 3779. * CV.LB_TO_KG + STD_CARGO_KG # (3849+3708)/2
ret.wheelbase = 2.83 # 111.4 inches in meters
ret.steerRatio = 14.4 # guess for tourx
- ret.steerRatioRear = 0.
ret.centerToFront = ret.wheelbase * 0.4 # guess for tourx
elif candidate == CAR.CADILLAC_ATS:
- ret.minEnableSpeed = 18 * CV.MPH_TO_MS
ret.mass = 1601. + STD_CARGO_KG
ret.wheelbase = 2.78
ret.steerRatio = 15.3
- ret.steerRatioRear = 0.
- ret.centerToFront = ret.wheelbase * 0.49
+ ret.centerToFront = ret.wheelbase * 0.5
elif candidate == CAR.ESCALADE_ESV:
ret.minEnableSpeed = -1. # engage speed is decided by pcm
ret.mass = 2739. + STD_CARGO_KG
ret.wheelbase = 3.302
ret.steerRatio = 17.3
- ret.centerToFront = ret.wheelbase * 0.49
+ ret.centerToFront = ret.wheelbase * 0.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]]
ret.lateralTuning.pid.kf = 0.000045
tire_stiffness_factor = 1.0
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+ elif candidate == CAR.BOLT_EUV:
+ ret.mass = 1669. + STD_CARGO_KG
+ ret.wheelbase = 2.63779
+ ret.steerRatio = 16.8
+ ret.centerToFront = 2.15 # measured
+ tire_stiffness_factor = 1.0
+ ret.steerActuatorDelay = 0.2
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
+ elif candidate == CAR.SILVERADO:
+ ret.mass = 2200. + STD_CARGO_KG
+ ret.wheelbase = 3.75
+ ret.steerRatio = 16.3
+ ret.centerToFront = ret.wheelbase * 0.5
+ tire_stiffness_factor = 1.0
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
+ elif candidate == CAR.EQUINOX:
+ ret.mass = 3500. * CV.LB_TO_KG + STD_CARGO_KG
+ ret.wheelbase = 2.72
+ ret.steerRatio = 14.4
+ ret.centerToFront = ret.wheelbase * 0.4
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
tire_stiffness_factor=tire_stiffness_factor)
- ret.longitudinalTuning.kpBP = [5., 35.]
- ret.longitudinalTuning.kpV = [2.4, 1.5]
- ret.longitudinalTuning.kiBP = [0.]
- ret.longitudinalTuning.kiV = [0.36]
-
- ret.steerLimitTimer = 0.4
- ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
-
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- self.cp.update_strings(can_strings)
- self.cp_loopback.update_strings(can_strings)
-
- ret = self.CS.update(self.cp, self.cp_loopback)
-
- ret.canValid = self.cp.can_valid and self.cp_loopback.can_valid
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
-
- buttonEvents = []
+ def _update(self, c):
+ ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback)
if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT:
- be = car.CarState.ButtonEvent.new_message()
- be.type = ButtonType.unknown
- if self.CS.cruise_buttons != CruiseButtons.UNPRESS:
- be.pressed = True
- but = self.CS.cruise_buttons
- else:
- be.pressed = False
- but = self.CS.prev_cruise_buttons
- if but == CruiseButtons.RES_ACCEL:
- if not (ret.cruiseState.enabled and ret.standstill):
- be.type = ButtonType.accelCruise # Suppress resume button if we're resuming from stop so we don't adjust speed.
- elif but == CruiseButtons.DECEL_SET:
- be.type = ButtonType.decelCruise
- elif but == CruiseButtons.CANCEL:
- be.type = ButtonType.cancel
- elif but == CruiseButtons.MAIN:
- be.type = ButtonType.altButton3
- buttonEvents.append(be)
-
- ret.buttonEvents = buttonEvents
-
- events = self.create_common_events(ret, pcm_enable=False)
-
- if ret.vEgo < self.CP.minEnableSpeed:
+ buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)]
+ # Handle ACCButtons changing buttons mid-press
+ if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS:
+ buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS))
+
+ ret.buttonEvents = buttonEvents
+
+ # The ECM allows enabling on falling edge of set, but only rising edge of resume
+ events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low,
+ GearShifter.eco, GearShifter.manumatic],
+ pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
+ if not self.CP.pcmCruise:
+ if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.buttonEvents):
+ events.add(EventName.buttonEnable)
+
+ # Enabling at a standstill with brake is allowed
+ # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
+ below_min_enable_speed = ret.vEgo < self.CP.minEnableSpeed or self.CS.moving_backward
+ if below_min_enable_speed and not (ret.standstill and ret.brake >= 20 and
+ self.CP.networkLocation == NetworkLocation.fwdCamera):
events.add(EventName.belowEngageSpeed)
- if self.CS.park_brake:
- events.add(EventName.parkBrake)
if ret.cruiseState.standstill:
events.add(EventName.resumeRequired)
- if self.CS.pcm_acc_status == AccState.FAULTED:
- events.add(EventName.accFaulted)
if ret.vEgo < self.CP.minSteerSpeed:
- events.add(car.CarEvent.EventName.belowSteerSpeed)
-
- # handle button presses
- for b in ret.buttonEvents:
- # do enable on both accel and decel buttons
- if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed:
- events.add(EventName.buttonEnable)
- # do disable on button down
- if b.type == ButtonType.cancel and b.pressed:
- events.add(EventName.buttonCancel)
+ events.add(EventName.belowSteerSpeed)
ret.events = events.to_msg()
- # copy back carState packet to CS
- self.CS.out = ret.as_reader()
-
- return self.CS.out
+ return ret
def apply(self, c):
- hud_control = c.hudControl
- hud_v_cruise = hud_control.setSpeed
- if hud_v_cruise > 70:
- hud_v_cruise = 0
-
- # For Openpilot, "enabled" includes pre-enable.
- # In GM, PCM faults out if ACC command overlaps user gas.
- enabled = c.enabled and not self.CS.out.gasPressed
-
- ret = self.CC.update(c, enabled, self.CS, self.frame,
- c.actuators,
- hud_v_cruise, hud_control.lanesVisible,
- hud_control.leadVisible, hud_control.visualAlert)
-
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py
index 66fac54748..6904e6f899 100755
--- a/selfdrive/car/gm/radar_interface.py
+++ b/selfdrive/car/gm/radar_interface.py
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
import math
from cereal import car
+from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
from selfdrive.car.gm.values import DBC, CAR, CanBus
-from selfdrive.config import Conversions as CV
from selfdrive.car.interfaces import RadarInterfaceBase
RADAR_HEADER_MSG = 1120
diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py
index 8e09076713..84fa36a994 100644
--- a/selfdrive/car/gm/values.py
+++ b/selfdrive/car/gm/values.py
@@ -1,13 +1,20 @@
+from collections import defaultdict
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Union
+
from cereal import car
from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
Ecu = car.CarParams.Ecu
-class CarControllerParams():
- STEER_MAX = 300 # Safety limit, not LKA max. Trucks use 600.
- STEER_STEP = 2 # control frames per command
- STEER_DELTA_UP = 7
+
+class CarControllerParams:
+ STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output
+ ACTIVE_STEER_STEP = 2 # Active control frames per command (50hz)
+ INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz)
+ STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness
STEER_DELTA_DOWN = 17
- MIN_STEER_SPEED = 3. # m/s
STEER_DRIVER_ALLOWANCE = 50
STEER_DRIVER_MULTIPLIER = 4
STEER_DRIVER_FACTOR = 100
@@ -17,26 +24,41 @@ class CarControllerParams():
ADAS_KEEPALIVE_STEP = 100
CAMERA_KEEPALIVE_STEP = 100
- # Volt gasbrake lookups
- MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
- ZERO_GAS = 2048 # Coasting
- MAX_BRAKE = 350 # ~ -3.5 m/s^2 with regen
-
# Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we
# perform the closed loop control, and might need some
# to apply some more braking if we're on a downhill slope.
# Our controller should still keep the 2 second average above
# -3.5 m/s^2 as per planner limits
- ACCEL_MAX = 2. # m/s^2
- ACCEL_MIN = -4. # m/s^2
+ ACCEL_MAX = 2. # m/s^2
+ ACCEL_MIN = -4. # m/s^2
- MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
- GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX]
- GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS]
- BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.]
- BRAKE_LOOKUP_V = [MAX_BRAKE, 0.]
+ def __init__(self, CP):
+ # Gas/brake lookups
+ self.ZERO_GAS = 2048 # Coasting
+ self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen
+
+ if CP.carFingerprint in CAMERA_ACC_CAR:
+ self.MAX_GAS = 3400
+ self.MAX_ACC_REGEN = 1514
+ self.INACTIVE_REGEN = 1554
+ # Camera ACC vehicles have no regen while enabled.
+ # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly
+ max_regen_acceleration = 0.
+
+ else:
+ self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
+ self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
+ self.INACTIVE_REGEN = 1404
+ # ICE has much less engine braking force compared to regen in EVs,
+ # lower threshold removes some braking deadzone
+ max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
+
+ self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX]
+ self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS]
+
+ self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration]
+ self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.]
-STEER_THRESHOLD = 1.0
class CAR:
HOLDEN_ASTRA = "HOLDEN ASTRA RS-V BK 2017"
@@ -46,6 +68,49 @@ class CAR:
ACADIA = "GMC ACADIA DENALI 2018"
BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
+ BOLT_EUV = "CHEVROLET BOLT EUV 2022"
+ SILVERADO = "CHEVROLET SILVERADO 1500 2020"
+ EQUINOX = "CHEVROLET EQUINOX 2019"
+
+
+class Footnote(Enum):
+ OBD_II = CarFootnote(
+ 'Requires a community built ASCM harness. ' +
+ 'NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).',
+ Column.MODEL)
+
+
+@dataclass
+class GMCarInfo(CarInfo):
+ package: str = "Adaptive Cruise Control (ACC)"
+
+ def init_make(self, CP: car.CarParams):
+ if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera:
+ self.harness = Harness.gm
+ else:
+ self.harness = Harness.obd_ii
+ self.footnotes.append(Footnote.OBD_II)
+
+
+CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
+ CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"),
+ CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"),
+ CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"),
+ CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"),
+ CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"),
+ CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
+ CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
+ CAR.BOLT_EUV: [
+ GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"),
+ GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"),
+ ],
+ CAR.SILVERADO: [
+ GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"),
+ GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"),
+ ],
+ CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"),
+}
+
class CruiseButtons:
INIT = 0
@@ -64,13 +129,16 @@ class AccState:
class CanBus:
POWERTRAIN = 0
OBSTACLE = 1
+ CAMERA = 2
CHASSIS = 2
SW_GMLAN = 3
LOOPBACK = 128
+ DROPPED = 192
FINGERPRINTS = {
+ CAR.HOLDEN_ASTRA: [
# Astra BK MY17, ASCM unplugged
- CAR.HOLDEN_ASTRA: [{
+ {
190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7,
}],
CAR.VOLT: [
@@ -110,14 +178,25 @@ FINGERPRINTS = {
{
309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8
}],
+ CAR.BOLT_EUV: [
+ {
+ 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
+ }],
+ CAR.SILVERADO: [
+ {
+ 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
+ }],
+ CAR.EQUINOX: [
+ {
+ 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
+ }],
}
-DBC = {
- CAR.HOLDEN_ASTRA: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.VOLT: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.MALIBU: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.ACADIA: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.CADILLAC_ATS: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.BUICK_REGAL: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
- CAR.ESCALADE_ESV: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'),
-}
+DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'))
+
+EV_CAR = {CAR.VOLT, CAR.BOLT_EUV}
+
+# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness)
+CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX}
+
+STEER_THRESHOLD = 1.0
diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py
index 49581799a7..66a1485dc6 100644
--- a/selfdrive/car/honda/carcontroller.py
+++ b/selfdrive/car/honda/carcontroller.py
@@ -1,18 +1,21 @@
from collections import namedtuple
+
from cereal import car
-from common.realtime import DT_CTRL
-from selfdrive.controls.lib.drive_helpers import rate_limit
+from common.conversions import Conversions as CV
from common.numpy_fast import clip, interp
+from common.realtime import DT_CTRL
+from opendbc.can.packer import CANPacker
from selfdrive.car import create_gas_interceptor_command
from selfdrive.car.honda import hondacan
-from selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
-from opendbc.can.packer import CANPacker
+from selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
+from selfdrive.controls.lib.drive_helpers import rate_limit
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
+
def compute_gb_honda_bosch(accel, speed):
- #TODO returns 0s, is unused
+ # TODO returns 0s, is unused
return 0.0, 0.0
@@ -33,14 +36,14 @@ def compute_gas_brake(accel, speed, fingerprint):
return compute_gb_honda_nidec(accel, speed)
-#TODO not clear this does anything useful
-def actuator_hystereses(brake, braking, brake_steady, v_ego, car_fingerprint):
+# TODO not clear this does anything useful
+def actuator_hysteresis(brake, braking, brake_steady, v_ego, car_fingerprint):
# hyst params
- brake_hyst_on = 0.02 # to activate brakes exceed this value
- brake_hyst_off = 0.005 # to deactivate brakes below this value
- brake_hyst_gap = 0.01 # don't change brake command for small oscillations within this value
+ brake_hyst_on = 0.02 # to activate brakes exceed this value
+ brake_hyst_off = 0.005 # to deactivate brakes below this value
+ brake_hyst_gap = 0.01 # don't change brake command for small oscillations within this value
- #*** hysteresis logic to avoid brake blinking. go above 0.1 to trigger
+ # *** hysteresis logic to avoid brake blinking. go above 0.1 to trigger
if (brake < brake_hyst_on and not braking) or brake < brake_hyst_off:
brake = 0.
braking = brake > 0.
@@ -92,173 +95,173 @@ def process_hud_alert(hud_alert):
HUDData = namedtuple("HUDData",
- ["pcm_accel", "v_cruise", "car",
- "lanes", "fcw", "acc_alert", "steer_required"])
+ ["pcm_accel", "v_cruise", "lead_visible",
+ "lanes_visible", "fcw", "acc_alert", "steer_required"])
+
+def rate_limit_steer(new_steer, last_steer):
+ # TODO just hardcoded ramp to min/max in 0.33s for all Honda
+ MAX_DELTA = 3 * DT_CTRL
+ return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA)
-class CarController():
+
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
+ self.packer = CANPacker(dbc_name)
+ self.params = CarControllerParams(CP)
+ self.frame = 0
+
self.braking = False
self.brake_steady = 0.
self.brake_last = 0.
self.apply_brake_last = 0
self.last_pump_ts = 0.
- self.packer = CANPacker(dbc_name)
- self.accel = 0
- self.speed = 0
- self.gas = 0
- self.brake = 0
+ self.accel = 0.0
+ self.speed = 0.0
+ self.gas = 0.0
+ self.brake = 0.0
+ self.last_steer = 0.0
- self.params = CarControllerParams(CP)
-
- def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd,
- hud_v_cruise, hud_show_lanes, hud_show_car, hud_alert):
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+ hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH if hud_control.speedVisible else 255
+ pcm_cancel_cmd = CC.cruiseControl.cancel
- P = self.params
-
- if active:
+ if CC.longActive:
accel = actuators.accel
- gas, brake = compute_gas_brake(actuators.accel, CS.out.vEgo, CS.CP.carFingerprint)
+ gas, brake = compute_gas_brake(actuators.accel, CS.out.vEgo, self.CP.carFingerprint)
else:
accel = 0.0
gas, brake = 0.0, 0.0
+ # *** rate limit steer ***
+ limited_steer = rate_limit_steer(actuators.steer, self.last_steer)
+ self.last_steer = limited_steer
+
# *** apply brake hysteresis ***
- pre_limit_brake, self.braking, self.brake_steady = actuator_hystereses(brake, self.braking, self.brake_steady, CS.out.vEgo, CS.CP.carFingerprint)
+ pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady,
+ CS.out.vEgo, self.CP.carFingerprint)
# *** rate limit after the enable check ***
self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2., DT_CTRL)
# vehicle hud display, wait for one update from 10Hz 0x304 msg
- if hud_show_lanes:
- hud_lanes = 1
- else:
- hud_lanes = 0
-
- if enabled:
- if hud_show_car:
- hud_car = 2
- else:
- hud_car = 1
- else:
- hud_car = 0
-
- fcw_display, steer_required, acc_alert = process_hud_alert(hud_alert)
-
+ fcw_display, steer_required, acc_alert = process_hud_alert(hud_control.visualAlert)
# **** process the car messages ****
# steer torque is converted back to CAN reference (positive when steering right)
- apply_steer = int(interp(-actuators.steer * P.STEER_MAX, P.STEER_LOOKUP_BP, P.STEER_LOOKUP_V))
-
- lkas_active = active and not CS.steer_not_allowed
+ apply_steer = int(interp(-limited_steer * self.params.STEER_MAX,
+ self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
- # Send CAN commands.
+ # Send CAN commands
can_sends = []
# tester present - w/ no response (keeps radar disabled)
- if CS.CP.carFingerprint in HONDA_BOSCH and CS.CP.openpilotLongitudinalControl:
- if (frame % 10) == 0:
+ if self.CP.carFingerprint in HONDA_BOSCH and self.CP.openpilotLongitudinalControl:
+ if self.frame % 10 == 0:
can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1))
# Send steering command.
- idx = frame % 4
- can_sends.append(hondacan.create_steering_control(self.packer, apply_steer,
- lkas_active, CS.CP.carFingerprint, idx, CS.CP.openpilotLongitudinalControl))
-
- stopping = actuators.longControlState == LongCtrlState.stopping
+ can_sends.append(hondacan.create_steering_control(self.packer, apply_steer, CC.latActive, self.CP.carFingerprint,
+ CS.CP.openpilotLongitudinalControl))
# wind brake from air resistance decel at high speed
wind_brake = interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15])
# all of this is only relevant for HONDA NIDEC
- max_accel = interp(CS.out.vEgo, P.NIDEC_MAX_ACCEL_BP, P.NIDEC_MAX_ACCEL_V)
+ max_accel = interp(CS.out.vEgo, self.params.NIDEC_MAX_ACCEL_BP, self.params.NIDEC_MAX_ACCEL_V)
# TODO this 1.44 is just to maintain previous behavior
pcm_speed_BP = [-wind_brake,
- -wind_brake*(3/4),
- 0.0,
- 0.5]
+ -wind_brake * (3 / 4),
+ 0.0,
+ 0.5]
# The Honda ODYSSEY seems to have different PCM_ACCEL
# msgs, is it other cars too?
- if CS.CP.enableGasInterceptor:
+ if self.CP.enableGasInterceptor or not CC.longActive:
pcm_speed = 0.0
pcm_accel = int(0.0)
- elif CS.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL:
+ elif self.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL:
pcm_speed_V = [0.0,
clip(CS.out.vEgo - 3.0, 0.0, 100.0),
clip(CS.out.vEgo + 0.0, 0.0, 100.0),
clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
- pcm_speed = interp(gas-brake, pcm_speed_BP, pcm_speed_V)
- pcm_accel = int((1.0) * 0xc6)
+ pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V)
+ pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX)
else:
pcm_speed_V = [0.0,
clip(CS.out.vEgo - 2.0, 0.0, 100.0),
clip(CS.out.vEgo + 2.0, 0.0, 100.0),
clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
- pcm_speed = interp(gas-brake, pcm_speed_BP, pcm_speed_V)
- pcm_accel = int(clip((accel/1.44)/max_accel, 0.0, 1.0) * 0xc6)
+ pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V)
+ pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX)
- if not CS.CP.openpilotLongitudinalControl:
- if (frame % 2) == 0:
- idx = frame // 2
- can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, CS.CP.carFingerprint, idx))
+ if not self.CP.openpilotLongitudinalControl:
+ if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message
+ can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CP.carFingerprint))
# If using stock ACC, spam cancel command to kill gas when OP disengages.
if pcm_cancel_cmd:
- can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, idx, CS.CP.carFingerprint))
- elif CS.out.cruiseState.standstill:
- can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, idx, CS.CP.carFingerprint))
+ can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.CANCEL, self.CP.carFingerprint))
+ elif CC.cruiseControl.resume:
+ can_sends.append(hondacan.spam_buttons_command(self.packer, CruiseButtons.RES_ACCEL, self.CP.carFingerprint))
else:
# Send gas and brake commands.
- if (frame % 2) == 0:
- idx = frame // 2
- ts = frame * DT_CTRL
-
- if CS.CP.carFingerprint in HONDA_BOSCH:
- self.accel = clip(accel, P.BOSCH_ACCEL_MIN, P.BOSCH_ACCEL_MAX)
- self.gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP, P.BOSCH_GAS_LOOKUP_V)
- can_sends.extend(hondacan.create_acc_commands(self.packer, enabled, active, accel, self.gas, idx, stopping, CS.CP.carFingerprint))
+ if self.frame % 2 == 0:
+ ts = self.frame * DT_CTRL
+
+ if self.CP.carFingerprint in HONDA_BOSCH:
+ self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX)
+ self.gas = interp(accel, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V)
+
+ stopping = actuators.longControlState == LongCtrlState.stopping
+ can_sends.extend(hondacan.create_acc_commands(self.packer, CC.enabled, CC.longActive, self.accel, self.gas,
+ stopping, self.CP.carFingerprint))
else:
apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0)
- apply_brake = int(clip(apply_brake * P.NIDEC_BRAKE_MAX, 0, P.NIDEC_BRAKE_MAX - 1))
+ apply_brake = int(clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1))
pump_on, self.last_pump_ts = brake_pump_hysteresis(apply_brake, self.apply_brake_last, self.last_pump_ts, ts)
pcm_override = True
can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on,
- pcm_override, pcm_cancel_cmd, fcw_display, idx, CS.CP.carFingerprint, CS.stock_brake))
+ pcm_override, pcm_cancel_cmd, fcw_display,
+ self.CP.carFingerprint, CS.stock_brake))
self.apply_brake_last = apply_brake
- self.brake = apply_brake / P.NIDEC_BRAKE_MAX
+ self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX
- if CS.CP.enableGasInterceptor:
+ if self.CP.enableGasInterceptor:
# way too aggressive at low speed without this
gas_mult = interp(CS.out.vEgo, [0., 10.], [0.4, 1.0])
# send exactly zero if apply_gas is zero. Interceptor will send the max between read value and apply_gas.
# This prevents unexpected pedal range rescaling
# Sending non-zero gas when OP is not enabled will cause the PCM not to respond to throttle as expected
# when you do enable.
- if active:
- self.gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.)
+ if CC.longActive:
+ self.gas = clip(gas_mult * (gas - brake + wind_brake * 3 / 4), 0., 1.)
else:
self.gas = 0.0
- can_sends.append(create_gas_interceptor_command(self.packer, self.gas, idx))
+ can_sends.append(create_gas_interceptor_command(self.packer, self.gas, self.frame // 2))
# Send dashboard UI commands.
- if (frame % 10) == 0:
- idx = (frame//10) % 4
- hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car,
- hud_lanes, fcw_display, acc_alert, steer_required)
- can_sends.extend(hondacan.create_ui_commands(self.packer, CS.CP, pcm_speed, hud, CS.is_metric, idx, CS.stock_hud))
+ if self.frame % 10 == 0:
+ hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_control.leadVisible,
+ hud_control.lanesVisible, fcw_display, acc_alert, steer_required)
+ can_sends.extend(hondacan.create_ui_commands(self.packer, self.CP, CC.enabled, pcm_speed, hud, CS.is_metric, CS.acc_hud, CS.lkas_hud))
- if (CS.CP.openpilotLongitudinalControl) and (CS.CP.carFingerprint not in HONDA_BOSCH):
+ if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH:
self.speed = pcm_speed
- if not CS.CP.enableGasInterceptor:
- self.gas = pcm_accel / 0xc6
+ if not self.CP.enableGasInterceptor:
+ self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
new_actuators = actuators.copy()
new_actuators.speed = self.speed
new_actuators.accel = self.accel
new_actuators.gas = self.gas
new_actuators.brake = self.brake
+ new_actuators.steer = self.last_steer
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py
index 0a56a02b94..a37667fd3a 100644
--- a/selfdrive/car/honda/carstate.py
+++ b/selfdrive/car/honda/carstate.py
@@ -1,11 +1,13 @@
-from cereal import car
from collections import defaultdict
+
+from cereal import car
+from common.conversions import Conversions as CV
from common.numpy_fast import interp
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
-from selfdrive.config import Conversions as CV
+from selfdrive.car.honda.hondacan import get_pt_bus
+from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS
from selfdrive.car.interfaces import CarStateBase
-from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL
TransmissionType = car.CarParams.TransmissionType
@@ -21,9 +23,10 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
("STEER_ANGLE_RATE", "STEERING_SENSORS"),
("MOTOR_TORQUE", "STEER_MOTOR_TORQUE"),
("STEER_TORQUE_SENSOR", "STEER_STATUS"),
+ ("IMPERIAL_UNIT", "CAR_SPEED"),
+ ("ROUGH_CAR_SPEED_2", "CAR_SPEED"),
("LEFT_BLINKER", "SCM_FEEDBACK"),
("RIGHT_BLINKER", "SCM_FEEDBACK"),
- ("GEAR", gearbox_msg),
("SEATBELT_DRIVER_LAMP", "SEATBELT_STATUS"),
("SEATBELT_DRIVER_LATCHED", "SEATBELT_STATUS"),
("BRAKE_PRESSED", "POWERTRAIN_DATA"),
@@ -34,6 +37,7 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
("BRAKE_HOLD_ACTIVE", "VSA_STATUS"),
("STEER_STATUS", "STEER_STATUS"),
("GEAR_SHIFTER", gearbox_msg),
+ ("GEAR", gearbox_msg),
("PEDAL_GAS", "POWERTRAIN_DATA"),
("CRUISE_SETTING", "SCM_BUTTONS"),
("ACC_STATUS", "POWERTRAIN_DATA"),
@@ -47,9 +51,10 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
("SEATBELT_STATUS", 10),
("CRUISE", 10),
("POWERTRAIN_DATA", 100),
+ ("CAR_SPEED", 10),
("VSA_STATUS", 50),
("STEER_STATUS", 100),
- ("STEER_MOTOR_TORQUE", 0), # TODO: not on every car
+ ("STEER_MOTOR_TORQUE", 0), # TODO: not on every car
]
if CP.carFingerprint == CAR.ODYSSEY_CHN:
@@ -72,17 +77,13 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
signals.append(("BRAKE_PRESSED", "BRAKE_MODULE"))
checks.append(("BRAKE_MODULE", 50))
- if CP.carFingerprint in HONDA_BOSCH:
- signals += [
- ("EPB_STATE", "EPB_STATUS"),
- ("IMPERIAL_UNIT", "CAR_SPEED"),
- ]
- checks += [
- ("EPB_STATUS", 50),
- ("CAR_SPEED", 10),
- ]
+ if CP.carFingerprint in (HONDA_BOSCH | {CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN}):
+ signals.append(("EPB_STATE", "EPB_STATUS"))
+ checks.append(("EPB_STATUS", 50))
- if not CP.openpilotLongitudinalControl:
+ if CP.carFingerprint in HONDA_BOSCH:
+ # these messages are on camera bus on radarless cars
+ if not CP.openpilotLongitudinalControl and CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
signals += [
("CRUISE_CONTROL_LABEL", "ACC_HUD"),
("CRUISE_SPEED", "ACC_HUD"),
@@ -102,34 +103,16 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
else:
checks.append(("CRUISE_PARAMS", 50))
- if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E):
+ if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022):
signals.append(("DRIVERS_DOOR_OPEN", "SCM_FEEDBACK"))
- elif CP.carFingerprint == CAR.ODYSSEY_CHN:
+ elif CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV):
signals.append(("DRIVERS_DOOR_OPEN", "SCM_BUTTONS"))
- elif CP.carFingerprint in (CAR.FREED, CAR.HRV):
- signals += [("DRIVERS_DOOR_OPEN", "SCM_BUTTONS"),
- ("WHEELS_MOVING", "STANDSTILL")]
else:
signals += [("DOOR_OPEN_FL", "DOORS_STATUS"),
("DOOR_OPEN_FR", "DOORS_STATUS"),
("DOOR_OPEN_RL", "DOORS_STATUS"),
- ("DOOR_OPEN_RR", "DOORS_STATUS"),
- ("WHEELS_MOVING", "STANDSTILL")]
- checks += [
- ("DOORS_STATUS", 3),
- ("STANDSTILL", 50),
- ]
-
- if CP.carFingerprint == CAR.CIVIC:
- signals += [("IMPERIAL_UNIT", "HUD_SETTING"),
- ("EPB_STATE", "EPB_STATUS")]
- checks += [
- ("HUD_SETTING", 50),
- ("EPB_STATUS", 50),
- ]
- elif CP.carFingerprint in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
- signals.append(("EPB_STATE", "EPB_STATUS"))
- checks.append(("EPB_STATUS", 50))
+ ("DOOR_OPEN_RR", "DOORS_STATUS")]
+ checks.append(("DOORS_STATUS", 3))
# add gas interceptor reading if we are using it
if CP.enableGasInterceptor:
@@ -168,6 +151,10 @@ class CarState(CarStateBase):
self.cruise_setting = 0
self.v_cruise_pcm_prev = 0
+ # When available we use cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] to populate vEgoCluster
+ # However, on cars without a digital speedometer this is not always present (HRV, FIT, CRV 2016, ILX and RDX)
+ self.dash_speed_seen = False
+
def update(self, cp, cp_cam, cp_body):
ret = car.CarState.new_message()
@@ -178,30 +165,31 @@ class CarState(CarStateBase):
# update prevs, update must run once per loop
self.prev_cruise_buttons = self.cruise_buttons
self.prev_cruise_setting = self.cruise_setting
+ self.cruise_setting = cp.vl["SCM_BUTTONS"]["CRUISE_SETTING"]
+ self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"]
+
+ # used for car hud message
+ self.is_metric = not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
# ******************* parse out can *******************
- # TODO: find wheels moving bit in dbc
- if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E):
- ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 0.1
+ # STANDSTILL->WHEELS_MOVING bit can be noisy around zero, so use XMISSION_SPEED
+ # panda checks if the signal is non-zero
+ ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 1e-5
+ # TODO: find a common signal across all cars
+ if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022):
ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"])
- elif self.CP.carFingerprint == CAR.ODYSSEY_CHN:
- ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 0.1
- ret.doorOpen = bool(cp.vl["SCM_BUTTONS"]["DRIVERS_DOOR_OPEN"])
- elif self.CP.carFingerprint in (CAR.FREED, CAR.HRV):
- ret.standstill = not cp.vl["STANDSTILL"]["WHEELS_MOVING"]
+ elif self.CP.carFingerprint in (CAR.ODYSSEY_CHN, CAR.FREED, CAR.HRV):
ret.doorOpen = bool(cp.vl["SCM_BUTTONS"]["DRIVERS_DOOR_OPEN"])
else:
- ret.standstill = not cp.vl["STANDSTILL"]["WHEELS_MOVING"]
ret.doorOpen = any([cp.vl["DOORS_STATUS"]["DOOR_OPEN_FL"], cp.vl["DOORS_STATUS"]["DOOR_OPEN_FR"],
cp.vl["DOORS_STATUS"]["DOOR_OPEN_RL"], cp.vl["DOORS_STATUS"]["DOOR_OPEN_RR"]])
ret.seatbeltUnlatched = bool(cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LAMP"] or not cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LATCHED"])
steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]]
- ret.steerError = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT")
- # NO_TORQUE_ALERT_2 can be caused by bump OR steering nudge from driver
- self.steer_not_allowed = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_2")
+ ret.steerFaultPermanent = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT")
# LOW_SPEED_LOCKOUT is not worth a warning
- ret.steerWarning = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
+ # NO_TORQUE_ALERT_2 can be caused by bump or steering nudge from driver
+ ret.steerFaultTemporary = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2")
if self.CP.openpilotLongitudinalControl:
self.brake_error = cp.vl["STANDSTILL"]["BRAKE_ERROR_1"] or cp.vl["STANDSTILL"]["BRAKE_ERROR_2"]
@@ -220,30 +208,32 @@ class CarState(CarStateBase):
ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3
+ if self.dash_speed_seen:
+ conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
+ ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion
+
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"]
ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE_RATE"]
- self.cruise_setting = cp.vl["SCM_BUTTONS"]["CRUISE_SETTING"]
- self.cruise_buttons = cp.vl["SCM_BUTTONS"]["CRUISE_BUTTONS"]
-
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(
250, cp.vl["SCM_FEEDBACK"]["LEFT_BLINKER"], cp.vl["SCM_FEEDBACK"]["RIGHT_BLINKER"])
ret.brakeHoldActive = cp.vl["VSA_STATUS"]["BRAKE_HOLD_ACTIVE"] == 1
- if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH,
- CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E):
- self.park_brake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0
- else:
- self.park_brake = 0 # TODO
+ # TODO: set for all cars
+ if self.CP.carFingerprint in (HONDA_BOSCH | {CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN}):
+ ret.parkingBrake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0
gear = int(cp.vl[self.gearbox_msg]["GEAR_SHIFTER"])
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear, None))
if self.CP.enableGasInterceptor:
- ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
+ # Same threshold as panda, equivalent to 1e-5 with previous DBC scaling
+ ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2
+ ret.gasPressed = ret.gas > 492
else:
ret.gas = cp.vl["POWERTRAIN_DATA"]["PEDAL_GAS"]
- ret.gasPressed = ret.gas > 1e-5
+ ret.gasPressed = ret.gas > 1e-5
ret.steeringTorque = cp.vl["STEER_STATUS"]["STEER_TORQUE_SENSOR"]
ret.steeringTorqueEps = cp.vl["STEER_MOTOR_TORQUE"]["MOTOR_TORQUE"]
@@ -251,11 +241,15 @@ class CarState(CarStateBase):
if self.CP.carFingerprint in HONDA_BOSCH:
if not self.CP.openpilotLongitudinalControl:
- ret.cruiseState.nonAdaptive = cp.vl["ACC_HUD"]["CRUISE_CONTROL_LABEL"] != 0
- ret.cruiseState.standstill = cp.vl["ACC_HUD"]["CRUISE_SPEED"] == 252.
+ # ACC_HUD is on camera bus on radarless cars
+ acc_hud = cp_cam.vl["ACC_HUD"] if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS else cp.vl["ACC_HUD"]
+ ret.cruiseState.nonAdaptive = acc_hud["CRUISE_CONTROL_LABEL"] != 0
+ ret.cruiseState.standstill = acc_hud["CRUISE_SPEED"] == 252.
+ # on certain cars, CRUISE_SPEED changes to imperial with car's unit setting
+ conversion = CV.MPH_TO_MS if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS and not self.is_metric else CV.KPH_TO_MS
# On set, cruise set speed pulses between 254~255 and the set speed prev is set to avoid this.
- ret.cruiseState.speed = self.v_cruise_pcm_prev if cp.vl["ACC_HUD"]["CRUISE_SPEED"] > 160.0 else cp.vl["ACC_HUD"]["CRUISE_SPEED"] * CV.KPH_TO_MS
+ ret.cruiseState.speed = self.v_cruise_pcm_prev if acc_hud["CRUISE_SPEED"] > 160.0 else acc_hud["CRUISE_SPEED"] * conversion
self.v_cruise_pcm_prev = ret.cruiseState.speed
else:
ret.cruiseState.speed = cp.vl["CRUISE"]["CRUISE_SPEED_PCM"] * CV.KPH_TO_MS
@@ -284,26 +278,21 @@ class CarState(CarStateBase):
if ret.brake > 0.1:
ret.brakePressed = True
- # TODO: discover the CAN msg that has the imperial unit bit for all other cars
- if self.CP.carFingerprint in (CAR.CIVIC, ):
- self.is_metric = not cp.vl["HUD_SETTING"]["IMPERIAL_UNIT"]
- elif self.CP.carFingerprint in HONDA_BOSCH:
- self.is_metric = not cp.vl["CAR_SPEED"]["IMPERIAL_UNIT"]
- else:
- self.is_metric = False
-
if self.CP.carFingerprint in HONDA_BOSCH:
- ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5)
+ # TODO: find the radarless AEB_STATUS bit and make sure ACCEL_COMMAND is correct to enable AEB alerts
+ if self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS:
+ ret.stockAeb = (not self.CP.openpilotLongitudinalControl) and bool(cp.vl["ACC_CONTROL"]["AEB_STATUS"] and cp.vl["ACC_CONTROL"]["ACCEL_COMMAND"] < -1e-5)
else:
ret.stockAeb = bool(cp_cam.vl["BRAKE_COMMAND"]["AEB_REQ_1"] and cp_cam.vl["BRAKE_COMMAND"]["COMPUTER_BRAKE"] > 1e-5)
- if self.CP.carFingerprint in HONDA_BOSCH:
- self.stock_hud = False
- ret.stockFcw = False
- else:
+ self.acc_hud = False
+ self.lkas_hud = False
+ if self.CP.carFingerprint not in HONDA_BOSCH:
ret.stockFcw = cp_cam.vl["BRAKE_COMMAND"]["FCW"] != 0
- self.stock_hud = cp_cam.vl["ACC_HUD"]
+ self.acc_hud = cp_cam.vl["ACC_HUD"]
self.stock_brake = cp_cam.vl["BRAKE_COMMAND"]
+ if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS:
+ self.lkas_hud = cp_cam.vl["LKAS_HUD"]
if self.CP.enableBsm and self.CP.carFingerprint in (CAR.CRV_5G, ):
# BSM messages are on B-CAN, requires a panda forwarding B-CAN messages to CAN 0
@@ -315,8 +304,7 @@ class CarState(CarStateBase):
def get_can_parser(self, CP):
signals, checks = get_can_signals(CP, self.gearbox_msg, self.main_on_sig_msg)
- bus_pt = 1 if CP.carFingerprint in HONDA_BOSCH else 0
- return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus_pt)
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_pt_bus(CP.carFingerprint))
@staticmethod
def get_cam_can_parser(CP):
@@ -325,7 +313,17 @@ class CarState(CarStateBase):
("STEERING_CONTROL", 100),
]
- if CP.carFingerprint not in HONDA_BOSCH:
+ if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
+ signals.append(("LKAS_PROBLEM", "LKAS_HUD"))
+ checks.append(("LKAS_HUD", 10))
+ if not CP.openpilotLongitudinalControl:
+ signals += [
+ ("CRUISE_SPEED", "ACC_HUD"),
+ ("CRUISE_CONTROL_LABEL", "ACC_HUD"),
+ ]
+ checks.append(("ACC_HUD", 10))
+
+ elif CP.carFingerprint not in HONDA_BOSCH:
signals += [("COMPUTER_BRAKE", "BRAKE_COMMAND"),
("AEB_REQ_1", "BRAKE_COMMAND"),
("FCW", "BRAKE_COMMAND"),
diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py
index db7104cd4f..87f8e6c5de 100644
--- a/selfdrive/car/honda/hondacan.py
+++ b/selfdrive/car/honda/hondacan.py
@@ -1,5 +1,5 @@
-from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, CAR, CarControllerParams
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
+from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, HONDA_BOSCH_RADARLESS, CAR, CarControllerParams
# CAN bus layout with relay
# 0 = ACC-CAN - radar side
@@ -7,8 +7,9 @@ from selfdrive.config import Conversions as CV
# 2 = ACC-CAN - camera side
# 3 = F-CAN A - OBDII port
+
def get_pt_bus(car_fingerprint):
- return 1 if car_fingerprint in HONDA_BOSCH else 0
+ return 1 if car_fingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) else 0
def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False):
@@ -18,7 +19,8 @@ def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False):
# normally steering commands are sent to radar, which forwards them to powertrain bus
return 0
-def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, idx, car_fingerprint, stock_brake):
+
+def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake):
# TODO: do we loose pressure if we keep pump off for long?
brakelights = apply_brake > 0
brake_rq = apply_brake > 0
@@ -40,10 +42,10 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_
"AEB_STATUS": 0,
}
bus = get_pt_bus(car_fingerprint)
- return packer.make_can_msg("BRAKE_COMMAND", bus, values, idx)
+ return packer.make_can_msg("BRAKE_COMMAND", bus, values)
-def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_fingerprint):
+def create_acc_commands(packer, enabled, active, accel, gas, stopping, car_fingerprint):
commands = []
bus = get_pt_bus(car_fingerprint)
min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0]
@@ -65,7 +67,7 @@ def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_
"STANDSTILL": standstill,
"STANDSTILL_RELEASE": standstill_release,
}
- commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values, idx))
+ commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values))
acc_control_on_values = {
"SET_TO_3": 0x03,
@@ -74,20 +76,21 @@ def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_
"SET_TO_75": 0x75,
"SET_TO_30": 0x30,
}
- commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values, idx))
+ commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values))
return commands
-def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, idx, radar_disabled):
+
+def create_steering_control(packer, apply_steer, lkas_active, car_fingerprint, radar_disabled):
values = {
"STEER_TORQUE": apply_steer if lkas_active else 0,
"STEER_TORQUE_REQUEST": lkas_active,
}
bus = get_lkas_cmd_bus(car_fingerprint, radar_disabled)
- return packer.make_can_msg("STEERING_CONTROL", bus, values, idx)
+ return packer.make_can_msg("STEERING_CONTROL", bus, values)
-def create_bosch_supplemental_1(packer, car_fingerprint, idx):
+def create_bosch_supplemental_1(packer, car_fingerprint):
# non-active params
values = {
"SET_ME_X04": 0x04,
@@ -95,80 +98,79 @@ def create_bosch_supplemental_1(packer, car_fingerprint, idx):
"SET_ME_X10": 0x10,
}
bus = get_lkas_cmd_bus(car_fingerprint)
- return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values, idx)
+ return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values)
-def create_ui_commands(packer, CP, pcm_speed, hud, is_metric, idx, stock_hud):
+def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud):
commands = []
bus_pt = get_pt_bus(CP.carFingerprint)
radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl
bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled)
if CP.openpilotLongitudinalControl:
+ acc_hud_values = {
+ 'CRUISE_SPEED': hud.v_cruise,
+ 'ENABLE_MINI_CAR': 1,
+ 'HUD_DISTANCE': 0, # max distance setting on display
+ 'IMPERIAL_UNIT': int(not is_metric),
+ 'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0,
+ 'SET_ME_X01_2': 1,
+ }
+
if CP.carFingerprint in HONDA_BOSCH:
- acc_hud_values = {
- 'CRUISE_SPEED': hud.v_cruise,
- 'ENABLE_MINI_CAR': 1,
- 'SET_TO_1': 1,
- 'HUD_LEAD': hud.car,
- 'HUD_DISTANCE': 3,
- 'ACC_ON': hud.car != 0,
- 'SET_TO_X1': 1,
- 'IMPERIAL_UNIT': int(not is_metric),
- 'FCM_OFF': 1,
- }
+ acc_hud_values['ACC_ON'] = int(enabled)
+ acc_hud_values['FCM_OFF'] = 1
+ acc_hud_values['FCM_OFF_2'] = 1
else:
- acc_hud_values = {
- 'PCM_SPEED': pcm_speed * CV.MS_TO_KPH,
- 'PCM_GAS': hud.pcm_accel,
- 'CRUISE_SPEED': hud.v_cruise,
- 'ENABLE_MINI_CAR': 1,
- 'HUD_LEAD': hud.car,
- 'HUD_DISTANCE': 3, # max distance setting on display
- 'IMPERIAL_UNIT': int(not is_metric),
- 'SET_ME_X01_2': 1,
- 'SET_ME_X01': 1,
- "FCM_OFF": stock_hud["FCM_OFF"],
- "FCM_OFF_2": stock_hud["FCM_OFF_2"],
- "FCM_PROBLEM": stock_hud["FCM_PROBLEM"],
- "ICONS": stock_hud["ICONS"],
- }
- commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values, idx))
+ acc_hud_values['PCM_SPEED'] = pcm_speed * CV.MS_TO_KPH
+ acc_hud_values['PCM_GAS'] = hud.pcm_accel
+ acc_hud_values['SET_ME_X01'] = 1
+ acc_hud_values['FCM_OFF'] = acc_hud['FCM_OFF']
+ acc_hud_values['FCM_OFF_2'] = acc_hud['FCM_OFF_2']
+ acc_hud_values['FCM_PROBLEM'] = acc_hud['FCM_PROBLEM']
+ acc_hud_values['ICONS'] = acc_hud['ICONS']
+ commands.append(packer.make_can_msg("ACC_HUD", bus_pt, acc_hud_values))
lkas_hud_values = {
'SET_ME_X41': 0x41,
- 'SET_ME_X48': 0x48,
'STEERING_REQUIRED': hud.steer_required,
- 'SOLID_LANES': hud.lanes,
+ 'SOLID_LANES': hud.lanes_visible,
'BEEP': 0,
}
+ if CP.carFingerprint in HONDA_BOSCH_RADARLESS:
+ lkas_hud_values['LANE_LINES'] = 3
+ lkas_hud_values['DASHED_LANES'] = hud.lanes_visible
+ # car likely needs to see LKAS_PROBLEM fall within a specific time frame, so forward from camera
+ lkas_hud_values['LKAS_PROBLEM'] = lkas_hud['LKAS_PROBLEM']
+
if not (CP.flags & HondaFlags.BOSCH_EXT_HUD):
lkas_hud_values['SET_ME_X48'] = 0x48
if CP.flags & HondaFlags.BOSCH_EXT_HUD and not CP.openpilotLongitudinalControl:
- commands.append(packer.make_can_msg('LKAS_HUD_A', bus_lkas, lkas_hud_values, idx))
- commands.append(packer.make_can_msg('LKAS_HUD_B', bus_lkas, lkas_hud_values, idx))
+ commands.append(packer.make_can_msg('LKAS_HUD_A', bus_lkas, lkas_hud_values))
+ commands.append(packer.make_can_msg('LKAS_HUD_B', bus_lkas, lkas_hud_values))
else:
- commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values, idx))
+ commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values))
if radar_disabled and CP.carFingerprint in HONDA_BOSCH:
radar_hud_values = {
'CMBS_OFF': 0x01,
'SET_TO_1': 0x01,
}
- commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values, idx))
+ commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values))
if CP.carFingerprint == CAR.CIVIC_BOSCH:
- commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {}, idx))
+ commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {}))
return commands
-def spam_buttons_command(packer, button_val, idx, car_fingerprint):
+def spam_buttons_command(packer, button_val, car_fingerprint):
values = {
'CRUISE_BUTTONS': button_val,
'CRUISE_SETTING': 0,
}
- bus = get_pt_bus(car_fingerprint)
- return packer.make_can_msg("SCM_BUTTONS", bus, values, idx)
+ # send buttons to camera on radarless cars
+ bus = 2 if car_fingerprint in HONDA_BOSCH_RADARLESS else get_pt_bus(car_fingerprint)
+ return packer.make_can_msg("SCM_BUTTONS", bus, values)
diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py
index 94e4305909..990238ae5d 100755
--- a/selfdrive/car/honda/interface.py
+++ b/selfdrive/car/honda/interface.py
@@ -1,18 +1,19 @@
#!/usr/bin/env python3
from cereal import car
from panda import Panda
+from common.conversions import Conversions as CV
from common.numpy_fast import interp
-from common.params import Params
-from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL
-from selfdrive.car import STD_CARGO_KG, CivicParams, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS
+from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_tire_stiffness, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
from selfdrive.car.disable_ecu import disable_ecu
-from selfdrive.config import Conversions as CV
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
TransmissionType = car.CarParams.TransmissionType
+BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.DECEL_SET: ButtonType.decelCruise,
+ CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
class CarInterface(CarInterfaceBase):
@@ -28,17 +29,18 @@ class CarInterface(CarInterfaceBase):
return CarControllerParams.NIDEC_ACCEL_MIN, interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS)
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # pylint: disable=dangerous-default-value
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "honda"
if candidate in HONDA_BOSCH:
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)]
ret.radarOffCan = True
- # Disable the radar and let openpilot control longitudinal
- # WARNING: THIS DISABLES AEB!
- ret.openpilotLongitudinalControl = Params().get_bool("DisableRadar")
+ if candidate not in HONDA_BOSCH_RADARLESS:
+ # Disable the radar and let openpilot control longitudinal
+ # WARNING: THIS DISABLES AEB!
+ ret.experimentalLongitudinalAvailable = True
+ ret.openpilotLongitudinalControl = experimental_long
ret.pcmCruise = not ret.openpilotLongitudinalControl
else:
@@ -84,7 +86,6 @@ class CarInterface(CarInterfaceBase):
eps_modified = True
if candidate == CAR.CIVIC:
- stop_and_go = True
ret.mass = CivicParams.MASS
ret.wheelbase = CivicParams.WHEELBASE
ret.centerToFront = CivicParams.CENTER_TO_FRONT
@@ -103,8 +104,7 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
tire_stiffness_factor = 1.
- elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL):
- stop_and_go = True
+ elif candidate in (CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CIVIC_2022):
ret.mass = CivicParams.MASS
ret.wheelbase = CivicParams.WHEELBASE
ret.centerToFront = CivicParams.CENTER_TO_FRONT
@@ -114,7 +114,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
elif candidate in (CAR.ACCORD, CAR.ACCORDH):
- stop_and_go = True
ret.mass = 3279. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83
ret.centerToFront = ret.wheelbase * 0.39
@@ -128,7 +127,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
elif candidate == CAR.ACURA_ILX:
- stop_and_go = False
ret.mass = 3095. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.37
@@ -138,7 +136,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
elif candidate in (CAR.CRV, CAR.CRV_EU):
- stop_and_go = False
ret.mass = 3572. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.62
ret.centerToFront = ret.wheelbase * 0.41
@@ -149,7 +146,6 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.025
elif candidate == CAR.CRV_5G:
- stop_and_go = True
ret.mass = 3410. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.66
ret.centerToFront = ret.wheelbase * 0.41
@@ -167,7 +163,6 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.025
elif candidate == CAR.CRV_HYBRID:
- stop_and_go = True
ret.mass = 1667. + STD_CARGO_KG # mean of 4 models in kg
ret.wheelbase = 2.66
ret.centerToFront = ret.wheelbase * 0.41
@@ -178,7 +173,6 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.025
elif candidate == CAR.FIT:
- stop_and_go = False
ret.mass = 2644. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.53
ret.centerToFront = ret.wheelbase * 0.39
@@ -188,7 +182,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
elif candidate == CAR.FREED:
- stop_and_go = False
ret.mass = 3086. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.74
# the remaining parameters were copied from FIT
@@ -199,7 +192,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
elif candidate == CAR.HRV:
- stop_and_go = False
ret.mass = 3125 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.61
ret.centerToFront = ret.wheelbase * 0.41
@@ -210,17 +202,15 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.025
elif candidate == CAR.ACURA_RDX:
- stop_and_go = False
ret.mass = 3935. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.68
ret.centerToFront = ret.wheelbase * 0.38
ret.steerRatio = 15.0 # as spec
- ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.444
+ ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
elif candidate == CAR.ACURA_RDX_3G:
- stop_and_go = True
ret.mass = 4068. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.75
ret.centerToFront = ret.wheelbase * 0.41
@@ -229,28 +219,19 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
tire_stiffness_factor = 0.677
- elif candidate == CAR.ODYSSEY:
- stop_and_go = False
- ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG
+ elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
+ ret.mass = 1900. + STD_CARGO_KG
ret.wheelbase = 3.00
ret.centerToFront = ret.wheelbase * 0.41
ret.steerRatio = 14.35 # as spec
- ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
- tire_stiffness_factor = 0.82
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
-
- elif candidate == CAR.ODYSSEY_CHN:
- stop_and_go = False
- ret.mass = 1849.2 + STD_CARGO_KG # mean of 4 models in kg
- ret.wheelbase = 2.90
- ret.centerToFront = ret.wheelbase * 0.41 # from CAR.ODYSSEY
- ret.steerRatio = 14.35
- ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
+ if candidate == CAR.ODYSSEY_CHN:
+ ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
+ else:
+ ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
elif candidate in (CAR.PILOT, CAR.PASSPORT):
- stop_and_go = False
ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight
ret.wheelbase = 2.82
ret.centerToFront = ret.wheelbase * 0.428
@@ -260,7 +241,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.RIDGELINE:
- stop_and_go = False
ret.mass = 4515. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 3.18
ret.centerToFront = ret.wheelbase * 0.41
@@ -270,7 +250,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.INSIGHT:
- stop_and_go = True
ret.mass = 2987. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.7
ret.centerToFront = ret.wheelbase * 0.39
@@ -280,7 +259,6 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
elif candidate == CAR.HONDA_E:
- stop_and_go = True
ret.mass = 3338.8 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.5
ret.centerToFront = ret.wheelbase * 0.5
@@ -303,14 +281,14 @@ class CarInterface(CarInterfaceBase):
if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_BOSCH_LONG
+ if candidate in HONDA_BOSCH_RADARLESS:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_RADARLESS
+
# min speed to enable ACC. if car can do stop and go, then set enabling speed
# to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not
# conflict with PCM acc
- ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else 25.5 * CV.MPH_TO_MS
-
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+ stop_and_go = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor
+ ret.minEnableSpeed = -1. if stop_and_go else 25.5 * CV.MPH_TO_MS
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
@@ -318,70 +296,33 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor=tire_stiffness_factor)
ret.steerActuatorDelay = 0.1
- ret.steerRateCost = 0.5
ret.steerLimitTimer = 0.8
return ret
@staticmethod
def init(CP, logcan, sendcan):
- if CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl:
+ if CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl:
disable_ecu(logcan, sendcan, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03')
# returns a car.CarState
- def update(self, c, can_strings):
- # ******************* do can recv *******************
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
- if self.cp_body:
- self.cp_body.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_body)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid and (self.cp_body is None or self.cp_body.can_valid)
-
buttonEvents = []
if self.CS.cruise_buttons != self.CS.prev_cruise_buttons:
- be = car.CarState.ButtonEvent.new_message()
- be.type = ButtonType.unknown
- if self.CS.cruise_buttons != 0:
- be.pressed = True
- but = self.CS.cruise_buttons
- else:
- be.pressed = False
- but = self.CS.prev_cruise_buttons
- if but == CruiseButtons.RES_ACCEL:
- be.type = ButtonType.accelCruise
- elif but == CruiseButtons.DECEL_SET:
- be.type = ButtonType.decelCruise
- elif but == CruiseButtons.CANCEL:
- be.type = ButtonType.cancel
- elif but == CruiseButtons.MAIN:
- be.type = ButtonType.altButton3
- buttonEvents.append(be)
+ buttonEvents.append(create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT))
if self.CS.cruise_setting != self.CS.prev_cruise_setting:
- be = car.CarState.ButtonEvent.new_message()
- be.type = ButtonType.unknown
- if self.CS.cruise_setting != 0:
- be.pressed = True
- but = self.CS.cruise_setting
- else:
- be.pressed = False
- but = self.CS.prev_cruise_setting
- if but == 1:
- be.type = ButtonType.altButton1
- # TODO: more buttons?
- buttonEvents.append(be)
+ buttonEvents.append(create_button_event(self.CS.cruise_setting, self.CS.prev_cruise_setting, {1: ButtonType.altButton1}))
+
ret.buttonEvents = buttonEvents
# events
events = self.create_common_events(ret, pcm_enable=False)
if self.CS.brake_error:
events.add(EventName.brakeUnavailable)
- if self.CS.park_brake:
- events.add(EventName.parkBrake)
if self.CP.pcmCruise and ret.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed)
@@ -401,39 +342,11 @@ class CarInterface(CarInterfaceBase):
if self.CS.CP.minEnableSpeed > 0 and ret.vEgo < 0.001:
events.add(EventName.manualRestart)
- # handle button presses
- for b in ret.buttonEvents:
-
- # do enable on both accel and decel buttons
- if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed:
- if not self.CP.pcmCruise:
- events.add(EventName.buttonEnable)
-
- # do disable on button down
- if b.type == ButtonType.cancel and b.pressed:
- events.add(EventName.buttonCancel)
-
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
# pass in a car.CarControl
# to be called @ 100hz
def apply(self, c):
- hud_control = c.hudControl
- if hud_control.speedVisible:
- hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH
- else:
- hud_v_cruise = 255
-
- ret = self.CC.update(c.enabled, c.active, self.CS, self.frame,
- c.actuators,
- c.cruiseControl.cancel,
- hud_v_cruise,
- hud_control.lanesVisible,
- hud_show_car=hud_control.leadVisible,
- hud_alert=hud_control.visualAlert)
-
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py
index 50ea52faae..905c9f4b4f 100644
--- a/selfdrive/car/honda/values.py
+++ b/selfdrive/car/honda/values.py
@@ -1,13 +1,18 @@
-from enum import IntFlag
+from dataclasses import dataclass
+from enum import Enum, IntFlag
+from typing import Dict, List, Optional, Union
from cereal import car
+from common.conversions import Conversions as CV
from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = car.CarParams.Ecu
VisualAlert = car.CarControl.HUDControl.VisualAlert
-class CarControllerParams():
+class CarControllerParams:
# Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we
# perform the closed loop control, and might need some
# to apply some more braking if we're on a downhill slope.
@@ -22,6 +27,7 @@ class CarControllerParams():
NIDEC_MAX_ACCEL_V = [0.5, 2.4, 1.4, 0.6]
NIDEC_MAX_ACCEL_BP = [0.0, 4.0, 10., 20.]
+ NIDEC_GAS_MAX = 198 # 0xc6
NIDEC_BRAKE_MAX = 1024 // 4
BOSCH_ACCEL_MIN = -3.5 # m/s^2
@@ -51,6 +57,7 @@ class CruiseButtons:
CANCEL = 2
MAIN = 1
+
# See dbc files for info on values
VISUAL_HUD = {
VisualAlert.none: 0,
@@ -63,12 +70,14 @@ VISUAL_HUD = {
VisualAlert.speedTooHigh: 8
}
+
class CAR:
ACCORD = "HONDA ACCORD 2018"
ACCORDH = "HONDA ACCORD HYBRID 2018"
CIVIC = "HONDA CIVIC 2016"
CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019"
CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019"
+ CIVIC_2022 = "HONDA CIVIC 2022"
ACURA_ILX = "ACURA ILX 2016"
CRV = "HONDA CR-V 2016"
CRV_5G = "HONDA CR-V 2017"
@@ -87,6 +96,68 @@ class CAR:
INSIGHT = "HONDA INSIGHT 2019"
HONDA_E = "HONDA E 2020"
+
+class Footnote(Enum):
+ CIVIC_DIESEL = CarFootnote(
+ "2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.",
+ Column.FSR_STEERING)
+
+
+@dataclass
+class HondaCarInfo(CarInfo):
+ package: str = "Honda Sensing"
+
+ def init_make(self, CP: car.CarParams):
+ if CP.carFingerprint in HONDA_BOSCH:
+ self.harness = Harness.bosch_b if CP.carFingerprint in HONDA_BOSCH_RADARLESS else Harness.bosch_a
+ else:
+ self.harness = Harness.nidec
+
+
+CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
+ CAR.ACCORD: [
+ HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS),
+ HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS),
+ ],
+ CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
+ CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"),
+ CAR.CIVIC_BOSCH: [
+ HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS),
+ HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS),
+ ],
+ CAR.CIVIC_BOSCH_DIESEL: None, # same platform
+ CAR.CIVIC_2022: [
+ HondaCarInfo("Honda Civic 2022", "All"),
+ HondaCarInfo("Honda Civic Hatchback 2022", "All"),
+ ],
+ CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS),
+ CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring
+ CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"),
+ CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey
+ CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
+ CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
+ CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
+ CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS),
+}
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.UDS_VERSION_REQUEST],
+ [StdQueries.UDS_VERSION_RESPONSE],
+ ),
+ ],
+)
+
FW_VERSIONS = {
CAR.ACCORD: {
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
@@ -102,6 +173,7 @@ FW_VERSIONS = {
b'37805-6A0-A750\x00\x00',
b'37805-6A0-A840\x00\x00',
b'37805-6A0-A850\x00\x00',
+ b'37805-6A0-A930\x00\x00',
b'37805-6A0-AF30\x00\x00',
b'37805-6A0-AG30\x00\x00',
b'37805-6B2-C520\x00\x00',
@@ -171,7 +243,7 @@ FW_VERSIONS = {
b'39990-TVA-A340\x00\x00',
b'39990-TVA-X030\x00\x00',
b'39990-TVA-X040\x00\x00',
- b'39990-TVA,A150\x00\x00',
+ b'39990-TVA,A150\x00\x00', # modified firmware
b'39990-TVE-H130\x00\x00',
],
(Ecu.unknown, 0x18da3af1, None): [
@@ -193,6 +265,7 @@ FW_VERSIONS = {
b'78109-TVA-A030\x00\x00',
b'78109-TVA-A110\x00\x00',
b'78109-TVA-A120\x00\x00',
+ b'78109-TVA-A130\x00\x00',
b'78109-TVA-A210\x00\x00',
b'78109-TVA-A220\x00\x00',
b'78109-TVA-A230\x00\x00',
@@ -380,7 +453,7 @@ FW_VERSIONS = {
b'78109-TED-Q510\x00\x00',
b'78109-TEG-A310\x00\x00',
],
- (Ecu.fwdCamera, 0x18dab0f1, None): [
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TBA-A020\x00\x00',
b'36161-TBA-A030\x00\x00',
b'36161-TBA-A040\x00\x00',
@@ -898,7 +971,7 @@ FW_VERSIONS = {
b'77959-THR-A110\x00\x00',
b'77959-THR-X010\x00\x00',
],
- (Ecu.fwdCamera, 0x18dab0f1, None): [
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-THR-A020\x00\x00',
b'36161-THR-A030\x00\x00',
b'36161-THR-A110\x00\x00',
@@ -965,6 +1038,23 @@ FW_VERSIONS = {
b'54008-THR-A020\x00\x00',
],
},
+ CAR.ODYSSEY_CHN: {
+ (Ecu.eps, 0x18da30f1, None): [
+ b'39990-T6D-H220\x00\x00',
+ ],
+ (Ecu.gateway, 0x18daeff1, None): [
+ b'38897-T6A-J010\x00\x00',
+ ],
+ (Ecu.combinationMeter, 0x18da60f1, None): [
+ b'78109-T6A-F310\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
+ b'36161-T6A-P040\x00\x00',
+ ],
+ (Ecu.srs, 0x18da53f1, None): [
+ b'77959-T6A-P110\x00\x00',
+ ],
+ },
CAR.PILOT: {
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TG7-A520\x00\x00',
@@ -1002,7 +1092,7 @@ FW_VERSIONS = {
b'39990-TG7-A070\x00\x00',
b'39990-TGS-A230\x00\x00',
],
- (Ecu.fwdCamera, 0x18dab0f1, None): [
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TG7-A310\x00\x00',
b'36161-TG7-A520\x00\x00',
b'36161-TG7-A630\x00\x00',
@@ -1076,12 +1166,14 @@ FW_VERSIONS = {
CAR.PASSPORT: {
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
b'37805-RLV-B220\x00\x00',
+ b'37805-RLV-B210\x00\x00',
],
(Ecu.eps, 0x18da30f1, None): [
b'39990-TGS-A230\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TGS-A030\x00\x00',
+ b'36161-TGS-A130\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-TG7-A040\x00\x00',
@@ -1097,6 +1189,7 @@ FW_VERSIONS = {
],
(Ecu.combinationMeter, 0x18da60f1, None): [
b'78109-TGS-AT20\x00\x00',
+ b'78109-TGS-AX20\x00\x00',
],
(Ecu.vsa, 0x18da28f1, None): [
b'57114-TGS-A530\x00\x00',
@@ -1107,7 +1200,7 @@ FW_VERSIONS = {
b'57114-TX5-A220\x00\x00',
b'57114-TX4-A220\x00\x00',
],
- (Ecu.fwdCamera, 0x18dab0f1, None): [
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TX5-A030\x00\x00',
b'36161-TX4-A030\x00\x00',
],
@@ -1204,7 +1297,7 @@ FW_VERSIONS = {
b'39990-T6Z-A030\x00\x00',
b'39990-T6Z-A050\x00\x00',
],
- (Ecu.fwdCamera, 0x18dab0f1, None): [
+ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T6Z-A020\x00\x00',
b'36161-T6Z-A310\x00\x00',
b'36161-T6Z-A420\x00\x00',
@@ -1242,6 +1335,7 @@ FW_VERSIONS = {
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-TXM-A070\x00\x00',
+ b'36802-TXM-A080\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'36161-TXM-A050\x00\x00',
@@ -1280,6 +1374,7 @@ FW_VERSIONS = {
b'36161-T7A-A140\x00\x00',
b'36161-T7A-A240\x00\x00',
b'36161-T7A-C440\x00\x00',
+ b'36161-T7A-A040\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-T7A-A230\x00\x00',
@@ -1290,6 +1385,7 @@ FW_VERSIONS = {
b'78109-THX-A210\x00\x00',
b'78109-THX-A220\x00\x00',
b'78109-THX-C220\x00\x00',
+ b'78109-THW-A110\x00\x00',
],
},
CAR.ACURA_ILX: {
@@ -1336,6 +1432,48 @@ FW_VERSIONS = {
b'57114-TYF-E030\x00\x00'
],
},
+ CAR.CIVIC_2022: {
+ (Ecu.eps, 0x18DA30F1, None): [
+ b'39990-T39-A130\x00\x00',
+ b'39990-T43-J020\x00\x00',
+ ],
+ (Ecu.gateway, 0x18DAEFF1, None): [
+ b'38897-T20-A020\x00\x00',
+ b'38897-T20-A510\x00\x00',
+ b'38897-T21-A010\x00\x00',
+ b'38897-T20-A210\x00\x00',
+ b'38897-T20-A310\x00\x00',
+ ],
+ (Ecu.srs, 0x18DA53F1, None): [
+ b'77959-T20-A970\x00\x00',
+ b'77959-T47-A940\x00\x00',
+ b'77959-T47-A950\x00\x00',
+ ],
+ (Ecu.combinationMeter, 0x18DA60F1, None): [
+ b'78108-T21-A220\x00\x00',
+ b'78108-T21-A620\x00\x00',
+ b'78108-T23-A110\x00\x00',
+ b'78108-T21-A230\x00\x00',
+ b'78108-T22-A020\x00\x00',
+ ],
+ (Ecu.vsa, 0x18DA28F1, None): [
+ b'57114-T20-AB40\x00\x00',
+ b'57114-T43-JB30\x00\x00',
+ ],
+ (Ecu.transmission, 0x18da1ef1, None): [
+ b'28101-65D-A020\x00\x00',
+ b'28101-65D-A120\x00\x00',
+ b'28101-65H-A020\x00\x00',
+ b'28101-65H-A120\x00\x00',
+ ],
+ (Ecu.programmedFuelInjection, 0x18da10f1, None): [
+ b'37805-64L-A540\x00\x00',
+ b'37805-64S-A540\x00\x00',
+ b'37805-64S-A720\x00\x00',
+ b'37805-64A-A540\x00\x00',
+ b'37805-64A-A620\x00\x00',
+ ],
+ },
}
DBC = {
@@ -1361,6 +1499,7 @@ DBC = {
CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None),
CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None),
+ CAR.CIVIC_2022: dbc_dict('honda_civic_ex_2022_can_generated', None),
}
STEER_THRESHOLD = {
@@ -1373,5 +1512,6 @@ HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY}
HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN,
CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE}
HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G,
- CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E}
+ CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022}
HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G}
+HONDA_BOSCH_RADARLESS = {CAR.CIVIC_2022}
diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py
index f7c43bd6e3..3f128b1598 100644
--- a/selfdrive/car/hyundai/carcontroller.py
+++ b/selfdrive/car/hyundai/carcontroller.py
@@ -1,125 +1,192 @@
from cereal import car
+from common.conversions import Conversions as CV
+from common.numpy_fast import clip
from common.realtime import DT_CTRL
-from common.numpy_fast import clip, interp
-from selfdrive.config import Conversions as CV
-from selfdrive.car import apply_std_steer_torque_limits
-from selfdrive.car.hyundai.hyundaican import create_lkas11, create_clu11, create_lfahda_mfc, create_acc_commands, create_acc_opt, create_frt_radar_opt
-from selfdrive.car.hyundai.values import Buttons, CarControllerParams, CAR
from opendbc.can.packer import CANPacker
+from selfdrive.car import apply_std_steer_torque_limits
+from selfdrive.car.hyundai import hyundaicanfd, hyundaican
+from selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR
VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState
+# EPS faults if you apply torque while the steering angle is above 90 degrees for more than 1 second
+# All slightly below EPS thresholds to avoid fault
+MAX_ANGLE = 85
+MAX_ANGLE_FRAMES = 89
+MAX_ANGLE_CONSECUTIVE_FRAMES = 2
-def process_hud_alert(enabled, fingerprint, visual_alert, left_lane,
- right_lane, left_lane_depart, right_lane_depart):
- sys_warning = (visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw))
+
+def process_hud_alert(enabled, fingerprint, hud_control):
+ sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw))
# initialize to no line visible
+ # TODO: this is not accurate for all cars
sys_state = 1
- if left_lane and right_lane or sys_warning: # HUD alert only display when LKAS status is active
+ if hud_control.leftLaneVisible and hud_control.rightLaneVisible or sys_warning: # HUD alert only display when LKAS status is active
sys_state = 3 if enabled or sys_warning else 4
- elif left_lane:
+ elif hud_control.leftLaneVisible:
sys_state = 5
- elif right_lane:
+ elif hud_control.rightLaneVisible:
sys_state = 6
# initialize to no warnings
left_lane_warning = 0
right_lane_warning = 0
- if left_lane_depart:
+ if hud_control.leftLaneDepart:
left_lane_warning = 1 if fingerprint in (CAR.GENESIS_G90, CAR.GENESIS_G80) else 2
- if right_lane_depart:
+ if hud_control.rightLaneDepart:
right_lane_warning = 1 if fingerprint in (CAR.GENESIS_G90, CAR.GENESIS_G80) else 2
return sys_warning, sys_state, left_lane_warning, right_lane_warning
-class CarController():
+class CarController:
def __init__(self, dbc_name, CP, VM):
- self.p = CarControllerParams(CP)
+ self.CP = CP
+ self.params = CarControllerParams(CP)
self.packer = CANPacker(dbc_name)
+ self.angle_limit_counter = 0
+ self.frame = 0
+ self.accel_last = 0
self.apply_steer_last = 0
self.car_fingerprint = CP.carFingerprint
- self.steer_rate_limited = False
- self.last_resume_frame = 0
- self.accel = 0
+ self.last_button_frame = 0
- def update(self, c, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, hud_speed,
- left_lane, right_lane, left_lane_depart, right_lane_depart):
- # Steering Torque
- new_steer = int(round(actuators.steer * self.p.STEER_MAX))
- apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p)
- self.steer_rate_limited = new_steer != apply_steer
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
- # disable when temp fault is active, or below LKA minimum speed
- lkas_active = c.active and not CS.out.steerWarning and CS.out.vEgo >= CS.CP.minSteerSpeed
+ # steering torque
+ steer = actuators.steer
+ new_steer = int(round(steer * self.params.STEER_MAX))
+ apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params)
- if not lkas_active:
+ if not CC.latActive:
apply_steer = 0
self.apply_steer_last = apply_steer
- sys_warning, sys_state, left_lane_warning, right_lane_warning = \
- process_hud_alert(enabled, self.car_fingerprint, visual_alert,
- left_lane, right_lane, left_lane_depart, right_lane_depart)
+ # accel + longitudinal
+ accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
+ stopping = actuators.longControlState == LongCtrlState.stopping
+ set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH)
+
+ # HUD messages
+ sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
+ hud_control)
can_sends = []
- # tester present - w/ no response (keeps radar disabled)
- if CS.CP.openpilotLongitudinalControl:
- if (frame % 100) == 0:
- can_sends.append([0x7D0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0])
-
- can_sends.append(create_lkas11(self.packer, frame, self.car_fingerprint, apply_steer, lkas_active,
- CS.lkas11, sys_warning, sys_state, enabled,
- left_lane, right_lane,
- left_lane_warning, right_lane_warning))
-
- if not CS.CP.openpilotLongitudinalControl:
- if pcm_cancel_cmd:
- can_sends.append(create_clu11(self.packer, frame, CS.clu11, Buttons.CANCEL))
- elif CS.out.cruiseState.standstill:
- # send resume at a max freq of 10Hz
- if (frame - self.last_resume_frame) * DT_CTRL > 0.1:
- # send 25 messages at a time to increases the likelihood of resume being accepted
- can_sends.extend([create_clu11(self.packer, frame, CS.clu11, Buttons.RES_ACCEL)] * 25)
- self.last_resume_frame = frame
-
- if frame % 2 == 0 and CS.CP.openpilotLongitudinalControl:
- lead_visible = False
- accel = actuators.accel if c.active else 0
-
- jerk = clip(2.0 * (accel - CS.out.aEgo), -12.7, 12.7)
-
- if accel < 0:
- accel = interp(accel - CS.out.aEgo, [-1.0, -0.5], [2 * accel, accel])
-
- accel = clip(accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
-
- stopping = (actuators.longControlState == LongCtrlState.stopping)
- set_speed_in_units = hud_speed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH)
- can_sends.extend(create_acc_commands(self.packer, enabled, accel, jerk, int(frame / 2), lead_visible, set_speed_in_units, stopping))
- self.accel = accel
-
- # 20 Hz LFA MFA message
- if frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021,
- CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV,
- CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022,
- CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022):
- can_sends.append(create_lfahda_mfc(self.packer, enabled))
-
- # 5 Hz ACC options
- if frame % 20 == 0 and CS.CP.openpilotLongitudinalControl:
- can_sends.extend(create_acc_opt(self.packer))
-
- # 2 Hz front radar options
- if frame % 50 == 0 and CS.CP.openpilotLongitudinalControl:
- can_sends.append(create_frt_radar_opt(self.packer))
+ # *** common hyundai stuff ***
+
+ # tester present - w/ no response (keeps relevant ECU disabled)
+ if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and self.CP.openpilotLongitudinalControl:
+ addr, bus = 0x7d0, 0
+ if self.CP.flags & HyundaiFlags.CANFD_HDA2.value:
+ addr, bus = 0x730, 5
+ can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus])
+
+ # >90 degree steering fault prevention
+ # Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault
+ if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE:
+ self.angle_limit_counter += 1
+ else:
+ self.angle_limit_counter = 0
+
+ # Cut steer actuation bit for two frames and hold torque with induced temporary fault
+ torque_fault = CC.latActive and self.angle_limit_counter > MAX_ANGLE_FRAMES
+ lat_active = CC.latActive and not torque_fault
+
+ if self.angle_limit_counter >= MAX_ANGLE_FRAMES + MAX_ANGLE_CONSECUTIVE_FRAMES:
+ self.angle_limit_counter = 0
+
+ # CAN-FD platforms
+ if self.CP.carFingerprint in CANFD_CAR:
+ hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2
+ hda2_long = hda2 and self.CP.openpilotLongitudinalControl
+
+ # steering control
+ can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, CC.enabled, lat_active, apply_steer))
+
+ # disable LFA on HDA2
+ if self.frame % 5 == 0 and hda2:
+ can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, CS.cam_0x2a4))
+
+ # LFA and HDA icons
+ if self.frame % 5 == 0 and (not hda2 or hda2_long):
+ can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled))
+
+ if self.CP.openpilotLongitudinalControl:
+ if hda2:
+ can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame))
+ if self.frame % 2 == 0:
+ can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
+ set_speed_in_units))
+ self.accel_last = accel
+ else:
+ # button presses
+ if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
+ # cruise cancel
+ if CC.cruiseControl.cancel:
+ if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
+ can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, CS.cruise_info))
+ self.last_button_frame = self.frame
+ else:
+ for _ in range(20):
+ can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.CANCEL))
+ self.last_button_frame = self.frame
+
+ # cruise standstill resume
+ elif CC.cruiseControl.resume:
+ if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
+ # TODO: resume for alt button cars
+ pass
+ else:
+ for _ in range(20):
+ can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.RES_ACCEL))
+ self.last_button_frame = self.frame
+ else:
+ can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active,
+ torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
+ hud_control.leftLaneVisible, hud_control.rightLaneVisible,
+ left_lane_warning, right_lane_warning))
+
+ if not self.CP.openpilotLongitudinalControl:
+ if CC.cruiseControl.cancel:
+ can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP.carFingerprint))
+ elif CC.cruiseControl.resume:
+ # send resume at a max freq of 10Hz
+ if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
+ # send 25 messages at a time to increases the likelihood of resume being accepted
+ can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25)
+ self.last_button_frame = self.frame
+
+ if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl:
+ # TODO: unclear if this is needed
+ jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
+ can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
+ hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override))
+
+ # 20 Hz LFA MFA message
+ if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021,
+ CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022,
+ CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022,
+ CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
+ can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled))
+
+ # 5 Hz ACC options
+ if self.frame % 20 == 0 and self.CP.openpilotLongitudinalControl:
+ can_sends.extend(hyundaican.create_acc_opt(self.packer))
+
+ # 2 Hz front radar options
+ if self.frame % 50 == 0 and self.CP.openpilotLongitudinalControl:
+ can_sends.append(hyundaican.create_frt_radar_opt(self.packer))
new_actuators = actuators.copy()
- new_actuators.steer = apply_steer / self.p.STEER_MAX
- new_actuators.accel = self.accel
+ new_actuators.steer = apply_steer / self.params.STEER_MAX
+ new_actuators.accel = accel
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py
index bdd49e2067..bd6b72f461 100644
--- a/selfdrive/car/hyundai/carstate.py
+++ b/selfdrive/car/hyundai/carstate.py
@@ -1,10 +1,16 @@
+from collections import deque
import copy
+import math
+
from cereal import car
-from selfdrive.car.hyundai.values import DBC, STEER_THRESHOLD, FEATURES, EV_CAR, HYBRID_CAR
-from selfdrive.car.interfaces import CarStateBase
+from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
from opendbc.can.can_define import CANDefine
-from selfdrive.config import Conversions as CV
+from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams
+from selfdrive.car.interfaces import CarStateBase
+
+PREV_BUTTON_SAMPLES = 8
+CLUSTER_SAMPLE_RATE = 20 # frames
class CarState(CarStateBase):
@@ -12,16 +18,39 @@ class CarState(CarStateBase):
super().__init__(CP)
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
- if self.CP.carFingerprint in FEATURES["use_cluster_gears"]:
+ self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
+ self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
+
+ self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
+ if CP.carFingerprint in CANFD_CAR:
+ self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"]
+ elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]:
self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"]
elif self.CP.carFingerprint in FEATURES["use_tcu_gears"]:
self.shifter_values = can_define.dv["TCU12"]["CUR_GR"]
else: # preferred and elect gear methods use same definition
self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"]
+ self.is_metric = False
+ self.brake_error = False
+ self.buttons_counter = 0
+
+ self.cruise_info = {}
+
+ # On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
+ self.cluster_speed = 0
+ self.cluster_speed_counter = CLUSTER_SAMPLE_RATE
+
+ self.params = CarControllerParams(CP)
def update(self, cp, cp_cam):
+ if self.CP.carFingerprint in CANFD_CAR:
+ return self.update_canfd(cp, cp_cam)
+
ret = car.CarState.new_message()
+ cp_cruise = cp_cam if self.CP.carFingerprint in CAMERA_SCC_CAR else cp
+ self.is_metric = cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] == 0
+ speed_conv = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
ret.doorOpen = any([cp.vl["CGW1"]["CF_Gway_DrvDrSw"], cp.vl["CGW1"]["CF_Gway_AstDrSw"],
cp.vl["CGW2"]["CF_Gway_RLDrSw"], cp.vl["CGW2"]["CF_Gway_RRDrSw"]])
@@ -36,9 +65,21 @@ class CarState(CarStateBase):
)
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
-
ret.standstill = ret.vEgoRaw < 0.1
+ self.cluster_speed_counter += 1
+ if self.cluster_speed_counter > CLUSTER_SAMPLE_RATE:
+ self.cluster_speed = cp.vl["CLU15"]["CF_Clu_VehicleSpeed"]
+ self.cluster_speed_counter = 0
+
+ # Mimic how dash converts to imperial.
+ # Sorento is the only platform where CF_Clu_VehicleSpeed is already imperial when not is_metric
+ # TODO: CGW_USM1->CF_Gway_DrLockSoundRValue may describe this
+ if not self.is_metric and self.CP.carFingerprint not in (CAR.KIA_SORENTO,):
+ self.cluster_speed = math.floor(self.cluster_speed * CV.KPH_TO_MPH + CV.KPH_TO_MPH)
+
+ ret.vEgoCluster = self.cluster_speed * speed_conv
+
ret.steeringAngleDeg = cp.vl["SAS11"]["SAS_Angle"]
ret.steeringRateDeg = cp.vl["SAS11"]["SAS_Speed"]
ret.yawRate = cp.vl["ESP12"]["YAW_RATE"]
@@ -46,8 +87,8 @@ class CarState(CarStateBase):
50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"])
ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"]
ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"]
- ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
- ret.steerWarning = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
+ ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
+ ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
# cruise state
if self.CP.openpilotLongitudinalControl:
@@ -56,20 +97,16 @@ class CarState(CarStateBase):
ret.cruiseState.enabled = cp.vl["TCS13"]["ACC_REQ"] == 1
ret.cruiseState.standstill = False
else:
- ret.cruiseState.available = cp.vl["SCC11"]["MainMode_ACC"] == 1
- ret.cruiseState.enabled = cp.vl["SCC12"]["ACCMode"] != 0
- ret.cruiseState.standstill = cp.vl["SCC11"]["SCCInfoDisplay"] == 4.
-
- if ret.cruiseState.enabled:
- speed_conv = CV.MPH_TO_MS if cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] else CV.KPH_TO_MS
- ret.cruiseState.speed = cp.vl["SCC11"]["VSetDis"] * speed_conv
- else:
- ret.cruiseState.speed = 0
+ ret.cruiseState.available = cp_cruise.vl["SCC11"]["MainMode_ACC"] == 1
+ ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0
+ ret.cruiseState.standstill = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 4.
+ ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv
# TODO: Find brake pressure
ret.brake = 0
ret.brakePressed = cp.vl["TCS13"]["DriverBraking"] != 0
ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY
+ ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1
if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR):
if self.CP.carFingerprint in HYBRID_CAR:
@@ -95,12 +132,12 @@ class CarState(CarStateBase):
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
if not self.CP.openpilotLongitudinalControl:
- if self.CP.carFingerprint in FEATURES["use_fca"]:
- ret.stockAeb = cp.vl["FCA11"]["FCA_CmdAct"] != 0
- ret.stockFcw = cp.vl["FCA11"]["CF_VSM_Warn"] == 2
- else:
- ret.stockAeb = cp.vl["SCC12"]["AEB_CmdAct"] != 0
- ret.stockFcw = cp.vl["SCC12"]["CF_VSM_Warn"] == 2
+ aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12"
+ aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct"
+ aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
+ aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0
+ ret.stockFcw = aeb_warning and not aeb_braking
+ ret.stockAeb = aeb_warning and aeb_braking
if self.CP.enableBsm:
ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0
@@ -109,18 +146,87 @@ class CarState(CarStateBase):
# save the entire LKAS11 and CLU11
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
self.clu11 = copy.copy(cp.vl["CLU11"])
- self.park_brake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1
self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
- self.brake_error = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
- self.prev_cruise_buttons = self.cruise_buttons
- self.cruise_buttons = cp.vl["CLU11"]["CF_Clu_CruiseSwState"]
+ self.brake_error = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
+ self.prev_cruise_buttons = self.cruise_buttons[-1]
+ self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
+ self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
+
+ return ret
+
+ def update_canfd(self, cp, cp_cam):
+ ret = car.CarState.new_message()
+
+ if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR):
+ if self.CP.carFingerprint in EV_CAR:
+ ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255.
+ else:
+ ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023.
+ ret.gasPressed = ret.gas > 1e-5
+ else:
+ ret.gasPressed = bool(cp.vl["ACCELERATOR_BRAKE_ALT"]["ACCELERATOR_PEDAL_PRESSED"])
+
+ ret.brakePressed = cp.vl["TCS"]["DriverBraking"] == 1
+
+ ret.doorOpen = cp.vl["DOORS_SEATBELTS"]["DRIVER_DOOR_OPEN"] == 1
+ ret.seatbeltUnlatched = cp.vl["DOORS_SEATBELTS"]["DRIVER_SEATBELT_LATCHED"] == 0
+
+ gear = cp.vl[self.gear_msg_canfd]["GEAR"]
+ ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
+
+ # TODO: figure out positions
+ ret.wheelSpeeds = self.get_wheel_speeds(
+ cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_1"],
+ cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_2"],
+ cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_3"],
+ cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_4"],
+ )
+ ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.standstill = ret.vEgoRaw < 0.1
+
+ ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEERING_RATE"]
+ ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1
+ ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"]
+ ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
+ ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
+ ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0
+
+ ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"],
+ cp.vl["BLINKERS"]["RIGHT_LAMP"])
+ if self.CP.enableBsm:
+ ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0
+ ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0
+
+ ret.cruiseState.available = True
+ cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
+ distance_unit_msg = cruise_btn_msg if self.CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN else "CLUSTER_INFO"
+ self.is_metric = cp.vl[distance_unit_msg]["DISTANCE_UNIT"] != 1
+ if not self.CP.openpilotLongitudinalControl:
+ speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
+ cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
+ ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
+ ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
+ ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
+ self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"])
+
+ self.prev_cruise_buttons = self.cruise_buttons[-1]
+ self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"])
+ self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"])
+ self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"]
+
+ if self.CP.flags & HyundaiFlags.CANFD_HDA2:
+ self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"])
return ret
@staticmethod
def get_can_parser(CP):
+ if CP.carFingerprint in CANFD_CAR:
+ return CarState.get_can_parser_canfd(CP)
+
signals = [
- # sig_name, sig_address
+ # signal_name, signal_address
("WHL_SPD_FL", "WHL_SPD11"),
("WHL_SPD_FR", "WHL_SPD11"),
("WHL_SPD_RL", "WHL_SPD11"),
@@ -132,9 +238,9 @@ class CarState(CarStateBase):
("CF_Gway_DrvSeatBeltSw", "CGW1"),
("CF_Gway_DrvDrSw", "CGW1"), # Driver Door
- ("CF_Gway_AstDrSw", "CGW1"), # Passenger door
- ("CF_Gway_RLDrSw", "CGW2"), # Rear reft door
- ("CF_Gway_RRDrSw", "CGW2"), # Rear right door
+ ("CF_Gway_AstDrSw", "CGW1"), # Passenger Door
+ ("CF_Gway_RLDrSw", "CGW2"), # Rear left Door
+ ("CF_Gway_RRDrSw", "CGW2"), # Rear right Door
("CF_Gway_TurnSigLh", "CGW1"),
("CF_Gway_TurnSigRh", "CGW1"),
("CF_Gway_ParkBrakeSw", "CGW1"),
@@ -154,6 +260,8 @@ class CarState(CarStateBase):
("CF_Clu_AmpInfo", "CLU11"),
("CF_Clu_AliveCnt1", "CLU11"),
+ ("CF_Clu_VehicleSpeed", "CLU15"),
+
("ACCEnable", "TCS13"),
("ACC_REQ", "TCS13"),
("DriverBraking", "TCS13"),
@@ -172,13 +280,13 @@ class CarState(CarStateBase):
("SAS_Angle", "SAS11"),
("SAS_Speed", "SAS11"),
]
-
checks = [
# address, frequency
("MDPS12", 50),
("TCS13", 50),
("TCS15", 10),
("CLU11", 50),
+ ("CLU15", 5),
("ESP12", 100),
("CGW1", 10),
("CGW2", 5),
@@ -187,7 +295,7 @@ class CarState(CarStateBase):
("SAS11", 100),
]
- if not CP.openpilotLongitudinalControl:
+ if not CP.openpilotLongitudinalControl and CP.carFingerprint not in CAMERA_SCC_CAR:
signals += [
("MainMode_ACC", "SCC11"),
("VSetDis", "SCC11"),
@@ -195,7 +303,6 @@ class CarState(CarStateBase):
("ACC_ObjDist", "SCC11"),
("ACCMode", "SCC12"),
]
-
checks += [
("SCC11", 50),
("SCC12", 50),
@@ -205,12 +312,14 @@ class CarState(CarStateBase):
signals += [
("FCA_CmdAct", "FCA11"),
("CF_VSM_Warn", "FCA11"),
+ ("CF_VSM_DecCmdAct", "FCA11"),
]
checks.append(("FCA11", 50))
else:
signals += [
("AEB_CmdAct", "SCC12"),
("CF_VSM_Warn", "SCC12"),
+ ("CF_VSM_DecCmdAct", "SCC12"),
]
if CP.enableBsm:
@@ -238,7 +347,6 @@ class CarState(CarStateBase):
if CP.carFingerprint in FEATURES["use_cluster_gears"]:
signals.append(("CF_Clu_Gear", "CLU15"))
- checks.append(("CLU15", 5))
elif CP.carFingerprint in FEATURES["use_tcu_gears"]:
signals.append(("CUR_GR", "TCU12"))
checks.append(("TCU12", 100))
@@ -253,8 +361,11 @@ class CarState(CarStateBase):
@staticmethod
def get_cam_can_parser(CP):
+ if CP.carFingerprint in CANFD_CAR:
+ return CarState.get_cam_can_parser_canfd(CP)
+
signals = [
- # sig_name, sig_address
+ # signal_name, signal_address
("CF_Lkas_LdwsActivemode", "LKAS11"),
("CF_Lkas_LdwsSysState", "LKAS11"),
("CF_Lkas_SysWarning", "LKAS11"),
@@ -271,9 +382,155 @@ class CarState(CarStateBase):
("CF_Lkas_FcwOpt_USM", "LKAS11"),
("CF_Lkas_LdwsOpt_USM", "LKAS11"),
]
-
checks = [
("LKAS11", 100)
]
+ if not CP.openpilotLongitudinalControl and CP.carFingerprint in CAMERA_SCC_CAR:
+ signals += [
+ ("MainMode_ACC", "SCC11"),
+ ("VSetDis", "SCC11"),
+ ("SCCInfoDisplay", "SCC11"),
+ ("ACC_ObjDist", "SCC11"),
+ ("ACCMode", "SCC12"),
+ ]
+ checks += [
+ ("SCC11", 50),
+ ("SCC12", 50),
+ ]
+
+ if CP.carFingerprint in FEATURES["use_fca"]:
+ signals += [
+ ("FCA_CmdAct", "FCA11"),
+ ("CF_VSM_Warn", "FCA11"),
+ ("CF_VSM_DecCmdAct", "FCA11"),
+ ]
+ checks.append(("FCA11", 50))
+ else:
+ signals += [
+ ("AEB_CmdAct", "SCC12"),
+ ("CF_VSM_Warn", "SCC12"),
+ ("CF_VSM_DecCmdAct", "SCC12"),
+ ]
+
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
+
+ @staticmethod
+ def get_can_parser_canfd(CP):
+
+ cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
+ gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
+ signals = [
+ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"),
+ ("WHEEL_SPEED_2", "WHEEL_SPEEDS"),
+ ("WHEEL_SPEED_3", "WHEEL_SPEEDS"),
+ ("WHEEL_SPEED_4", "WHEEL_SPEEDS"),
+
+ ("GEAR", gear_msg),
+
+ ("STEERING_RATE", "STEERING_SENSORS"),
+ ("STEERING_ANGLE", "STEERING_SENSORS"),
+ ("STEERING_COL_TORQUE", "MDPS"),
+ ("STEERING_OUT_TORQUE", "MDPS"),
+ ("LKA_FAULT", "MDPS"),
+
+ ("DriverBraking", "TCS"),
+
+ ("COUNTER", cruise_btn_msg),
+ ("CRUISE_BUTTONS", cruise_btn_msg),
+ ("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg),
+
+ ("DISTANCE_UNIT", "CLUSTER_INFO"),
+
+ ("LEFT_LAMP", "BLINKERS"),
+ ("RIGHT_LAMP", "BLINKERS"),
+
+ ("DRIVER_DOOR_OPEN", "DOORS_SEATBELTS"),
+ ("DRIVER_SEATBELT_LATCHED", "DOORS_SEATBELTS"),
+ ]
+
+ if CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN:
+ signals.append(("DISTANCE_UNIT", cruise_btn_msg))
+
+ checks = [
+ ("WHEEL_SPEEDS", 100),
+ (gear_msg, 100),
+ ("STEERING_SENSORS", 100),
+ ("MDPS", 100),
+ ("TCS", 50),
+ (cruise_btn_msg, 50),
+ ("CLUSTER_INFO", 4),
+ ("BLINKERS", 4),
+ ("DOORS_SEATBELTS", 4),
+ ]
+
+ if CP.enableBsm:
+ signals += [
+ ("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"),
+ ("FR_INDICATOR", "BLINDSPOTS_REAR_CORNERS"),
+ ]
+ checks += [
+ ("BLINDSPOTS_REAR_CORNERS", 20),
+ ]
+
+ if not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and not CP.openpilotLongitudinalControl:
+ signals += [
+ ("ACCMode", "SCC_CONTROL"),
+ ("VSetDis", "SCC_CONTROL"),
+ ("CRUISE_STANDSTILL", "SCC_CONTROL"),
+ ]
+ checks += [
+ ("SCC_CONTROL", 50),
+ ]
+
+ if CP.carFingerprint in EV_CAR:
+ signals += [
+ ("ACCELERATOR_PEDAL", "ACCELERATOR"),
+ ]
+ checks += [
+ ("ACCELERATOR", 100),
+ ]
+ elif CP.carFingerprint in HYBRID_CAR:
+ signals += [
+ ("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"),
+ ]
+ checks += [
+ ("ACCELERATOR_ALT", 100),
+ ]
+ else:
+ signals += [
+ ("ACCELERATOR_PEDAL_PRESSED", "ACCELERATOR_BRAKE_ALT"),
+ ]
+ checks += [
+ ("ACCELERATOR_BRAKE_ALT", 100),
+ ]
+
+ bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus)
+
+ @staticmethod
+ def get_cam_can_parser_canfd(CP):
+ signals = []
+ checks = []
+ if CP.flags & HyundaiFlags.CANFD_HDA2:
+ signals += [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)]
+ checks += [("CAM_0x2a4", 20)]
+ elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC:
+ signals += [
+ ("COUNTER", "SCC_CONTROL"),
+ ("NEW_SIGNAL_1", "SCC_CONTROL"),
+ ("MainMode_ACC", "SCC_CONTROL"),
+ ("ACCMode", "SCC_CONTROL"),
+ ("CRUISE_INACTIVE", "SCC_CONTROL"),
+ ("ZEROS_9", "SCC_CONTROL"),
+ ("CRUISE_STANDSTILL", "SCC_CONTROL"),
+ ("ZEROS_5", "SCC_CONTROL"),
+ ("DISTANCE_SETTING", "SCC_CONTROL"),
+ ("VSetDis", "SCC_CONTROL"),
+ ]
+
+ checks += [
+ ("SCC_CONTROL", 50),
+ ]
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6)
diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py
index fd3fc78e88..c2ffffbf22 100644
--- a/selfdrive/car/hyundai/hyundaican.py
+++ b/selfdrive/car/hyundai/hyundaican.py
@@ -1,10 +1,10 @@
import crcmod
-from selfdrive.car.hyundai.values import CAR, CHECKSUM
+from selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
- lkas11, sys_warning, sys_state, enabled,
+ torque_fault, lkas11, sys_warning, sys_state, enabled,
left_lane, right_lane,
left_lane_depart, right_lane_depart):
values = lkas11
@@ -14,12 +14,14 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
values["CF_Lkas_LdwsRHWarning"] = right_lane_depart
values["CR_Lkas_StrToqReq"] = apply_steer
values["CF_Lkas_ActToi"] = steer_req
+ values["CF_Lkas_ToiFlt"] = torque_fault # seems to allow actuation on CR_Lkas_StrToqReq
values["CF_Lkas_MsgCount"] = frame % 0x10
if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE,
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
- CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.SANTA_FE_2022,
- CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
+ CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
+ CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
+ CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
values["CF_Lkas_LdwsOpt_USM"] = 2
@@ -37,12 +39,26 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
# Note: the warning is hidden while the blinkers are on
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
+ # Likely cars lacking the ability to show individual lane lines in the dash
+ elif car_fingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL):
+ # SysWarning 4 = keep hands on wheel + beep
+ values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
+
+ # SysState 0 = no icons
+ # SysState 1-2 = white car + lanes
+ # SysState 3 = green car + lanes, green steering wheel
+ # SysState 4 = green car + lanes
+ values["CF_Lkas_LdwsSysState"] = 3 if enabled else 1
+ values["CF_Lkas_LdwsOpt_USM"] = 2 # non-2 changes above SysState definition
+
+ # these have no effect
+ values["CF_Lkas_LdwsActivemode"] = 0
+ values["CF_Lkas_FcwOpt_USM"] = 0
+
elif car_fingerprint == CAR.HYUNDAI_GENESIS:
# This field is actually LdwsActivemode
# Genesis and Optima fault when forwarding while engaged
values["CF_Lkas_LdwsActivemode"] = 2
- elif car_fingerprint == CAR.KIA_OPTIMA:
- values["CF_Lkas_LdwsActivemode"] = 0
dat = packer.make_can_msg("LKAS11", 0, values)[2]
@@ -62,11 +78,13 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
return packer.make_can_msg("LKAS11", 0, values)
-def create_clu11(packer, frame, clu11, button):
+def create_clu11(packer, frame, clu11, button, car_fingerprint):
values = clu11
values["CF_Clu_CruiseSwState"] = button
values["CF_Clu_AliveCnt1"] = frame % 0x10
- return packer.make_can_msg("CLU11", 0, values)
+ # send buttons to camera on camera-scc based cars
+ bus = 2 if car_fingerprint in CAMERA_SCC_CAR else 0
+ return packer.make_can_msg("CLU11", bus, values)
def create_lfahda_mfc(packer, enabled, hda_set_speed=0):
@@ -78,7 +96,7 @@ def create_lfahda_mfc(packer, enabled, hda_set_speed=0):
}
return packer.make_can_msg("LFAHDA_MFC", 0, values)
-def create_acc_commands(packer, enabled, accel, jerk, idx, lead_visible, set_speed, stopping):
+def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override):
commands = []
scc11_values = {
@@ -86,19 +104,19 @@ def create_acc_commands(packer, enabled, accel, jerk, idx, lead_visible, set_spe
"TauGapSet": 4,
"VSetDis": set_speed if enabled else 0,
"AliveCounterACC": idx % 0x10,
- "ObjValid": 1 if lead_visible else 0,
- "ACC_ObjStatus": 1 if lead_visible else 0,
+ "ObjValid": 1, # close lead makes controls tighter
+ "ACC_ObjStatus": 1, # close lead makes controls tighter
"ACC_ObjLatPos": 0,
"ACC_ObjRelSpd": 0,
- "ACC_ObjDist": 0,
- }
+ "ACC_ObjDist": 1, # close lead makes controls tighter
+ }
commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
scc12_values = {
- "ACCMode": 1 if enabled else 0,
- "StopReq": 1 if enabled and stopping else 0,
- "aReqRaw": accel if enabled else 0,
- "aReqValue": accel if enabled else 0, # stock ramps up and down respecting jerk limit until it reaches aReqRaw
+ "ACCMode": 2 if enabled and long_override else 1 if enabled else 0,
+ "StopReq": 1 if stopping else 0,
+ "aReqRaw": accel,
+ "aReqValue": accel, # stock ramps up and down respecting jerk limit until it reaches aReqRaw
"CR_VSM_Alive": idx % 0xF,
}
scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[2]
@@ -109,25 +127,23 @@ def create_acc_commands(packer, enabled, accel, jerk, idx, lead_visible, set_spe
scc14_values = {
"ComfortBandUpper": 0.0, # stock usually is 0 but sometimes uses higher values
"ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values
- "JerkUpperLimit": max(jerk, 1.0) if (enabled and not stopping) else 0, # stock usually is 1.0 but sometimes uses higher values
- "JerkLowerLimit": max(-jerk, 1.0) if enabled else 0, # stock usually is 0.5 but sometimes uses higher values
- "ACCMode": 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
+ "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
+ "JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
+ "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
"ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
}
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
+ # note that some vehicles most likely have an alternate checksum/counter definition
+ # https://github.com/commaai/opendbc/commit/9ddcdb22c4929baf310295e832668e6e7fcfa602
fca11_values = {
- # seems to count 2,1,0,3,2,1,0,3,2,1,0,3,2,1,0,repeat...
- # (where first value is aligned to Supplemental_Counter == 0)
- # test: [(idx % 0xF, -((idx % 0xF) + 2) % 4) for idx in range(0x14)]
- "CR_FCA_Alive": ((-((idx % 0xF) + 2) % 4) << 2) + 1,
- "Supplemental_Counter": idx % 0xF,
+ "CR_FCA_Alive": idx % 0xF,
"PAINT1_Status": 1,
"FCA_DrvSetStatus": 1,
"FCA_Status": 1, # AEB disabled
}
fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2]
- fca11_values["CR_FCA_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in fca11_dat) % 0x10
+ fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7])
commands.append(packer.make_can_msg("FCA11", 0, fca11_values))
return commands
diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py
new file mode 100644
index 0000000000..8b53e7c378
--- /dev/null
+++ b/selfdrive/car/hyundai/hyundaicanfd.py
@@ -0,0 +1,150 @@
+from common.numpy_fast import clip
+from selfdrive.car.hyundai.values import HyundaiFlags
+
+
+def get_e_can_bus(CP):
+ # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars
+ # have a different harness than the HDA1 and non-HDA variants in order to split
+ # a different bus, since the steering is done by different ECUs.
+ return 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4
+
+
+def create_steering_messages(packer, CP, enabled, lat_active, apply_steer):
+
+ ret = []
+
+ values = {
+ "LKA_MODE": 2,
+ "LKA_ICON": 2 if enabled else 1,
+ "TORQUE_REQUEST": apply_steer,
+ "LKA_ASSIST": 0,
+ "STEER_REQ": 1 if lat_active else 0,
+ "STEER_MODE": 0,
+ "SET_ME_1": 0,
+ "NEW_SIGNAL_1": 0,
+ "NEW_SIGNAL_2": 0,
+ }
+
+ if CP.flags & HyundaiFlags.CANFD_HDA2:
+ if CP.openpilotLongitudinalControl:
+ ret.append(packer.make_can_msg("LFA", 5, values))
+ ret.append(packer.make_can_msg("LKAS", 4, values))
+ else:
+ ret.append(packer.make_can_msg("LFA", 4, values))
+
+ return ret
+
+def create_cam_0x2a4(packer, camera_values):
+ camera_values.update({
+ "BYTE7": 0,
+ })
+ return packer.make_can_msg("CAM_0x2a4", 4, camera_values)
+
+def create_buttons(packer, CP, cnt, btn):
+ values = {
+ "COUNTER": cnt,
+ "SET_ME_1": 1,
+ "CRUISE_BUTTONS": btn,
+ }
+
+ bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 6
+ return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
+
+def create_acc_cancel(packer, CP, cruise_info_copy):
+ values = cruise_info_copy
+ values.update({
+ "ACCMode": 4,
+ })
+ return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)
+
+def create_lfahda_cluster(packer, CP, enabled):
+ values = {
+ "HDA_ICON": 1 if enabled else 0,
+ "LFA_ICON": 2 if enabled else 0,
+ }
+ return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values)
+
+
+def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed):
+ jerk = 5
+ jn = jerk / 50
+ if not enabled or gas_override:
+ a_val, a_raw = 0, 0
+ else:
+ a_raw = accel
+ a_val = clip(accel, accel_last - jn, accel_last + jn)
+ if stopping:
+ a_raw = 0
+
+ values = {
+ "ACCMode": 0 if not enabled else (2 if gas_override else 1),
+ "MainMode_ACC": 1,
+ "StopReq": 1 if stopping else 0,
+ "aReqValue": a_val,
+ "aReqRaw": a_raw,
+ "VSetDis": set_speed,
+ "JerkLowerLimit": jerk if enabled else 1,
+
+ "ACC_ObjDist": 1,
+ "ObjValid": 0,
+ "OBJ_STATUS": 2,
+ "SET_ME_2": 0x4,
+ "SET_ME_3": 0x3,
+ "SET_ME_TMP_64": 0x64,
+ "NEW_SIGNAL_10": 4,
+ "DISTANCE_SETTING": 4,
+ }
+
+ return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)
+
+
+
+def create_adrv_messages(packer, frame):
+ # messages needed to car happy after disabling
+ # the ADAS Driving ECU to do longitudinal control
+
+ ret = []
+
+ values = {
+ }
+ ret.append(packer.make_can_msg("ADRV_0x51", 4, values))
+
+ if frame % 2 == 0:
+ values = {
+ 'AEB_SETTING': 0x1, # show AEB disabled icon
+ 'SET_ME_2': 0x2,
+ 'SET_ME_FF': 0xff,
+ 'SET_ME_FC': 0xfc,
+ 'SET_ME_9': 0x9,
+ }
+ ret.append(packer.make_can_msg("ADRV_0x160", 5, values))
+
+ if frame % 5 == 0:
+ values = {
+ 'SET_ME_1C': 0x1c,
+ 'SET_ME_FF': 0xff,
+ 'SET_ME_TMP_F': 0xf,
+ 'SET_ME_TMP_F_2': 0xf,
+ }
+ ret.append(packer.make_can_msg("ADRV_0x1ea", 5, values))
+
+ values = {
+ 'SET_ME_E1': 0xe1,
+ 'SET_ME_3A': 0x3a,
+ }
+ ret.append(packer.make_can_msg("ADRV_0x200", 5, values))
+
+ if frame % 20 == 0:
+ values = {
+ 'SET_ME_15': 0x15,
+ }
+ ret.append(packer.make_can_msg("ADRV_0x345", 5, values))
+
+ if frame % 100 == 0:
+ values = {
+ 'SET_ME_22': 0x22,
+ 'SET_ME_41': 0x41,
+ }
+ ret.append(packer.make_can_msg("ADRV_0x1da", 5, values))
+
+ return ret
diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py
index 379e6937ae..b9f6b8fc58 100644
--- a/selfdrive/car/hyundai/interface.py
+++ b/selfdrive/car/hyundai/interface.py
@@ -1,341 +1,311 @@
#!/usr/bin/env python3
from cereal import car
from panda import Panda
-from common.params import Params
-from selfdrive.config import Conversions as CV
-from selfdrive.car.hyundai.values import CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams
+from common.conversions import Conversions as CV
+from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons
from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
from selfdrive.car.disable_ecu import disable_ecu
+Ecu = car.CarParams.Ecu
ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
+ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL)
+BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: ButtonType.decelCruise,
+ Buttons.GAP_DIST: ButtonType.gapAdjustCruise, Buttons.CANCEL: ButtonType.cancel}
-class CarInterface(CarInterfaceBase):
- @staticmethod
- def get_pid_accel_limits(CP, current_speed, cruise_speed):
- return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX
+class CarInterface(CarInterfaceBase):
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # pylint: disable=dangerous-default-value
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
-
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "hyundai"
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)]
- ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1]
-
- # WARNING: disabling radar also disables AEB (and we show the same warning on the instrument cluster as if you manually disabled AEB)
- ret.openpilotLongitudinalControl = Params().get_bool("DisableRadar") and (candidate not in LEGACY_SAFETY_MODE_CAR)
-
- ret.pcmCruise = not ret.openpilotLongitudinalControl
+ ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
# These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is
- # added to selfdrive/test/test_routes, we can remove it from this list.
- ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30}
+ # added to selfdrive/car/tests/routes.py, we can remove it from this list.
+ ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, }
+
+ if candidate in CANFD_CAR:
+ # detect HDA2 with ADAS Driving ECU
+ if Ecu.adas in [fw.ecu for fw in car_fw]:
+ ret.flags |= HyundaiFlags.CANFD_HDA2.value
+ else:
+ # non-HDA2
+ if 0x1cf not in fingerprint[4]:
+ ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value
+ # ICE cars do not have 0x130; GEARS message on 0x40 instead
+ if 0x130 not in fingerprint[4]:
+ ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value
+ if candidate not in CANFD_RADAR_SCC_CAR:
+ ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value
ret.steerActuatorDelay = 0.1 # Default delay
- ret.steerRateCost = 0.5
ret.steerLimitTimer = 0.4
tire_stiffness_factor = 1.
-
- ret.stoppingControl = True
- ret.vEgoStopping = 1.0
-
- ret.longitudinalTuning.kpV = [0.1]
- ret.longitudinalTuning.kiV = [0.0]
- ret.stopAccel = 0.0
-
- ret.longitudinalActuatorDelayUpperBound = 1.0 # s
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.766
# Values from optimizer
ret.steerRatio = 16.55 # 13.8 is spec end-to-end
tire_stiffness_factor = 0.82
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]]
elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID):
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1513. + STD_CARGO_KG
ret.wheelbase = 2.84
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
tire_stiffness_factor = 0.65
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.SONATA_LF:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 4497. * CV.LB_TO_KG
ret.wheelbase = 2.804
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.PALISADE:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1999. + STD_CARGO_KG
ret.wheelbase = 2.90
ret.steerRatio = 15.6 * 1.15
tire_stiffness_factor = 0.63
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.05]]
- elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30):
- ret.lateralTuning.pid.kf = 0.00006
+ elif candidate == CAR.ELANTRA:
ret.mass = 1275. + STD_CARGO_KG
ret.wheelbase = 2.7
ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535
tire_stiffness_factor = 0.385 # stiffnessFactor settled on 1.0081302973865127
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.ELANTRA_2021:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = (2800. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72
ret.steerRatio = 12.9
tire_stiffness_factor = 0.65
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.ELANTRA_HEV_2021:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72
ret.steerRatio = 12.9
tire_stiffness_factor = 0.65
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.HYUNDAI_GENESIS:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01
ret.steerRatio = 16.5
- ret.lateralTuning.init('indi')
- ret.lateralTuning.indi.innerLoopGainBP = [0.]
- ret.lateralTuning.indi.innerLoopGainV = [3.5]
- ret.lateralTuning.indi.outerLoopGainBP = [0.]
- ret.lateralTuning.indi.outerLoopGainV = [2.0]
- ret.lateralTuning.indi.timeConstantBP = [0.]
- ret.lateralTuning.indi.timeConstantV = [1.4]
- ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
- ret.lateralTuning.indi.actuatorEffectivenessV = [2.3]
ret.minSteerSpeed = 60 * CV.KPH_TO_MS
- elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV):
- ret.lateralTuning.pid.kf = 0.00005
- ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425.}.get(candidate, 1275.) + STD_CARGO_KG
- ret.wheelbase = 2.7
- ret.steerRatio = 13.73 * 1.15 # Spec
+ elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022):
+ ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) + STD_CARGO_KG
+ ret.wheelbase = 2.6
+ ret.steerRatio = 13.42 # Spec
tire_stiffness_factor = 0.385
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022):
- ret.lateralTuning.pid.kf = 0.00006
ret.mass = 1490. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx
ret.wheelbase = 2.7
ret.steerRatio = 13.73 # Spec
tire_stiffness_factor = 0.385
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022):
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
+ elif candidate == CAR.IONIQ_PHEV_2019:
+ ret.mass = 1550. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/us/en/vehicles/2019-ioniq-plug-in-hybrid/compare-specs
+ ret.wheelbase = 2.7
+ ret.steerRatio = 13.73
+ ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.VELOSTER:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80
ret.steerRatio = 13.75 * 1.15
tire_stiffness_factor = 0.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
+ elif candidate == CAR.TUCSON:
+ ret.mass = 3520. * CV.LB_TO_KG
+ ret.wheelbase = 2.67
+ ret.steerRatio = 14.00 * 1.15
+ tire_stiffness_factor = 0.385
+ elif candidate in (CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN):
+ ret.mass = 1630. + STD_CARGO_KG # average
+ ret.wheelbase = 2.756
+ ret.steerRatio = 16.
+ tire_stiffness_factor = 0.385
+ elif candidate == CAR.SANTA_CRUZ_1ST_GEN:
+ ret.mass = 1870. + STD_CARGO_KG # weight from Limited trim - the only supported trim
+ ret.wheelbase = 3.000
+ ret.steerRatio = 14.2 # steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf
# Kia
elif candidate == CAR.KIA_SORENTO:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1985. + STD_CARGO_KG
ret.wheelbase = 2.78
ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
- elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021):
- ret.lateralTuning.pid.kf = 0.00006
+ elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021):
ret.mass = 1737. + STD_CARGO_KG
ret.wheelbase = 2.7
ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec
tire_stiffness_factor = 0.385
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
- if candidate == CAR.KIA_NIRO_HEV:
+ if candidate == CAR.KIA_NIRO_PHEV:
ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.KIA_SELTOS:
ret.mass = 1337. + STD_CARGO_KG
ret.wheelbase = 2.63
ret.steerRatio = 14.56
tire_stiffness_factor = 1
- ret.lateralTuning.init('indi')
- ret.lateralTuning.indi.innerLoopGainBP = [0.]
- ret.lateralTuning.indi.innerLoopGainV = [4.]
- ret.lateralTuning.indi.outerLoopGainBP = [0.]
- ret.lateralTuning.indi.outerLoopGainV = [3.]
- ret.lateralTuning.indi.timeConstantBP = [0.]
- ret.lateralTuning.indi.timeConstantV = [1.4]
- ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
- ret.lateralTuning.indi.actuatorEffectivenessV = [1.8]
- elif candidate in (CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_H):
- ret.lateralTuning.pid.kf = 0.00005
+ elif candidate == CAR.KIA_SPORTAGE_5TH_GEN:
+ ret.mass = 1700. + STD_CARGO_KG # weight from SX and above trims, average of FWD and AWD versions
+ ret.wheelbase = 2.756
+ ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications
+ elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H):
ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80
ret.steerRatio = 13.75
tire_stiffness_factor = 0.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
- elif candidate == CAR.KIA_STINGER:
- ret.lateralTuning.pid.kf = 0.00005
+ if candidate == CAR.KIA_OPTIMA_G4:
+ ret.minSteerSpeed = 32 * CV.MPH_TO_MS
+ elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022):
ret.mass = 1825. + STD_CARGO_KG
ret.wheelbase = 2.78
ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_FORTE:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80
ret.steerRatio = 13.75
tire_stiffness_factor = 0.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_CEED:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1450. + STD_CARGO_KG
ret.wheelbase = 2.65
ret.steerRatio = 13.75
tire_stiffness_factor = 0.5
- ret.lateralTuning.pid.kf = 0.00005
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_K5_2021:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3228. * CV.LB_TO_KG
ret.wheelbase = 2.85
ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims)
tire_stiffness_factor = 0.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
+ elif candidate == CAR.KIA_EV6:
+ ret.mass = 2055 + STD_CARGO_KG
+ ret.wheelbase = 2.9
+ ret.steerRatio = 16.
+ tire_stiffness_factor = 0.65
+ elif candidate == CAR.IONIQ_5:
+ ret.mass = 2012 + STD_CARGO_KG
+ ret.wheelbase = 3.0
+ ret.steerRatio = 16.
+ tire_stiffness_factor = 0.65
+ elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN:
+ ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only
+ ret.wheelbase = 2.756
+ ret.steerRatio = 13.6
+ elif candidate == CAR.KIA_SORENTO_PHEV_4TH_GEN:
+ ret.mass = 4095.8 * CV.LB_TO_KG + STD_CARGO_KG # weight from EX and above trims, average of FWD and AWD versions (EX, X-Line EX AWD, SX, SX Pestige, X-Line SX Prestige AWD)
+ ret.wheelbase = 2.81
+ ret.steerRatio = 13.27 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sorento-phev/2022/specifications
# Genesis
elif candidate == CAR.GENESIS_G70:
- ret.lateralTuning.init('indi')
- ret.lateralTuning.indi.innerLoopGainBP = [0.]
- ret.lateralTuning.indi.innerLoopGainV = [2.5]
- ret.lateralTuning.indi.outerLoopGainBP = [0.]
- ret.lateralTuning.indi.outerLoopGainV = [3.5]
- ret.lateralTuning.indi.timeConstantBP = [0.]
- ret.lateralTuning.indi.timeConstantV = [1.4]
- ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
- ret.lateralTuning.indi.actuatorEffectivenessV = [1.8]
ret.steerActuatorDelay = 0.1
ret.mass = 1640.0 + STD_CARGO_KG
ret.wheelbase = 2.84
ret.steerRatio = 13.56
elif candidate == CAR.GENESIS_G70_2020:
- ret.lateralTuning.pid.kf = 0.
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.112], [0.004]]
ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83
ret.steerRatio = 12.9
+ elif candidate == CAR.GENESIS_GV70_1ST_GEN:
+ ret.mass = 1950. + STD_CARGO_KG
+ ret.wheelbase = 2.87
+ ret.steerRatio = 14.6
elif candidate == CAR.GENESIS_G80:
- ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01
ret.steerRatio = 16.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]]
elif candidate == CAR.GENESIS_G90:
ret.mass = 2200
ret.wheelbase = 3.15
ret.steerRatio = 12.069
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]]
- # these cars require a special panda safety mode due to missing counters and checksums in the messages
- if candidate in LEGACY_SAFETY_MODE_CAR:
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)]
+ # *** longitudinal control ***
+ if candidate in CANFD_CAR:
+ ret.longitudinalTuning.kpV = [0.1]
+ ret.longitudinalTuning.kiV = [0.0]
+ ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in CANFD_RADAR_SCC_CAR
+ else:
+ ret.longitudinalTuning.kpV = [0.5]
+ ret.longitudinalTuning.kiV = [0.0]
+ ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR)
+ ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
+ ret.pcmCruise = not ret.openpilotLongitudinalControl
+
+ ret.stoppingControl = True
+ ret.startingState = True
+ ret.vEgoStarting = 0.1
+ ret.startAccel = 2.0
+ ret.longitudinalActuatorDelayLowerBound = 0.5
+ ret.longitudinalActuatorDelayUpperBound = 0.5
+
+ # *** feature detection ***
+ if candidate in CANFD_CAR:
+ bus = 5 if ret.flags & HyundaiFlags.CANFD_HDA2 else 4
+ ret.enableBsm = 0x1e5 in fingerprint[bus]
+ else:
+ ret.enableBsm = 0x58b in fingerprint[0]
+
+ # *** panda safety config ***
+ if candidate in CANFD_CAR:
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput),
+ get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)]
+
+ if ret.flags & HyundaiFlags.CANFD_HDA2:
+ ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
+ if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
+ ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS
+ if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC:
+ ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
+ else:
+ if candidate in LEGACY_SAFETY_MODE_CAR:
+ # these cars require a special panda safety mode due to missing counters and checksums in the messages
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)]
+ else:
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)]
+
+ if candidate in CAMERA_SCC_CAR:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
- # set appropriate safety param for gas signal
+ if ret.openpilotLongitudinalControl:
+ ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG
if candidate in HYBRID_CAR:
- ret.safetyConfigs[0].safetyParam = 2
+ ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_HYBRID_GAS
elif candidate in EV_CAR:
- ret.safetyConfigs[0].safetyParam = 1
+ ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS
- ret.centerToFront = ret.wheelbase * 0.4
+ if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022):
+ ret.flags |= HyundaiFlags.ALT_LIMITS.value
+ ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+ ret.centerToFront = ret.wheelbase * 0.4
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
tire_stiffness_factor=tire_stiffness_factor)
-
- ret.enableBsm = 0x58b in fingerprint[0]
-
- if ret.openpilotLongitudinalControl:
- ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LONG
-
return ret
@staticmethod
def init(CP, logcan, sendcan):
- if CP.openpilotLongitudinalControl:
- disable_ecu(logcan, sendcan, addr=0x7d0, com_cont_req=b'\x28\x83\x01')
-
- def update(self, c, can_strings):
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
+ if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value):
+ addr, bus = 0x7d0, 0
+ if CP.flags & HyundaiFlags.CANFD_HDA2.value:
+ addr, bus = 0x730, 5
+ disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01')
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
-
- events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise)
-
- if self.CS.brake_error:
- events.add(EventName.brakeUnavailable)
- if self.CS.park_brake:
- events.add(EventName.parkBrake)
- if self.CS.CP.openpilotLongitudinalControl:
- buttonEvents = []
+ if self.CS.CP.openpilotLongitudinalControl and self.CS.cruise_buttons[-1] != self.CS.prev_cruise_buttons:
+ buttonEvents = [create_button_event(self.CS.cruise_buttons[-1], self.CS.prev_cruise_buttons, BUTTONS_DICT)]
+ # Handle CF_Clu_CruiseSwState changing buttons mid-press
+ if self.CS.cruise_buttons[-1] != 0 and self.CS.prev_cruise_buttons != 0:
+ buttonEvents.append(create_button_event(0, self.CS.prev_cruise_buttons, BUTTONS_DICT))
- if self.CS.cruise_buttons != self.CS.prev_cruise_buttons:
- be = car.CarState.ButtonEvent.new_message()
- be.type = ButtonType.unknown
- if self.CS.cruise_buttons != 0:
- be.pressed = True
- but = self.CS.cruise_buttons
- else:
- be.pressed = False
- but = self.CS.prev_cruise_buttons
- if but == Buttons.RES_ACCEL:
- be.type = ButtonType.accelCruise
- elif but == Buttons.SET_DECEL:
- be.type = ButtonType.decelCruise
- elif but == Buttons.GAP_DIST:
- be.type = ButtonType.gapAdjustCruise
- elif but == Buttons.CANCEL:
- be.type = ButtonType.cancel
- buttonEvents.append(be)
+ ret.buttonEvents = buttonEvents
- ret.buttonEvents = buttonEvents
+ # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state
+ # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
+ # Main button also can trigger an engagement on these cars
+ allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons)
+ events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable)
- for b in ret.buttonEvents:
- # do enable on both accel and decel buttons
- if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed:
- events.add(EventName.buttonEnable)
- # do disable on button down
- if b.type == ButtonType.cancel and b.pressed:
- events.add(EventName.buttonCancel)
+ if self.CS.brake_error:
+ events.add(EventName.brakeUnavailable)
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
@@ -347,13 +317,7 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- hud_control = c.hudControl
- ret = self.CC.update(c, c.enabled, self.CS, self.frame, c.actuators,
- c.cruiseControl.cancel, hud_control.visualAlert, hud_control.setSpeed, hud_control.leftLaneVisible,
- hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py
new file mode 100755
index 0000000000..a52027f448
--- /dev/null
+++ b/selfdrive/car/hyundai/tests/test_hyundai.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+import unittest
+
+from cereal import car
+from selfdrive.car.car_helpers import get_interface_attr
+from selfdrive.car.fw_versions import FW_QUERY_CONFIGS
+from selfdrive.car.hyundai.values import CANFD_CAR
+
+Ecu = car.CarParams.Ecu
+
+ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
+VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True)
+
+
+class TestHyundaiFingerprint(unittest.TestCase):
+ def test_auxiliary_request_ecu_whitelist(self):
+ # Asserts only auxiliary Ecus can exist in database for CAN-FD cars
+ config = FW_QUERY_CONFIGS['hyundai']
+ whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus if r.bus > 3}
+
+ for car_model in CANFD_CAR:
+ ecus = {fw[0] for fw in VERSIONS['hyundai'][car_model].keys()}
+ ecus_not_in_whitelist = ecus - whitelisted_ecus
+ ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_in_whitelist])
+ self.assertEqual(len(ecus_not_in_whitelist), 0, f'{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}')
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py
index e26d17b241..1e9d0f7f62 100644
--- a/selfdrive/car/hyundai/values.py
+++ b/selfdrive/car/hyundai/values.py
@@ -1,41 +1,79 @@
+from dataclasses import dataclass
+from enum import Enum, IntFlag
+from typing import Dict, List, Optional, Union
+
from cereal import car
+from panda.python import uds
+from common.conversions import Conversions as CV
from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
+
Ecu = car.CarParams.Ecu
-# Steer torque limits
+
class CarControllerParams:
ACCEL_MIN = -3.5 # m/s
ACCEL_MAX = 2.0 # m/s
def __init__(self, CP):
- # To determine the limit for your car, find the maximum value that the stock LKAS will request.
- # If the max stock LKAS request is <384, add your car to this list.
- if CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ,
- CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV,
- CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA, CAR.KIA_SORENTO, CAR.KIA_STINGER):
- self.STEER_MAX = 255
- else:
- self.STEER_MAX = 384
self.STEER_DELTA_UP = 3
self.STEER_DELTA_DOWN = 7
self.STEER_DRIVER_ALLOWANCE = 50
self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_DRIVER_FACTOR = 1
+ self.STEER_THRESHOLD = 150
+
+ if CP.carFingerprint in CANFD_CAR:
+ self.STEER_MAX = 270
+ self.STEER_DRIVER_ALLOWANCE = 250
+ self.STEER_DRIVER_MULTIPLIER = 2
+ self.STEER_THRESHOLD = 250
+ self.STEER_DELTA_UP = 2
+ self.STEER_DELTA_DOWN = 3
+
+ # To determine the limit for your car, find the maximum value that the stock LKAS will request.
+ # If the max stock LKAS request is <384, add your car to this list.
+ elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.IONIQ,
+ CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV,
+ CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO):
+ self.STEER_MAX = 255
+
+ # these cars have significantly more torque than most HKG; limit to 70% of max
+ elif CP.flags & HyundaiFlags.ALT_LIMITS:
+ self.STEER_MAX = 270
+ self.STEER_DELTA_UP = 2
+ self.STEER_DELTA_DOWN = 3
+
+ # Default for most HKG
+ else:
+ self.STEER_MAX = 384
+
+
+class HyundaiFlags(IntFlag):
+ CANFD_HDA2 = 1
+ CANFD_ALT_BUTTONS = 2
+ CANFD_ALT_GEARS = 4
+ CANFD_CAMERA_SCC = 8
+
+ ALT_LIMITS = 16
+
class CAR:
# Hyundai
ELANTRA = "HYUNDAI ELANTRA 2017"
ELANTRA_2021 = "HYUNDAI ELANTRA 2021"
ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021"
- ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT"
HYUNDAI_GENESIS = "HYUNDAI GENESIS 2015-2016"
IONIQ = "HYUNDAI IONIQ HYBRID 2017-2019"
IONIQ_HEV_2022 = "HYUNDAI IONIQ HYBRID 2020-2022"
IONIQ_EV_LTD = "HYUNDAI IONIQ ELECTRIC LIMITED 2019"
IONIQ_EV_2020 = "HYUNDAI IONIQ ELECTRIC 2020"
+ IONIQ_PHEV_2019 = "HYUNDAI IONIQ PLUG-IN HYBRID 2019"
IONIQ_PHEV = "HYUNDAI IONIQ PHEV 2020"
KONA = "HYUNDAI KONA 2020"
KONA_EV = "HYUNDAI KONA ELECTRIC 2019"
+ KONA_EV_2022 = "HYUNDAI KONA ELECTRIC 2022"
KONA_HEV = "HYUNDAI KONA HYBRID 2020"
SANTA_FE = "HYUNDAI SANTA FE 2019"
SANTA_FE_2022 = "HYUNDAI SANTA FE 2022"
@@ -43,50 +81,159 @@ class CAR:
SANTA_FE_PHEV_2022 = "HYUNDAI SANTA FE PlUG-IN HYBRID 2022"
SONATA = "HYUNDAI SONATA 2020"
SONATA_LF = "HYUNDAI SONATA 2019"
+ TUCSON = "HYUNDAI TUCSON 2019"
PALISADE = "HYUNDAI PALISADE 2020"
VELOSTER = "HYUNDAI VELOSTER 2019"
SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021"
+ IONIQ_5 = "HYUNDAI IONIQ 5 2022"
+ TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN"
+ TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN"
+ SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN"
# Kia
KIA_FORTE = "KIA FORTE E 2018 & GT 2021"
KIA_K5_2021 = "KIA K5 2021"
KIA_NIRO_EV = "KIA NIRO EV 2020"
- KIA_NIRO_HEV = "KIA NIRO HYBRID 2019"
+ KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021"
- KIA_OPTIMA = "KIA OPTIMA SX 2019 & 2016"
+ KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN"
+ KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT"
KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019"
KIA_SELTOS = "KIA SELTOS 2021"
+ KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN"
KIA_SORENTO = "KIA SORENTO GT LINE 2018"
+ KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN"
+ KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN"
KIA_STINGER = "KIA STINGER GT2 2018"
+ KIA_STINGER_2022 = "KIA STINGER 2022"
KIA_CEED = "KIA CEED INTRO ED 2019"
+ KIA_EV6 = "KIA EV6 2022"
# Genesis
GENESIS_G70 = "GENESIS G70 2018"
GENESIS_G70_2020 = "GENESIS G70 2020"
+ GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN"
GENESIS_G80 = "GENESIS G80 2017"
GENESIS_G90 = "GENESIS G90 2017"
+class Footnote(Enum):
+ CANFD = CarFootnote(
+ "Requires a red panda, additional harness box, " +
+ "additional OBD-C cable, USB-A to USB-A cable, and a USB-A to USB-C OTG dongle.",
+ Column.MODEL, shop_footnote=True)
+
+
+@dataclass
+class HyundaiCarInfo(CarInfo):
+ package: str = "Smart Cruise Control (SCC)"
+
+ def init_make(self, CP: car.CarParams):
+ if CP.carFingerprint in CANFD_CAR:
+ self.footnotes.insert(0, Footnote.CANFD)
+
+
+CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
+ CAR.ELANTRA: [
+ HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b),
+ HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e),
+ HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e),
+ ],
+ CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
+ CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
+ CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages
+ CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c),
+ CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness
+ CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c),
+ CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", "All", harness=Harness.hyundai_h),
+ CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", harness=Harness.hyundai_c),
+ CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-21", "All", harness=Harness.hyundai_h),
+ CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", harness=Harness.hyundai_b),
+ CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", harness=Harness.hyundai_g),
+ CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", harness=Harness.hyundai_o),
+ CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", harness=Harness.hyundai_i), # TODO: check packages
+ CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", harness=Harness.hyundai_d),
+ CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", "https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l),
+ CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l),
+ CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l),
+ CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a),
+ CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e),
+ CAR.TUCSON: [
+ HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l),
+ HyundaiCarInfo("Hyundai Tucson Diesel 2019", harness=Harness.hyundai_l),
+ ],
+ CAR.PALISADE: [
+ HyundaiCarInfo("Hyundai Palisade 2020-22", "All", "https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h),
+ HyundaiCarInfo("Kia Telluride 2020-22", "All", harness=Harness.hyundai_h),
+ ],
+ CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e),
+ CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a),
+ CAR.IONIQ_5: [
+ HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", harness=Harness.hyundai_k),
+ HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q),
+ ],
+ CAR.TUCSON_4TH_GEN: [
+ HyundaiCarInfo("Hyundai Tucson 2022", harness=Harness.hyundai_n),
+ HyundaiCarInfo("Hyundai Tucson 2023", "All", harness=Harness.hyundai_n),
+ ],
+ CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n),
+ CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", harness=Harness.hyundai_n),
+
+ # Kia
+ CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g),
+ CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a),
+ CAR.KIA_NIRO_EV: [
+ HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
+ HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f),
+ HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c),
+ HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
+ ],
+ CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c),
+ CAR.KIA_NIRO_HEV_2021: [
+ HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify
+ HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h),
+ ],
+ CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018
+ CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g),
+ CAR.KIA_OPTIMA_H: [
+ HyundaiCarInfo("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control"), # TODO: may support adjacent years
+ HyundaiCarInfo("Kia Optima Hybrid 2019"),
+ ],
+ CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a),
+ CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", harness=Harness.hyundai_n),
+ CAR.KIA_SORENTO: [
+ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c),
+ HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e),
+ ],
+ CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a),
+ CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n),
+ CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c),
+ CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k),
+ CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e),
+ CAR.KIA_EV6: [
+ HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l),
+ HyundaiCarInfo("Kia EV6 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_p)
+ ],
+
+ # Genesis
+ CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f),
+ CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f),
+ CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
+ CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
+ CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
+}
+
class Buttons:
NONE = 0
RES_ACCEL = 1
SET_DECEL = 2
GAP_DIST = 3
- CANCEL = 4
+ CANCEL = 4 # on newer models, this is a pause/resume button
FINGERPRINTS = {
CAR.ELANTRA: [{
66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
}],
- CAR.ELANTRA_GT_I30: [{
- 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1193: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1952: 8, 1960: 8, 1988: 8, 2000: 8, 2001: 8, 2005: 8, 2008: 8, 2009: 8, 2013: 8, 2017: 8, 2025: 8
- },
- {
- 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1419: 8, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8
- },
- {
- 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1960: 8, 1990: 8, 1998: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
- }],
CAR.HYUNDAI_GENESIS: [{
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4
},
@@ -117,9 +264,6 @@ FINGERPRINTS = {
CAR.SONATA_LF: [
{66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8},
],
- CAR.KIA_OPTIMA: [{
- 64: 8, 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 558: 8, 593: 8, 608: 8, 640: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1186: 2, 1191: 2, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1268: 8, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1492: 8, 1530: 8, 1532: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 1996: 8, 2000: 8, 2001: 8, 2004: 8, 2008: 8, 2009: 8, 2012: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8, 1371: 8, 1397: 8, 1961: 8
- }],
CAR.KIA_SORENTO: [{
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1
}],
@@ -147,6 +291,9 @@ FINGERPRINTS = {
CAR.KONA_EV: [{
127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8
}],
+ CAR.KONA_EV_2022: [{
+ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1069: 8, 1078: 4, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1173: 8, 1183: 8, 1188: 8, 1191: 2, 1193: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1339: 8, 1342: 8, 1343: 8, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1446: 8, 1456: 4, 1470: 8, 1473: 8, 1485: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8
+ }],
CAR.KIA_NIRO_EV: [{
127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 516: 8, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1193: 8, 1225: 8, 1260: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8
}],
@@ -161,6 +308,47 @@ FINGERPRINTS = {
}],
}
+HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(0xf100) # Long description
+HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \
+ p16(0xf100)
+HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ # TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD
+ # CAN queries (OBD-II port)
+ Request(
+ [HYUNDAI_VERSION_REQUEST_LONG],
+ [HYUNDAI_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera],
+ ),
+ Request(
+ [HYUNDAI_VERSION_REQUEST_MULTI],
+ [HYUNDAI_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar],
+ ),
+ # CAN-FD queries (camera)
+ Request(
+ [HYUNDAI_VERSION_REQUEST_LONG],
+ [HYUNDAI_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar],
+ bus=4,
+ ),
+ Request(
+ [HYUNDAI_VERSION_REQUEST_LONG],
+ [HYUNDAI_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar],
+ bus=5,
+ ),
+ ],
+ extra_ecus=[
+ (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms
+ (Ecu.cornerRadar, 0x7b7, None),
+ ],
+)
FW_VERSIONS = {
CAR.IONIQ: {
@@ -180,25 +368,49 @@ FW_VERSIONS = {
b'\xf1\x816U3H1051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H1051\x00\x00HAE0G16US2\x00\x00\x00\x00',
],
},
+ CAR.IONIQ_PHEV_2019: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2100 ',
+ ],
+ (Ecu.eps, 0x7d4, None): [
+ b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2400 180222',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81',
+ b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\x00\x00\x00\x00',
+ ],
+ },
CAR.IONIQ_PHEV: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\000AEhe SCC FHCUP 1.00 1.02 99110-G2100 ',
b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ',
+ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\000AE MDPS C 1.00 1.01 56310/G2510 4APHC101',
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101',
+ b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\000AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819',
b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819',
+ b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2',
b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00',
+ b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt',
+ b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00',
],
},
CAR.IONIQ_EV_2020: {
@@ -225,6 +437,7 @@ FW_VERSIONS = {
b'\xf1\x00AE MDPS C 1.00 1.02 56310G7300\x00 4AEEC102',
b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104',
b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103',
+ b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418',
@@ -262,14 +475,14 @@ FW_VERSIONS = {
b'\xf1\x8799110L0000\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ',
b'\xf1\x8799110L0000\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ',
],
- (Ecu.esp, 0x7d1, None): [
- b'\xf1\x00DN ESC \a 106 \a\x01 58910-L0100',
+ (Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100',
b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300',
b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300',
b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100',
b'\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100',
b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300',
- b'\xf1\x8758910-L0100\xf1\x00DN ESC \a 106 \a\x01 58910-L0100',
+ b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100',
b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100',
b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100',
b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100',
@@ -283,6 +496,7 @@ FW_VERSIONS = {
b'\xf1\x82DNBWN5TMDCXXXG2E',
b'\xf1\x82DNCVN5GMCCXXXF0A',
b'\xf1\x82DNCVN5GMCCXXXG2B',
+ b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A',
b'\xf1\x87391162M003',
b'\xf1\x87391162M013',
@@ -290,10 +504,11 @@ FW_VERSIONS = {
b'HM6M1_0a0_F00',
b'HM6M1_0a0_G20',
b'HM6M2_0a0_BD0',
+ b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B',
],
(Ecu.eps, 0x7d4, None): [
- b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware
- b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware
+ b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware
+ b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware
b'\xf1\x00DN8 MDPS C 1.00 1.01 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4DNAC101',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101',
b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101',
@@ -306,6 +521,7 @@ FW_VERSIONS = {
b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101',
b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101',
b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100',
+ b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422',
@@ -326,6 +542,7 @@ FW_VERSIONS = {
b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92',
b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:',
b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
+ b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE',
b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
@@ -371,13 +588,17 @@ FW_VERSIONS = {
b'\xf1\x87SAMFBA7978674GJ2gw\x87xgw\x97ywwwwvUGeUUeU\x87O\xfb\xff\x98w\x8f\xfffF\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
+ b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
+ b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
+ b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc',
],
},
CAR.SONATA_LF: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00LF ESC \f 11 \x17\x01\x13 58920-C2610',
b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610',
],
@@ -388,6 +609,7 @@ FW_VERSIONS = {
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00LFF LKAS AT USA LHD 1.00 1.01 95740-C1000 E51',
+ b'\xf1\x00LFF LKAS AT USA LHD 1.01 1.02 95740-C1000 E52',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5',
@@ -396,6 +618,25 @@ FW_VERSIONS = {
b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8',
b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00',
b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm',
+ b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm',
+ ],
+ },
+ CAR.TUCSON: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00TL__ FCA F-CUP 1.00 1.01 99110-D3500 ',
+ b'\xf1\x00TL__ FCA F-CUP 1.00 1.02 99110-D3510 ',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002',
+ b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00TL MFC AT KOR LHD 1.00 1.02 95895-D3800 180719',
+ b'\xf1\x00TL MFC AT USA LHD 1.00 1.06 95895-D3800 190107',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a',
+ b'\xf1\x87KMLDCU585233TJ20wx\x87\x88x\x88\x98\x89vfwfwwww\x87f\x9f\xff\x98\xff\x7f\xf9\xf7s\xf1\x816T6G4051\x00\x00\xf1\x006T6J0_C2\x00\x006T6G4051\x00\x00TTL4G24NH2\x00\x00\x00\x00',
],
},
CAR.SANTA_FE: {
@@ -404,7 +645,7 @@ FW_VERSIONS = {
b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ',
b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650',
b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650',
b'\xf1\x00TM ESC \r 104\x19\a\b 58910-S2650',
@@ -459,18 +700,26 @@ FW_VERSIONS = {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ',
b'\xf1\x8799110S1500\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ',
+ b'\xf1\x8799110S1500\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ',
+ b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0',
b'\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0',
b'\xf1\x8758910-S2DA0\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0',
b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0',
b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0',
+ b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
+ b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
],
(Ecu.engine, 0x7e0, None): [
+ b'\xf1\x82TACVN5GMI3XXXH0A',
b'\xf1\x82TMBZN5TMD3XXXG2E',
b'\xf1\x82TACVN5GSI3XXXH0A',
b'\xf1\x82TMCFD5MMCXXXXG0A',
+ b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A',
+ b'\xf1\x81HM6M2_0a0_G00',
+ b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19',
@@ -480,12 +729,18 @@ FW_VERSIONS = {
b'\xf1\x00TMA MFC AT MEX LHD 1.00 1.01 99211-S2500 210205',
b'\xf1\x00TMA MFC AT USA LHD 1.00 1.00 99211-S2500 200720',
b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224',
+ b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205',
],
(Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00',
b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7',
b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00',
b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7',
b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6',
+ b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15',
+ b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc',
+ b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc',
],
},
CAR.SANTA_FE_HEV_2022: {
@@ -511,12 +766,14 @@ FW_VERSIONS = {
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102',
+ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe',
+ b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x87391312MTF0',
@@ -526,12 +783,14 @@ FW_VERSIONS = {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ',
b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ',
+ b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00',
b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00',
+ b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104',
@@ -543,6 +802,7 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822',
b'\xf1\x00CK MFC AT USA LHD 1.00 1.04 95740-J5000 180504',
+ b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0',
@@ -552,11 +812,33 @@ FW_VERSIONS = {
b'\xf1\x87VDHLG17118862DK2\x8awWwgu\x96wVfUVwv\x97xWvfvUTGTx\x87o\xff\xc9\xed\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0',
b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2',
b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94',
+ b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S',
+ b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
+ b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
+ b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0',
+ ],
+ },
+ CAR.KIA_STINGER_2022: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7d4, None): [
+ b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6',
],
},
CAR.PALISADE: {
(Ecu.fwdRadar, 0x7d0, None): [
- b'\xf1\000LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ',
+ b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ',
+ b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ',
b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ',
b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ',
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ',
@@ -564,16 +846,18 @@ FW_VERSIONS = {
b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ',
b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360',
- b'\xf1\x00LX ESC \x01 103\x31\t\020 58910-S8360',
+ b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360',
b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330',
b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330',
+ b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350',
b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330',
b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360',
b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360',
b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360',
b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360',
+ b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -581,7 +865,8 @@ FW_VERSIONS = {
b'\xf1\x81640S1051\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7d4, None): [
- b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', # modified firmware
+ b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103',
+ b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103',
b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103',
b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104',
b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13',
@@ -594,14 +879,16 @@ FW_VERSIONS = {
b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 200903',
b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105',
b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720',
+ b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28',
+ b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08',
b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6',
b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1',
b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8',
b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8',
- b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\177\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\000bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8',
+ b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8',
b'\xf1\x87LDKVAA0028604HH1\xa8\x88x\x87vgvw\x88\x99\xa8\x89gw\x86ww\x88\x97x\x97o\xf9\xff\x97w\x7f\xffo\x02\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28',
b'\xf1\x87LDKVAA3068374HH1wwww\x87xw\x87y\x99\xa7\x99w\x88\x87xw\x88\x97x\x85\xaf\xfa\xffvU/\xffU\xdc\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28',
b'\xf1\x87LDKVBN382172KF26\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\xa7\x89\x87\x88\x98x\x98\x99\xa9\x89\xa5_\xf6\xffDDO\xff\xcd\x16\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7',
@@ -642,6 +929,9 @@ FW_VERSIONS = {
b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89',
b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89',
b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8",
+ b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89',
+ b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6',
+ b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89',
],
},
CAR.VELOSTER: {
@@ -649,7 +939,7 @@ FW_VERSIONS = {
b'\xf1\x00JS__ SCC H-CUP 1.00 1.02 95650-J3200 ',
b'\xf1\x00JS__ SCC HNCUP 1.00 1.02 95650-J3100 ',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x816V8RAC00121.ELF\xf1\x00\x00\x00\x00\x00\x00\x00',
],
@@ -679,25 +969,38 @@ FW_VERSIONS = {
CAR.GENESIS_G70_2020: {
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9220 4I2VL107',
+ b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107',
+ b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00',
b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL',
+ b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL',
+ b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ',
b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 \xf1\xa01.02',
+ b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920',
+ b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
+ CAR.GENESIS_G90: {
+ (Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7'],
+ (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '],
+ (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302'],
+ (Ecu.engine, 0x7e0, None): [b'\xf1\x810000000000\x00'],
+ },
CAR.KONA: {
(Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ],
- (Ecu.esp, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ],
+ (Ecu.abs, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ],
(Ecu.engine, 0x7e0, None): [b'"\x01TOS-0NU06F301J02', ],
(Ecu.eps, 0x7d4, None): [b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', ],
(Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ],
@@ -705,14 +1008,14 @@ FW_VERSIONS = {
},
CAR.KIA_CEED: {
(Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ],
- (Ecu.esp, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ],
+ (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ],
(Ecu.fwdCamera, 0x7C4, None): [b'\xf1\000CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ],
(Ecu.engine, 0x7E0, None): [b'\001TCD-JECU4F202H0K', ],
(Ecu.transmission, 0x7E1, None): [
b'\xf1\x816U2V7051\000\000\xf1\0006U2V0_C2\000\0006U2V7051\000\000DCD0T14US1\000\000\000\000',
b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1U\x867Z',
],
- (Ecu.esp, 0x7D1, None): [b'\xf1\000CD ESC \003 102\030\b\005 58920-J7350', ],
+ (Ecu.abs, 0x7D1, None): [b'\xf1\000CD ESC \003 102\030\b\005 58920-J7350', ],
},
CAR.KIA_FORTE: {
(Ecu.eps, 0x7D4, None): [
@@ -730,7 +1033,7 @@ FW_VERSIONS = {
b'\x01TBDM1NU06F200H01',
b'391182B945\x00',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x816VGRAH00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
@@ -744,21 +1047,24 @@ FW_VERSIONS = {
b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ',
b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ',
b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ',
+ b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ',
],
(Ecu.eps, 0x7D4, None): [
b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101',
b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101',
b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102',
+ b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101',
],
(Ecu.fwdCamera, 0x7C4, None): [
b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915',
b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208',
],
- (Ecu.esp, 0x7D1, None): [
+ (Ecu.abs, 0x7D1, None): [
b'\xf1\000DL ESC \006 101 \004\002 58910-L3200',
b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200',
b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800',
b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600',
+ b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800',
],
(Ecu.engine, 0x7E0, None): [
b'\xf1\x87391212MKT0',
@@ -771,10 +1077,11 @@ FW_VERSIONS = {
b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8',
b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18',
b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%',
+ b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18',
],
},
CAR.KONA_EV: {
- (Ecu.esp, 0x7D1, None): [
+ (Ecu.abs, 0x7D1, None): [
b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000',
b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000',
b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000',
@@ -800,20 +1107,50 @@ FW_VERSIONS = {
b'\xf1\x00OSev SCC FNCUP 1.00 1.01 99110-K4000 ',
],
},
+ CAR.KONA_EV_2022: {
+ (Ecu.abs, 0x7D1, None): [
+ b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010',
+ b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010',
+ b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010',
+ b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010',
+ # TODO: these return from the MULTI request, above return from LONG
+ b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01',
+ b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04',
+ b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xf4\xff\xd9\x00\xc0',
+ ],
+ (Ecu.fwdCamera, 0x7C4, None): [
+ b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802',
+ b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802',
+ b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904',
+ b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904',
+ ],
+ (Ecu.eps, 0x7D4, None): [
+ b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102',
+ b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102',
+ b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102',
+ ],
+ (Ecu.fwdRadar, 0x7D0, None): [
+ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00',
+ ],
+ },
CAR.KIA_NIRO_EV: {
(Ecu.fwdRadar, 0x7D0, None): [
b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ',
+ b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ',
b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ',
b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ',
+ b'\xf1\x00DEev SCC FHCUP 1.00 1.03 96400-Q4000 ',
b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ',
b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ',
b'\xf1\x8799110Q4500\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ',
+ b'\xf1\x8799110Q4600\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ',
b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ',
b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ',
],
(Ecu.eps, 0x7D4, None): [
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105',
b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105',
+ b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104',
],
(Ecu.fwdCamera, 0x7C4, None): [
b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706',
@@ -821,24 +1158,30 @@ FW_VERSIONS = {
b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211',
b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821',
b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428',
+ b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821',
+ b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.03 95740-Q4000 180821',
],
},
- CAR.KIA_NIRO_HEV: {
+ CAR.KIA_NIRO_PHEV: {
(Ecu.engine, 0x7e0, None): [
- b'\xf1\x816H6F4051\000\000\000\000\000\000\000\000',
+ b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
- b"\xf1\x816U3J2051\000\000\xf1\0006U3H0_C2\000\0006U3J2051\000\000PDE0G16NS2\xf4\'\\\x91",
- b'\xf1\x816U3J2051\000\000\xf1\0006U3H0_C2\000\0006U3J2051\000\000PDE0G16NS2\000\000\000\000',
+ b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91",
+ b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00',
+ b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00',
+ b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92',
],
(Ecu.eps, 0x7D4, None): [
- b'\xf1\000DE MDPS C 1.00 1.09 56310G5301\000 4DEHC109',
+ b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109',
],
(Ecu.fwdCamera, 0x7C4, None): [
- b'\xf1\000DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424',
+ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424',
+ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117',
],
(Ecu.fwdRadar, 0x7D0, None): [
- b'\xf1\000DEhe SCC H-CUP 1.01 1.02 96400-G5100 ',
+ b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ',
],
},
CAR.KIA_NIRO_HEV_2021: {
@@ -854,6 +1197,7 @@ FW_VERSIONS = {
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221',
+ b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 99211-G5500 210428',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ',
@@ -861,16 +1205,16 @@ FW_VERSIONS = {
},
CAR.KIA_SELTOS: {
(Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x8799110Q5100\xf1\000SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ',],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x8758910-Q5450\xf1\000SP ESC \a 101\031\t\005 58910-Q5450',
b'\xf1\x8758910-Q5450\xf1\000SP ESC \t 101\031\t\005 58910-Q5450',
- ],
+ ],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81616D2051\000\000\000\000\000\000\000\000',
b'\xf1\x81616D5051\000\000\000\000\000\000\000\000',
b'\001TSP2KNL06F100J0K',
b'\001TSP2KNL06F200J0K',
- ],
+ ],
(Ecu.eps, 0x7d4, None): [
b'\xf1\000SP2 MDPS C 1.00 1.04 56300Q5200 ',
b'\xf1\000SP2 MDPS C 1.01 1.05 56300Q5200 ',
@@ -883,42 +1227,88 @@ FW_VERSIONS = {
b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\000\000\xf1\0006V2B0_C2\000\0006V2C2051\000\000CSP4N20NS3\000\000\000\000',
b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9',
b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b',
- ],
+ ],
},
- CAR.KIA_OPTIMA: {
- (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 '],
- (Ecu.esp, 0x7d1, None): [b'\xf1\x00JF ESC \v 11 \x18\x030 58920-D5180',],
- (Ecu.engine, 0x7e0, None): [
- b'\x01TJFAJNU06F201H03',
- b'\xf1\x89F1JF600AISEIU702\xf1\x82F1JF600AISEIU702',
+ CAR.KIA_OPTIMA_G4: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ',
+ ],
+ (Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00JF ESC \x0f 16 \x16\x06\x17 58920-D5080',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00',
+ ],
+ },
+ CAR.KIA_OPTIMA_G4_FL: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ',
+ ],
+ (Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180',
+ b"\xf1\x00JF ESC \t 11 \x18\x03' 58920-D5260",
],
- (Ecu.eps, 0x7d4, None): [b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409'],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32',
- b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.02 95895-D5000 h31',
- ],
- (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW'],
+ b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5100 h32',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW',
+ b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.',
+ b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01',
+ b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00',
+ b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW',
+ b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.',
+ b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01',
+ b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00',
+ b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4',
+ ],
+ },
+ CAR.ELANTRA: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e',
+ ],
+ (Ecu.eps, 0x7d4, None): [
+ b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104',
+ ],
+ (Ecu.abs, 0x7d1, None): [
+ b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ',
+ ],
},
CAR.ELANTRA_2021: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ',
b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ',
b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ',
+ b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106',
b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
+ b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819',
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819',
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205',
+ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111',
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800',
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
+ b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800',
+ b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -926,38 +1316,44 @@ FW_VERSIONS = {
b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\177\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00',
b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00',
b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00',
- b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00'
+ b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x87CXMQFM3806705JB2\x89\x87wwx\x88g\x86\x99\x87\x86xwwv\x88yv\x7f\xffz\xff\xff\xffV\x15\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x82CNCWD0AMFCXCSFFA',
- b'\xf1\x82CNCWD0AMFCXCSFFB',
+ b'\xf1\x81HM6M2_0a0_FF0',
b'\xf1\x82CNCVD0AMFCXCSFFB',
- b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82CNDWD0AMFCXCSG8A',
+ b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80',
],
},
CAR.ELANTRA_HEV_2021: {
- (Ecu.fwdCamera, 0x7c4, None) : [
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930',
b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819',
+ b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426',
],
- (Ecu.fwdRadar, 0x7d0, None) : [
+ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
],
- (Ecu.eps, 0x7d4, None) :[
+ (Ecu.eps, 0x7d4, None): [
+ b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103',
+ b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103',
b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102',
],
- (Ecu.transmission, 0x7e1, None) :[
+ (Ecu.transmission, 0x7e1, None): [
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000',
b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00',
],
- (Ecu.engine, 0x7e0, None) : [
+ (Ecu.engine, 0x7e0, None): [
b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00',
]
},
CAR.KONA_HEV: {
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00OS IEB \x01 104 \x11 58520-CM000',
],
(Ecu.fwdRadar, 0x7d0, None): [
@@ -982,35 +1378,36 @@ FW_VERSIONS = {
b'\xf1\x8799110L5000\xf1\000DNhe SCC FHCUP 1.00 1.02 99110-L5000 ',
b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
- ],
+ ],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102',
b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102',
b'\xf1\x8756310-L5450\xf1\000DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103',
- ],
+ ],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016',
b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109',
b'\xf1\000DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325',
- ],
+ ],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\000PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW',
- b'\xf1\x87959102T250\000\000\000\000\000\xf1\x81E09\000\000\000\000\000\000\000\xf1\000PSBG2323 E09\000\000\000\000\000\000\000TDN2H20SA5\x97R\x88\x9e',
+ b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e',
b'\xf1\000PSBG2323 E09\000\000\000\000\000\000\000TDN2H20SA5\x97R\x88\x9e',
b'\xf1\000PSBG2333 E16\000\000\000\000\000\000\000TDN2H20SA7\0323\xf9\xab',
b'\xf1\x87PCU\000\000\000\000\000\000\000\000\000\xf1\x81E16\000\000\000\000\000\000\000\xf1\000PSBG2333 E16\000\000\000\000\000\000\000TDN2H20SA7\0323\xf9\xab',
b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW',
- ],
+ ],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x87391162J012',
b'\xf1\x87391162J013',
- ],
+ b'\xf1\x87391062J002',
+ ],
},
CAR.KIA_SORENTO: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01'
],
- (Ecu.esp, 0x7d1, None): [
+ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330'
],
(Ecu.fwdRadar, 0x7D0, None): [
@@ -1023,6 +1420,89 @@ FW_VERSIONS = {
b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00'
],
},
+ CAR.KIA_SORENTO_PHEV_4TH_GEN: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217',
+ ]
+ },
+ CAR.KIA_EV6: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
+ b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027',
+ b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328',
+ b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027',
+ b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328',
+ ],
+ },
+ CAR.IONIQ_5: {
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
+ b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
+ ],
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206',
+ b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813',
+ b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614',
+ ],
+ },
+ CAR.TUCSON_4TH_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ',
+ ],
+ },
+ CAR.TUCSON_HYBRID_4TH_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q',
+ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K',
+ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ',
+ ],
+ },
+ CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ',
+ ],
+ },
+ CAR.SANTA_CRUZ_1ST_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ',
+ ],
+ },
+ CAR.KIA_SPORTAGE_5TH_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662',
+ b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ',
+ b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ',
+ ],
+ },
+ CAR.GENESIS_GV70_1ST_GEN: {
+ (Ecu.fwdCamera, 0x7c4, None): [
+ b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204',
+ ],
+ (Ecu.fwdRadar, 0x7d0, None): [
+ b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ',
+ ],
+ },
}
CHECKSUM = {
@@ -1032,19 +1512,27 @@ CHECKSUM = {
FEATURES = {
# which message has the gear
- "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA},
- "use_tcu_gears": {CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER},
- "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021,CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022},
+ "use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
+ "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
+ "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022},
# these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
- "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022},
+ "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022},
}
-HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022} # these cars use a different gas signal
-EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV}
+CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN}
+
+# The radar does SCC on these cars when HDA I, rather than the camera
+CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN}
+
+# The camera does SCC on these cars, rather than the radar
+CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
+
+HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # these cars use a different gas signal
+EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5}
# these cars require a special panda safety mode due to missing counters and checksums in the messages
-LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022}
+LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022}
# If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points.
# If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py
@@ -1052,40 +1540,51 @@ DBC = {
CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None),
- CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None),
CAR.GENESIS_G70: dbc_dict('hyundai_kia_generic', None),
- CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.GENESIS_G80: dbc_dict('hyundai_kia_generic', None),
CAR.GENESIS_G90: dbc_dict('hyundai_kia_generic', None),
CAR.HYUNDAI_GENESIS: dbc_dict('hyundai_kia_generic', None),
+ CAR.IONIQ_PHEV_2019: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_PHEV: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_EV_2020: dbc_dict('hyundai_kia_generic', None),
- CAR.IONIQ_EV_LTD: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.IONIQ_EV_LTD: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.IONIQ: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None),
- CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
- CAR.KIA_NIRO_HEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
+ CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None),
- CAR.KIA_OPTIMA: dbc_dict('hyundai_kia_generic', None),
+ CAR.KIA_OPTIMA_G4: dbc_dict('hyundai_kia_generic', None),
+ CAR.KIA_OPTIMA_G4_FL: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_OPTIMA_H: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format
CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None),
+ CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KONA: dbc_dict('hyundai_kia_generic', None),
CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None),
+ CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None),
- CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None),
CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None),
- CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format
- CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.TUCSON: dbc_dict('hyundai_kia_generic', None),
+ CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.VELOSTER: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None),
- CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
+ CAR.KIA_EV6: dbc_dict('hyundai_canfd', None),
+ CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
+ CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.IONIQ_5: dbc_dict('hyundai_canfd', None),
+ CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None),
+ CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None),
}
-
-STEER_THRESHOLD = 150
diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py
index 98ede7fad1..0458178ee3 100644
--- a/selfdrive/car/interfaces.py
+++ b/selfdrive/car/interfaces.py
@@ -1,27 +1,60 @@
+import yaml
import os
import time
from abc import abstractmethod, ABC
-from typing import Dict, Tuple, List
+from typing import Any, Dict, Optional, Tuple, List, Callable
from cereal import car
+from common.basedir import BASEDIR
+from common.conversions import Conversions as CV
from common.kalman.simple_kalman import KF1D
+from common.numpy_fast import interp
from common.realtime import DT_CTRL
-from selfdrive.car import gen_empty_fingerprint
-from selfdrive.config import Conversions as CV
-from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX
+from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness
+from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone
from selfdrive.controls.lib.events import Events
from selfdrive.controls.lib.vehicle_model import VehicleModel
+ButtonType = car.CarState.ButtonEvent.Type
GearShifter = car.CarState.GearShifter
EventName = car.CarEvent.EventName
+TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float]
MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
ACCEL_MAX = 2.0
ACCEL_MIN = -3.5
+FRICTION_THRESHOLD = 0.3
+TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml')
+TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')
+TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml')
+
+
+def get_torque_params(candidate):
+ with open(TORQUE_SUBSTITUTE_PATH) as f:
+ sub = yaml.load(f, Loader=yaml.CSafeLoader)
+ if candidate in sub:
+ candidate = sub[candidate]
+
+ with open(TORQUE_PARAMS_PATH) as f:
+ params = yaml.load(f, Loader=yaml.CSafeLoader)
+ with open(TORQUE_OVERRIDE_PATH) as f:
+ override = yaml.load(f, Loader=yaml.CSafeLoader)
+
+ # Ensure no overlap
+ if sum([candidate in x for x in [sub, params, override]]) > 1:
+ raise RuntimeError(f'{candidate} is defined twice in torque config')
+
+ if candidate in override:
+ out = override[candidate]
+ elif candidate in params:
+ out = params[candidate]
+ else:
+ raise NotImplementedError(f"Did not find torque params for {candidate}")
+ return {key: out[i] for i, key in enumerate(params['legend'])}
-# generic car and radar interfaces
+# generic car and radar interfaces
class CarInterfaceBase(ABC):
def __init__(self, CP, CarController, CarState):
@@ -32,13 +65,19 @@ class CarInterfaceBase(ABC):
self.steering_unpressed = 0
self.low_speed_alert = False
self.silent_steer_warning = True
+ self.v_ego_cluster_seen = False
+ self.CS = None
+ self.can_parsers = []
if CarState is not None:
self.CS = CarState(CP)
+
self.cp = self.CS.get_can_parser(CP)
self.cp_cam = self.CS.get_cam_can_parser(CP)
+ self.cp_adas = self.CS.get_adas_can_parser(CP)
self.cp_body = self.CS.get_body_can_parser(CP)
self.cp_loopback = self.CS.get_loopback_can_parser(CP)
+ self.can_parsers = [self.cp, self.cp_cam, self.cp_adas, self.cp_body, self.cp_loopback]
self.CC = None
if CarController is not None:
@@ -48,10 +87,34 @@ class CarInterfaceBase(ABC):
def get_pid_accel_limits(CP, current_speed, cruise_speed):
return ACCEL_MIN, ACCEL_MAX
+ @classmethod
+ def get_params(cls, candidate: str, fingerprint: Optional[Dict[int, Dict[int, int]]] = None, car_fw: Optional[List[car.CarParams.CarFw]] = None, experimental_long: bool = False):
+ if fingerprint is None:
+ fingerprint = gen_empty_fingerprint()
+
+ if car_fw is None:
+ car_fw = list()
+
+ ret = CarInterfaceBase.get_std_params(candidate)
+ ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long)
+
+ # Set common params using fields set by the car interface
+ # TODO: get actual value, for now starting with reasonable value for
+ # civic and scaling by mass and wheelbase
+ ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+
+ # TODO: some car interfaces set stiffness factor
+ if ret.tireStiffnessFront == 0 or ret.tireStiffnessRear == 0:
+ # TODO: start from empirically derived lateral slip stiffness for the civic and scale by
+ # mass and CG position, so all cars will have approximately similar dyn behaviors
+ ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront)
+
+ return ret
+
@staticmethod
@abstractmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- pass
+ def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool):
+ raise NotImplementedError
@staticmethod
def init(CP, logcan, sendcan):
@@ -63,21 +126,35 @@ class CarInterfaceBase(ABC):
# TODO: something with lateralPlan.curvatureRates
return desired_angle * (v_ego**2)
- @classmethod
- def get_steer_feedforward_function(cls):
- return cls.get_steer_feedforward_default
+ def get_steer_feedforward_function(self):
+ return self.get_steer_feedforward_default
+
+ @staticmethod
+ def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation):
+ # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
+ friction_interp = interp(
+ apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
+ [-FRICTION_THRESHOLD, FRICTION_THRESHOLD],
+ [-torque_params.friction, torque_params.friction]
+ )
+ friction = friction_interp if friction_compensation else 0.0
+ return (lateral_accel_value / torque_params.latAccelFactor) + friction
+
+ def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
+ return self.torque_from_lateral_accel_linear
# returns a set of default params to avoid repetition in car specific params
@staticmethod
- def get_std_params(candidate, fingerprint):
+ def get_std_params(candidate):
ret = car.CarParams.new_message()
ret.carFingerprint = candidate
- ret.unsafeMode = 0 # see panda/board/safety_declarations.h for allowed values
+
+ # Car docs fields
+ ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED']
+ ret.autoResumeSng = True # describes whether car can resume from a stop automatically
# standard ALC params
ret.steerControlType = car.CarParams.SteerControlType.torque
- ret.steerMaxBP = [0.]
- ret.steerMaxV = [1.]
ret.minSteerSpeed = 0.
ret.wheelSpeedFactor = 1.0
@@ -88,7 +165,7 @@ class CarInterfaceBase(ABC):
ret.stopAccel = -2.0
ret.stoppingDecelRate = 0.8 # brake_travel/s while trying to stop
ret.vEgoStopping = 0.5
- ret.vEgoStarting = 0.5 # needs to be >= vEgoStopping to avoid state transition oscillation
+ ret.vEgoStarting = 0.5
ret.stoppingControl = True
ret.longitudinalTuning.deadzoneBP = [0.]
ret.longitudinalTuning.deadzoneV = [0.]
@@ -103,15 +180,63 @@ class CarInterfaceBase(ABC):
ret.steerLimitTimer = 1.0
return ret
+ @staticmethod
+ def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0, use_steering_angle=True):
+ params = get_torque_params(candidate)
+
+ tune.init('torque')
+ tune.torque.useSteeringAngle = use_steering_angle
+ tune.torque.kp = 1.0
+ tune.torque.kf = 1.0
+ tune.torque.ki = 0.1
+ tune.torque.friction = params['FRICTION']
+ tune.torque.latAccelFactor = params['LAT_ACCEL_FACTOR']
+ tune.torque.latAccelOffset = 0.0
+ tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
+
@abstractmethod
- def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState:
+ def _update(self, c: car.CarControl) -> car.CarState:
pass
+ def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState:
+ # parse can
+ for cp in self.can_parsers:
+ if cp is not None:
+ cp.update_strings(can_strings)
+
+ # get CarState
+ ret = self._update(c)
+
+ ret.canValid = all(cp.can_valid for cp in self.can_parsers if cp is not None)
+ ret.canTimeout = any(cp.bus_timeout for cp in self.can_parsers if cp is not None)
+
+ if ret.vEgoCluster == 0.0 and not self.v_ego_cluster_seen:
+ ret.vEgoCluster = ret.vEgo
+ else:
+ self.v_ego_cluster_seen = True
+
+ # Many cars apply hysteresis to the ego dash speed
+ if self.CS is not None:
+ ret.vEgoCluster = apply_hysteresis(ret.vEgoCluster, self.CS.out.vEgoCluster, self.CS.cluster_speed_hyst_gap)
+ if abs(ret.vEgo) < self.CS.cluster_min_speed:
+ ret.vEgoCluster = 0.0
+
+ if ret.cruiseState.speedCluster == 0:
+ ret.cruiseState.speedCluster = ret.cruiseState.speed
+
+ # copy back for next iteration
+ reader = ret.as_reader()
+ if self.CS is not None:
+ self.CS.out = reader
+
+ return reader
+
@abstractmethod
def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]:
pass
- def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True):
+ def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True,
+ enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)):
events = Events()
if cs_out.doorOpen:
@@ -127,8 +252,6 @@ class CarInterfaceBase(ABC):
events.add(EventName.wrongCarMode)
if cs_out.espDisabled:
events.add(EventName.espDisabled)
- if cs_out.gasPressed:
- events.add(EventName.gasPressed)
if cs_out.stockFcw:
events.add(EventName.stockFcw)
if cs_out.stockAeb:
@@ -139,11 +262,25 @@ class CarInterfaceBase(ABC):
events.add(EventName.wrongCruiseMode)
if cs_out.brakeHoldActive and self.CP.openpilotLongitudinalControl:
events.add(EventName.brakeHold)
-
+ if cs_out.parkingBrake:
+ events.add(EventName.parkBrake)
+ if cs_out.accFaulted:
+ events.add(EventName.accFaulted)
+ if cs_out.steeringPressed:
+ events.add(EventName.steerOverride)
+
+ # Handle button presses
+ for b in cs_out.buttonEvents:
+ # Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port)
+ if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed):
+ events.add(EventName.buttonEnable)
+ # Disable on rising and falling edge of cancel for both stock and OP long
+ if b.type == ButtonType.cancel:
+ events.add(EventName.buttonCancel)
# Handle permanent and temporary steering faults
self.steering_unpressed = 0 if cs_out.steeringPressed else self.steering_unpressed + 1
- if cs_out.steerWarning:
+ if cs_out.steerFaultTemporary:
# if the user overrode recently, show a less harsh alert
if self.silent_steer_warning or cs_out.standstill or self.steering_unpressed < int(1.5 / DT_CTRL):
self.silent_steer_warning = True
@@ -152,17 +289,13 @@ class CarInterfaceBase(ABC):
events.add(EventName.steerTempUnavailable)
else:
self.silent_steer_warning = False
- if cs_out.steerError:
+ if cs_out.steerFaultPermanent:
events.add(EventName.steerUnavailable)
- # Disable on rising edge of gas or brake. Also disable on brake when speed > 0.
- if (cs_out.gasPressed and not self.CS.out.gasPressed) or \
- (cs_out.brakePressed and (not self.CS.out.brakePressed or not cs_out.standstill)):
- events.add(EventName.pedalPressed)
-
# we engage when pcm is active (rising edge)
+ # enabling can optionally be blocked by the car interface
if pcm_enable:
- if cs_out.cruiseState.enabled and not self.CS.out.cruiseState.enabled:
+ if cs_out.cruiseState.enabled and not self.CS.out.cruiseState.enabled and allow_enable:
events.add(EventName.pcmEnable)
elif not cs_out.cruiseState.enabled:
events.add(EventName.pcmDisable)
@@ -172,6 +305,7 @@ class CarInterfaceBase(ABC):
class RadarInterfaceBase(ABC):
def __init__(self, CP):
+ self.rcp = None
self.pts = {}
self.delay = 0
self.radar_ts = CP.radarTimeStep
@@ -195,13 +329,15 @@ class CarStateBase(ABC):
self.right_blinker_cnt = 0
self.left_blinker_prev = False
self.right_blinker_prev = False
+ self.cluster_speed_hyst_gap = 0.0
+ self.cluster_min_speed = 0.0 # min speed before dropping to 0
- # Q = np.matrix([[10.0, 0.0], [0.0, 100.0]])
- # R = 1e3
+ # Q = np.matrix([[0.0, 0.0], [0.0, 100.0]])
+ # R = 0.3
self.v_ego_kf = KF1D(x0=[[0.0], [0.0]],
A=[[1.0, DT_CTRL], [0.0, 1.0]],
C=[1.0, 0.0],
- K=[[0.12287673], [0.29666309]])
+ K=[[0.17406039], [1.65925647]])
def update_speed_kf(self, v_ego_raw):
if abs(v_ego_raw - self.v_ego_kf.x[0][0]) > 2.0: # Prevent large accelerations when car starts at non zero speed
@@ -252,18 +388,31 @@ class CarStateBase(ABC):
return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0)
@staticmethod
- def parse_gear_shifter(gear: str) -> car.CarState.GearShifter:
+ def parse_gear_shifter(gear: Optional[str]) -> car.CarState.GearShifter:
+ if gear is None:
+ return GearShifter.unknown
+
d: Dict[str, car.CarState.GearShifter] = {
- 'P': GearShifter.park, 'R': GearShifter.reverse, 'N': GearShifter.neutral,
- 'E': GearShifter.eco, 'T': GearShifter.manumatic, 'D': GearShifter.drive,
- 'S': GearShifter.sport, 'L': GearShifter.low, 'B': GearShifter.brake
+ 'P': GearShifter.park, 'PARK': GearShifter.park,
+ 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse,
+ 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral,
+ 'E': GearShifter.eco, 'ECO': GearShifter.eco,
+ 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic,
+ 'D': GearShifter.drive, 'DRIVE': GearShifter.drive,
+ 'S': GearShifter.sport, 'SPORT': GearShifter.sport,
+ 'L': GearShifter.low, 'LOW': GearShifter.low,
+ 'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
}
- return d.get(gear, GearShifter.unknown)
+ return d.get(gear.upper(), GearShifter.unknown)
@staticmethod
def get_cam_can_parser(CP):
return None
+ @staticmethod
+ def get_adas_can_parser(CP):
+ return None
+
@staticmethod
def get_body_can_parser(CP):
return None
@@ -271,3 +420,31 @@ class CarStateBase(ABC):
@staticmethod
def get_loopback_can_parser(CP):
return None
+
+
+# interface-specific helpers
+
+def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str, Any]:
+ # read all the folders in selfdrive/car and return a dict where:
+ # - keys are all the car models or brand names
+ # - values are attr values from all car folders
+ result = {}
+ for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]):
+ try:
+ brand_name = car_folder.split('/')[-1]
+ brand_values = __import__(f'selfdrive.car.{brand_name}.values', fromlist=[attr])
+ if hasattr(brand_values, attr) or not ignore_none:
+ attr_data = getattr(brand_values, attr, None)
+ else:
+ continue
+
+ if combine_brands:
+ if isinstance(attr_data, dict):
+ for f, v in attr_data.items():
+ result[f] = v
+ else:
+ result[brand_name] = attr_data
+ except (ImportError, OSError):
+ pass
+
+ return result
diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py
index 1209a8f1a1..d9c658a14c 100644
--- a/selfdrive/car/isotp_parallel_query.py
+++ b/selfdrive/car/isotp_parallel_query.py
@@ -1,32 +1,29 @@
import time
from collections import defaultdict
from functools import partial
-from typing import Optional
import cereal.messaging as messaging
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
from selfdrive.boardd.boardd import can_list_to_can_capnp
from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr
class IsoTpParallelQuery:
- def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addr=False, debug=False):
+ def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addrs=None, debug=False, response_pending_timeout=10):
self.sendcan = sendcan
self.logcan = logcan
self.bus = bus
self.request = request
self.response = response
+ self.functional_addrs = functional_addrs or []
self.debug = debug
- self.functional_addr = functional_addr
+ self.response_pending_timeout = response_pending_timeout
- self.real_addrs = []
- for a in addrs:
- if isinstance(a, tuple):
- self.real_addrs.append(a)
- else:
- self.real_addrs.append((a, None))
+ real_addrs = [a if isinstance(a, tuple) else (a, None) for a in addrs]
+ for tx_addr, _ in real_addrs:
+ assert tx_addr not in FUNCTIONAL_ADDRS, f"Functional address should be defined in functional_addrs: {hex(tx_addr)}"
- self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in self.real_addrs}
+ self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs}
self.msg_buffer = defaultdict(list)
def rx(self):
@@ -35,13 +32,8 @@ class IsoTpParallelQuery:
for packet in can_packets:
for msg in packet.can:
- if msg.src == self.bus:
- if self.functional_addr:
- if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF):
- fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32)
- self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src))
- elif msg.address in self.msg_addrs.values():
- self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
+ if msg.src == self.bus and msg.address in self.msg_addrs.values():
+ self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
def _can_tx(self, tx_addr, dat, bus):
"""Helper function to send single message"""
@@ -71,10 +63,17 @@ class IsoTpParallelQuery:
messaging.drain_sock(self.logcan)
self.msg_buffer = defaultdict(list)
- def get_data(self, timeout, total_timeout=None):
- if total_timeout is None:
- total_timeout = 10 * timeout
+ def _create_isotp_msg(self, tx_addr, sub_addr, rx_addr):
+ can_client = CanClient(self._can_tx, partial(self._can_rx, rx_addr, sub_addr=sub_addr), tx_addr, rx_addr,
+ self.bus, sub_addr=sub_addr, debug=self.debug)
+ max_len = 8 if sub_addr is None else 7
+ # uses iso-tp frame separation time of 10 ms
+ # TODO: use single_frame_mode so ECUs can send as fast as they want,
+ # as well as reduces chances we process messages from previous queries
+ return IsoTpMessage(can_client, timeout=0, separation_time=0.01, debug=self.debug, max_len=max_len)
+
+ def get_data(self, timeout, total_timeout=60.):
self._drain_rx()
# Create message objects
@@ -82,25 +81,22 @@ class IsoTpParallelQuery:
request_counter = {}
request_done = {}
for tx_addr, rx_addr in self.msg_addrs.items():
- # rx_addr not set when using functional tx addr
- id_addr = rx_addr or tx_addr[0]
- sub_addr = tx_addr[1]
-
- can_client = CanClient(self._can_tx, partial(self._can_rx, id_addr, sub_addr=sub_addr), tx_addr[0], rx_addr,
- self.bus, sub_addr=sub_addr, debug=self.debug)
-
- max_len = 8 if sub_addr is None else 7
-
- msg = IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug)
- msg.send(self.request[0])
-
- msgs[tx_addr] = msg
+ msgs[tx_addr] = self._create_isotp_msg(*tx_addr, rx_addr)
request_counter[tx_addr] = 0
request_done[tx_addr] = False
+ # Send first request to functional addrs, subsequent responses are handled on physical addrs
+ if len(self.functional_addrs):
+ for addr in self.functional_addrs:
+ self._create_isotp_msg(addr, None, -1).send(self.request[0])
+
+ # If querying functional addrs, set up physical IsoTpMessages to send consecutive frames
+ for msg in msgs.values():
+ msg.send(self.request[0], setup_only=len(self.functional_addrs) > 0)
+
results = {}
start_time = time.monotonic()
- last_response_time = start_time
+ response_timeouts = {tx_addr: start_time + timeout for tx_addr in self.msg_addrs}
while True:
self.rx()
@@ -109,12 +105,15 @@ class IsoTpParallelQuery:
for tx_addr, msg in msgs.items():
try:
- dat: Optional[bytes] = msg.recv()
+ dat, updated = msg.recv()
except Exception:
- cloudlog.exception("Error processing UDS response")
+ cloudlog.exception(f"Error processing UDS response: {tx_addr}")
request_done[tx_addr] = True
continue
+ if updated:
+ response_timeouts[tx_addr] = time.monotonic() + timeout
+
if not dat:
continue
@@ -123,7 +122,6 @@ class IsoTpParallelQuery:
response_valid = dat[:len(expected_response)] == expected_response
if response_valid:
- last_response_time = time.monotonic()
if counter + 1 < len(self.request):
msg.send(self.request[counter + 1])
request_counter[tx_addr] += 1
@@ -131,18 +129,25 @@ class IsoTpParallelQuery:
results[tx_addr] = dat[len(expected_response):]
request_done[tx_addr] = True
else:
- request_done[tx_addr] = True
- cloudlog.warning(f"iso-tp query bad response: 0x{dat.hex()}")
+ error_code = dat[2] if len(dat) > 2 else -1
+ if error_code == 0x78:
+ response_timeouts[tx_addr] = time.monotonic() + self.response_pending_timeout
+ if self.debug:
+ cloudlog.warning(f"iso-tp query response pending: {tx_addr}")
+ else:
+ response_timeouts[tx_addr] = 0
+ request_done[tx_addr] = True
+ cloudlog.error(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}")
cur_time = time.monotonic()
- if cur_time - last_response_time > timeout:
+ if cur_time - max(response_timeouts.values()) > 0:
for tx_addr in msgs:
- if (request_counter[tx_addr] > 0) and (not request_done[tx_addr]):
- cloudlog.warning(f"iso-tp query timeout after receiving response: {tx_addr}")
+ if request_counter[tx_addr] > 0 and not request_done[tx_addr]:
+ cloudlog.error(f"iso-tp query timeout after receiving response: {tx_addr}")
break
if cur_time - start_time > total_timeout:
- cloudlog.warning("iso-tp query timeout while receiving data")
+ cloudlog.error("iso-tp query timeout while receiving data")
break
return results
diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py
index 60bb620377..2add59ccb0 100644
--- a/selfdrive/car/mazda/carcontroller.py
+++ b/selfdrive/car/mazda/carcontroller.py
@@ -1,65 +1,64 @@
from cereal import car
from opendbc.can.packer import CANPacker
+from selfdrive.car import apply_std_steer_torque_limits
from selfdrive.car.mazda import mazdacan
from selfdrive.car.mazda.values import CarControllerParams, Buttons
-from selfdrive.car import apply_std_steer_torque_limits
VisualAlert = car.CarControl.HUDControl.VisualAlert
-class CarController():
+
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
self.apply_steer_last = 0
self.packer = CANPacker(dbc_name)
- self.steer_rate_limited = False
self.brake_counter = 0
+ self.frame = 0
- def update(self, c, CS, frame):
+ def update(self, CC, CS):
can_sends = []
apply_steer = 0
- self.steer_rate_limited = False
- if c.active:
+ if CC.latActive:
# calculate steer and also set limits due to driver torque
- new_steer = int(round(c.actuators.steer * CarControllerParams.STEER_MAX))
+ new_steer = int(round(CC.actuators.steer * CarControllerParams.STEER_MAX))
apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last,
CS.out.steeringTorque, CarControllerParams)
- self.steer_rate_limited = new_steer != apply_steer
- if CS.out.standstill and frame % 5 == 0:
- # Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds
- # Send Resume button at 20hz if we're engaged at standstill to support full stop and go!
- # TODO: improve the resume trigger logic by looking at actual radar data
- can_sends.append(mazdacan.create_button_cmd(self.packer, CS.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME))
-
- if c.cruiseControl.cancel or (CS.out.cruiseState.enabled and not c.enabled):
+ if CC.cruiseControl.cancel:
# If brake is pressed, let us wait >70ms before trying to disable crz to avoid
# a race condition with the stock system, where the second cancel from openpilot
# will disable the crz 'main on'. crz ctrl msg runs at 50hz. 70ms allows us to
# read 3 messages and most likely sync state before we attempt cancel.
self.brake_counter = self.brake_counter + 1
- if frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7):
+ if self.frame % 10 == 0 and not (CS.out.brakePressed and self.brake_counter < 7):
# Cancel Stock ACC if it's enabled while OP is disengaged
# Send at a rate of 10hz until we sync with stock ACC state
- can_sends.append(mazdacan.create_button_cmd(self.packer, CS.CP.carFingerprint, CS.crz_btns_counter, Buttons.CANCEL))
+ can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.CANCEL))
else:
self.brake_counter = 0
+ if CC.cruiseControl.resume and self.frame % 5 == 0:
+ # Mazda Stop and Go requires a RES button (or gas) press if the car stops more than 3 seconds
+ # Send Resume button when planner wants car to move
+ can_sends.append(mazdacan.create_button_cmd(self.packer, self.CP.carFingerprint, CS.crz_btns_counter, Buttons.RESUME))
self.apply_steer_last = apply_steer
# send HUD alerts
- if frame % 50 == 0:
- ldw = c.hudControl.visualAlert == VisualAlert.ldw
- steer_required = c.hudControl.visualAlert == VisualAlert.steerRequired
+ if self.frame % 50 == 0:
+ ldw = CC.hudControl.visualAlert == VisualAlert.ldw
+ steer_required = CC.hudControl.visualAlert == VisualAlert.steerRequired
# TODO: find a way to silence audible warnings so we can add more hud alerts
steer_required = steer_required and CS.lkas_allowed_speed
can_sends.append(mazdacan.create_alert_command(self.packer, CS.cam_laneinfo, ldw, steer_required))
# send steering command
- can_sends.append(mazdacan.create_steering_control(self.packer, CS.CP.carFingerprint,
- frame, apply_steer, CS.cam_lkas))
+ can_sends.append(mazdacan.create_steering_control(self.packer, self.CP.carFingerprint,
+ self.frame, apply_steer, CS.cam_lkas))
- new_actuators = c.actuators.copy()
+ new_actuators = CC.actuators.copy()
new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py
index feb1147549..944d79809b 100644
--- a/selfdrive/car/mazda/carstate.py
+++ b/selfdrive/car/mazda/carstate.py
@@ -1,5 +1,5 @@
from cereal import car
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
from selfdrive.car.interfaces import CarStateBase
@@ -72,11 +72,14 @@ class CarState(CarStateBase):
self.lkas_allowed_speed = True
elif speed_kph < LKAS_LIMITS.DISABLE_SPEED:
self.lkas_allowed_speed = False
+ else:
+ self.lkas_allowed_speed = True
# TODO: the signal used for available seems to be the adaptive cruise signal, instead of the main on
# it should be used for carState.cruiseState.nonAdaptive instead
ret.cruiseState.available = cp.vl["CRZ_CTRL"]["CRZ_AVAILABLE"] == 1
ret.cruiseState.enabled = cp.vl["CRZ_CTRL"]["CRZ_ACTIVE"] == 1
+ ret.cruiseState.standstill = cp.vl["PEDALS"]["STANDSTILL"] == 1
ret.cruiseState.speed = cp.vl["CRZ_EVENTS"]["CRZ_SPEED"] * CV.KPH_TO_MS
if ret.cruiseState.enabled:
@@ -88,7 +91,7 @@ class CarState(CarStateBase):
# Check if LKAS is disabled due to lack of driver torque when all other states indicate
# it should be enabled (steer lockout). Don't warn until we actually get lkas active
# and lose it again, i.e, after initial lkas activation
- ret.steerWarning = self.lkas_allowed_speed and lkas_blocked
+ ret.steerFaultTemporary = self.lkas_allowed_speed and lkas_blocked
self.acc_active_last = ret.cruiseState.enabled
@@ -98,7 +101,7 @@ class CarState(CarStateBase):
self.lkas_disabled = cp_cam.vl["CAM_LANEINFO"]["LANE_LINES"] == 0
self.cam_lkas = cp_cam.vl["CAM_LKAS"]
self.cam_laneinfo = cp_cam.vl["CAM_LANEINFO"]
- ret.steerError = cp_cam.vl["CAM_LKAS"]["ERR_BIT_1"] == 1
+ ret.steerFaultPermanent = cp_cam.vl["CAM_LKAS"]["ERR_BIT_1"] == 1
return ret
diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py
index fb8edd6f42..fdd2439ff9 100755
--- a/selfdrive/car/mazda/interface.py
+++ b/selfdrive/car/mazda/interface.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
from selfdrive.car.mazda.values import CAR, LKAS_LIMITS
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type
@@ -11,13 +11,7 @@ EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase):
@staticmethod
- def compute_gb(accel, speed):
- return float(accel) / 4.0
-
- @staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
-
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "mazda"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)]
ret.radarOffCan = True
@@ -25,48 +19,33 @@ class CarInterface(CarInterfaceBase):
ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021)
ret.steerActuatorDelay = 0.1
- ret.steerRateCost = 1.0
ret.steerLimitTimer = 0.8
tire_stiffness_factor = 0.70 # not optimized yet
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
if candidate in (CAR.CX5, CAR.CX5_2022):
ret.mass = 3655 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.7
ret.steerRatio = 15.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]]
- ret.lateralTuning.pid.kf = 0.00006
elif candidate in (CAR.CX9, CAR.CX9_2021):
ret.mass = 4217 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 3.1
ret.steerRatio = 17.6
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]]
- ret.lateralTuning.pid.kf = 0.00006
elif candidate == CAR.MAZDA3:
ret.mass = 2875 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.7
ret.steerRatio = 14.0
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]]
- ret.lateralTuning.pid.kf = 0.00006
elif candidate == CAR.MAZDA6:
ret.mass = 3443 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83
ret.steerRatio = 15.5
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]]
- ret.lateralTuning.pid.kf = 0.00006
if candidate not in (CAR.CX5_2022, ):
ret.minSteerSpeed = LKAS_LIMITS.DISABLE_SPEED * CV.KPH_TO_MS
ret.centerToFront = ret.wheelbase * 0.41
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
-
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
@@ -75,13 +54,8 @@ class CarInterface(CarInterfaceBase):
return ret
# returns a car.CarState
- def update(self, c, can_strings):
-
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
# events
events = self.create_common_events(ret)
@@ -93,10 +67,7 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- ret = self.CC.update(c, self.CS, self.frame)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py
index 1fcf184281..e6e9b3aee8 100644
--- a/selfdrive/car/mazda/values.py
+++ b/selfdrive/car/mazda/values.py
@@ -1,5 +1,12 @@
-from selfdrive.car import dbc_dict
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Union
+
from cereal import car
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
Ecu = car.CarParams.Ecu
@@ -14,6 +21,7 @@ class CarControllerParams:
STEER_DRIVER_FACTOR = 1 # from dbc
STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor
+
class CAR:
CX5 = "MAZDA CX-5"
CX9 = "MAZDA CX-9"
@@ -22,11 +30,29 @@ class CAR:
CX9_2021 = "MAZDA CX-9 2021"
CX5_2022 = "MAZDA CX-5 2022"
+
+@dataclass
+class MazdaCarInfo(CarInfo):
+ package: str = "All"
+ harness: Enum = Harness.mazda
+
+
+CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = {
+ CAR.CX5: MazdaCarInfo("Mazda CX-5 2017-21"),
+ CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"),
+ CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"),
+ CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"),
+ CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4"),
+ CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-23"),
+}
+
+
class LKAS_LIMITS:
STEER_THRESHOLD = 15
DISABLE_SPEED = 45 # kph
ENABLE_SPEED = 52 # kph
+
class Buttons:
NONE = 0
SET_PLUS = 1
@@ -35,36 +61,53 @@ class Buttons:
CANCEL = 4
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ ),
+ ],
+)
+
FW_VERSIONS = {
- CAR.CX5_2022 : {
+ CAR.CX5_2022: {
(Ecu.eps, 0x730, None): [
b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'KSD5-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
},
CAR.CX5: {
(Ecu.eps, 0x730, None): [
+ b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -88,7 +131,7 @@ FW_VERSIONS = {
b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KBJ5-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KL2K-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -106,6 +149,7 @@ FW_VERSIONS = {
],
(Ecu.transmission, 0x7e1, None): [
b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PA66-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -145,7 +189,7 @@ FW_VERSIONS = {
b'TK80-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'TK80-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'TK79-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'TK79-437K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -189,7 +233,7 @@ FW_VERSIONS = {
b'GHP9-67Y10---41\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'B45A-437AS-0-08\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
@@ -220,7 +264,7 @@ FW_VERSIONS = {
b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'K131-67XK2-E\000\000\000\000\000\000\000\000\000\000\000\000',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'GBVH-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GDDM-437K2-A\000\000\000\000\000\000\000\000\000\000\000\000',
],
@@ -239,23 +283,28 @@ FW_VERSIONS = {
b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
+ b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x760, None): [
+ (Ecu.abs, 0x760, None): [
b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x706, None): [
b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
}
}
diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py
index b2e315a5f9..3ac487dbb7 100755
--- a/selfdrive/car/mock/interface.py
+++ b/selfdrive/car/mock/interface.py
@@ -1,44 +1,28 @@
#!/usr/bin/env python3
-import math
from cereal import car
-from selfdrive.config import Conversions as CV
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
import cereal.messaging as messaging
-from selfdrive.car import gen_empty_fingerprint, get_safety_config
+from selfdrive.car import get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
-# mocked car interface to work with chffrplus
-TS = 0.01 # 100Hz
-YAW_FR = 0.2 # ~0.8s time constant on yaw rate filter
-# low pass gain
-LPG = 2 * math.pi * YAW_FR * TS / (1 + 2 * math.pi * YAW_FR * TS)
-
+# mocked car interface to work with chffrplus
class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState):
super().__init__(CP, CarController, CarState)
cloudlog.debug("Using Mock Car Interface")
- self.sensor = messaging.sub_sock('sensorEvents')
- self.gps = messaging.sub_sock('gpsLocationExternal')
+ self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal'])
self.speed = 0.
self.prev_speed = 0.
- self.yaw_rate = 0.
- self.yaw_rate_meas = 0.
-
- @staticmethod
- def compute_gb(accel, speed):
- return accel
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "mock"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)]
ret.mass = 1700.
- ret.rotationalInertia = 2500.
ret.wheelbase = 2.70
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 13. # reasonable
@@ -48,30 +32,22 @@ class CarInterface(CarInterfaceBase):
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- # get basic data from phone and gps since CAN isn't connected
- sensors = messaging.recv_sock(self.sensor)
- if sensors is not None:
- for sensor in sensors.sensorEvents:
- if sensor.type == 4: # gyro
- self.yaw_rate_meas = -sensor.gyro.v[0]
-
- gps = messaging.recv_sock(self.gps)
- if gps is not None:
+ def _update(self, c):
+ self.sm.update(0)
+ gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation'
+ if self.sm.updated[gps_sock]:
self.prev_speed = self.speed
- self.speed = gps.gpsLocationExternal.speed
+ self.speed = self.sm[gps_sock].speed
# create message
ret = car.CarState.new_message()
- ret.canValid = True
# speeds
ret.vEgo = self.speed
ret.vEgoRaw = self.speed
- a = self.speed - self.prev_speed
- ret.aEgo = a
- ret.brakePressed = a < -0.5
+ ret.aEgo = self.speed - self.prev_speed
+ ret.brakePressed = ret.aEgo < -0.5
ret.standstill = self.speed < 0.01
ret.wheelSpeeds.fl = self.speed
@@ -79,11 +55,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeeds.rl = self.speed
ret.wheelSpeeds.rr = self.speed
- self.yawRate = LPG * self.yaw_rate_meas + (1. - LPG) * self.yaw_rate
- curvature = self.yaw_rate / max(self.speed, 1.)
- ret.steeringAngleDeg = curvature * self.CP.steerRatio * self.CP.wheelbase * CV.RAD_TO_DEG
-
- return ret.as_reader()
+ return ret
def apply(self, c):
# in mock no carcontrols
diff --git a/selfdrive/car/mock/values.py b/selfdrive/car/mock/values.py
index 0dd91565bd..dfc7902e41 100644
--- a/selfdrive/car/mock/values.py
+++ b/selfdrive/car/mock/values.py
@@ -1,2 +1,12 @@
+from typing import Dict, List, Optional, Union
+
+from selfdrive.car.docs_definitions import CarInfo
+
+
class CAR:
MOCK = 'mock'
+
+
+CAR_INFO: Dict[str, Optional[Union[CarInfo, List[CarInfo]]]] = {
+ CAR.MOCK: None,
+}
diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py
index 8b30c11249..dbc2b33c6b 100644
--- a/selfdrive/car/nissan/carcontroller.py
+++ b/selfdrive/car/nissan/carcontroller.py
@@ -1,38 +1,39 @@
from cereal import car
from common.numpy_fast import clip, interp
-from selfdrive.car.nissan import nissancan
from opendbc.can.packer import CANPacker
+from selfdrive.car.nissan import nissancan
from selfdrive.car.nissan.values import CAR, CarControllerParams
-
VisualAlert = car.CarControl.HUDControl.VisualAlert
-class CarController():
+class CarController:
def __init__(self, dbc_name, CP, VM):
self.CP = CP
self.car_fingerprint = CP.carFingerprint
+ self.frame = 0
self.lkas_max_torque = 0
self.last_angle = 0
self.packer = CANPacker(dbc_name)
- def update(self, c, enabled, CS, frame, actuators, cruise_cancel, hud_alert,
- left_line, right_line, left_lane_depart, right_lane_depart):
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+ pcm_cancel_cmd = CC.cruiseControl.cancel
can_sends = []
### STEER ###
- acc_active = CS.out.cruiseState.enabled
lkas_hud_msg = CS.lkas_hud_msg
lkas_hud_info_msg = CS.lkas_hud_info_msg
apply_angle = actuators.steeringAngleDeg
- steer_hud_alert = 1 if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0
+ steer_hud_alert = 1 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0
- if c.active:
- # # windup slower
+ if CC.latActive:
+ # windup slower
if self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle):
angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_V)
else:
@@ -58,29 +59,25 @@ class CarController():
self.last_angle = apply_angle
- if not enabled and acc_active:
- # send acc cancel cmd if drive is disabled but pcm is still on, or if the system can't be activated
- cruise_cancel = 1
-
- if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and cruise_cancel:
- can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg, frame))
+ if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and pcm_cancel_cmd:
+ can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg))
# TODO: Find better way to cancel!
# For some reason spamming the cancel button is unreliable on the Leaf
# We now cancel by making propilot think the seatbelt is unlatched,
# this generates a beep and a warning message every time you disengage
- if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and frame % 2 == 0:
- can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, cruise_cancel))
+ if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and self.frame % 2 == 0:
+ can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, pcm_cancel_cmd))
can_sends.append(nissancan.create_steering_control(
- self.packer, apply_angle, frame, enabled, self.lkas_max_torque))
+ self.packer, apply_angle, self.frame, CC.enabled, self.lkas_max_torque))
if lkas_hud_msg and lkas_hud_info_msg:
- if frame % 2 == 0:
+ if self.frame % 2 == 0:
can_sends.append(nissancan.create_lkas_hud_msg(
- self.packer, lkas_hud_msg, enabled, left_line, right_line, left_lane_depart, right_lane_depart))
+ self.packer, lkas_hud_msg, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart))
- if frame % 50 == 0:
+ if self.frame % 50 == 0:
can_sends.append(nissancan.create_lkas_hud_info_msg(
self.packer, lkas_hud_info_msg, steer_hud_alert
))
@@ -88,4 +85,5 @@ class CarController():
new_actuators = actuators.copy()
new_actuators.steeringAngleDeg = apply_angle
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py
index 6b030e9b45..d6b6d17d55 100644
--- a/selfdrive/car/nissan/carstate.py
+++ b/selfdrive/car/nissan/carstate.py
@@ -3,7 +3,7 @@ from collections import deque
from cereal import car
from opendbc.can.can_define import CANDefine
from selfdrive.car.interfaces import CarStateBase
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
from opendbc.can.parser import CANParser
from selfdrive.car.nissan.values import CAR, DBC, CarControllerParams
@@ -44,7 +44,7 @@ class CarState(CarStateBase):
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
- ret.standstill = ret.vEgoRaw < 0.01
+ ret.standstill = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] == 0.0 and cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] == 0.0
if self.CP.carFingerprint == CAR.ALTIMA:
ret.cruiseState.enabled = bool(cp.vl["CRUISE_STATE"]["CRUISE_ENABLED"])
@@ -74,8 +74,8 @@ class CarState(CarStateBase):
conversion = CV.MPH_TO_MS if cp.vl["HUD_SETTINGS"]["SPEED_MPH"] else CV.KPH_TO_MS
else:
conversion = CV.MPH_TO_MS if cp.vl["HUD"]["SPEED_MPH"] else CV.KPH_TO_MS
- speed -= 1 # Speed on HUD is always 1 lower than actually sent on can bus
ret.cruiseState.speed = speed * conversion
+ ret.cruiseState.speedCluster = (speed - 1) * conversion # Speed on HUD is always 1 lower than actually sent on can bus
if self.CP.carFingerprint == CAR.ALTIMA:
ret.steeringTorque = cp_cam.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"]
diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py
index c32fb13780..386e859089 100644
--- a/selfdrive/car/nissan/interface.py
+++ b/selfdrive/car/nissan/interface.py
@@ -1,67 +1,47 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.car.nissan.values import CAR
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from selfdrive.car import STD_CARGO_KG, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
+from selfdrive.car.nissan.values import CAR
+
class CarInterface(CarInterfaceBase):
- def __init__(self, CP, CarController, CarState):
- super().__init__(CP, CarController, CarState)
- self.cp_adas = self.CS.get_adas_can_parser(CP)
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
-
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "nissan"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.nissan)]
+ ret.autoResumeSng = False
ret.steerLimitTimer = 1.0
- ret.steerRateCost = 0.5
ret.steerActuatorDelay = 0.1
+ ret.steerRatio = 17
+
+ ret.steerControlType = car.CarParams.SteerControlType.angle
+ ret.radarOffCan = True
if candidate in (CAR.ROGUE, CAR.XTRAIL):
ret.mass = 1610 + STD_CARGO_KG
ret.wheelbase = 2.705
ret.centerToFront = ret.wheelbase * 0.44
- ret.steerRatio = 17
elif candidate in (CAR.LEAF, CAR.LEAF_IC):
ret.mass = 1610 + STD_CARGO_KG
ret.wheelbase = 2.705
ret.centerToFront = ret.wheelbase * 0.44
- ret.steerRatio = 17
elif candidate == CAR.ALTIMA:
# Altima has EPS on C-CAN unlike the others that have it on V-CAN
ret.safetyConfigs[0].safetyParam = 1 # EPS is on alternate bus
ret.mass = 1492 + STD_CARGO_KG
ret.wheelbase = 2.824
ret.centerToFront = ret.wheelbase * 0.44
- ret.steerRatio = 17
-
- ret.steerControlType = car.CarParams.SteerControlType.angle
- ret.radarOffCan = True
-
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
-
- # TODO: start from empirically derived lateral slip stiffness for the civic and scale by
- # mass and CG position, so all cars will have approximately similar dyn behaviors
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront)
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
- self.cp_adas.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_adas, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_adas.can_valid and self.cp_cam.can_valid
-
buttonEvents = []
be = car.CarState.ButtonEvent.new_message()
be.type = car.CarState.ButtonEvent.Type.accelCruise
@@ -74,14 +54,7 @@ class CarInterface(CarInterfaceBase):
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- hud_control = c.hudControl
- ret = self.CC.update(c, c.enabled, self.CS, self.frame, c.actuators,
- c.cruiseControl.cancel, hud_control.visualAlert,
- hud_control.leftLaneVisible, hud_control.rightLaneVisible,
- hud_control.leftLaneDepart, hud_control.rightLaneDepart)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/nissan/nissancan.py b/selfdrive/car/nissan/nissancan.py
index ceace5088a..01fb3463a9 100644
--- a/selfdrive/car/nissan/nissancan.py
+++ b/selfdrive/car/nissan/nissancan.py
@@ -2,17 +2,17 @@ import copy
import crcmod
from selfdrive.car.nissan.values import CAR
+# TODO: add this checksum to the CANPacker
nissan_checksum = crcmod.mkCrcFun(0x11d, initCrc=0x00, rev=False, xorOut=0xff)
def create_steering_control(packer, apply_steer, frame, steer_on, lkas_max_torque):
- idx = (frame % 16)
values = {
+ "COUNTER": frame % 0x10,
"DESIRED_ANGLE": apply_steer,
"SET_0x80_2": 0x80,
"SET_0x80": 0x80,
"MAX_TORQUE": lkas_max_torque if steer_on else 0,
- "COUNTER": idx,
"LKA_ACTIVE": steer_on,
}
@@ -22,12 +22,9 @@ def create_steering_control(packer, apply_steer, frame, steer_on, lkas_max_torqu
return packer.make_can_msg("LKAS", 0, values)
-def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg, frame):
+def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg):
values = copy.copy(cruise_throttle_msg)
- can_bus = 2
-
- if car_fingerprint == CAR.ALTIMA:
- can_bus = 1
+ can_bus = 1 if car_fingerprint == CAR.ALTIMA else 2
values["CANCEL_BUTTON"] = 1
values["NO_BUTTON_PRESSED"] = 0
@@ -35,7 +32,6 @@ def create_acc_cancel_cmd(packer, car_fingerprint, cruise_throttle_msg, frame):
values["SET_BUTTON"] = 0
values["RES_BUTTON"] = 0
values["FOLLOW_DISTANCE_BUTTON"] = 0
- values["COUNTER"] = (frame % 4)
return packer.make_can_msg("CRUISE_THROTTLE", can_bus, values)
diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py
index f7001d1417..09bd7ca838 100644
--- a/selfdrive/car/nissan/values.py
+++ b/selfdrive/car/nissan/values.py
@@ -1,5 +1,13 @@
-from selfdrive.car import dbc_dict
+from dataclasses import dataclass
+from typing import Dict, List, Optional, Union
+from enum import Enum
+
from cereal import car
+from panda.python import uds
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
Ecu = car.CarParams.Ecu
@@ -10,6 +18,7 @@ class CarControllerParams:
LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower
STEER_THRESHOLD = 1.0
+
class CAR:
XTRAIL = "NISSAN X-TRAIL 2017"
LEAF = "NISSAN LEAF 2018"
@@ -20,6 +29,20 @@ class CAR:
ALTIMA = "NISSAN ALTIMA 2020"
+@dataclass
+class NissanCarInfo(CarInfo):
+ package: str = "ProPILOT Assist"
+ harness: Enum = Harness.nissan_a
+
+
+CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = {
+ CAR.XTRAIL: NissanCarInfo("Nissan X-Trail 2017"),
+ CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-22"),
+ CAR.LEAF_IC: None, # same platforms
+ CAR.ROGUE: NissanCarInfo("Nissan Rogue 2018-20"),
+ CAR.ALTIMA: NissanCarInfo("Nissan Altima 2019-20", harness=Harness.nissan_b),
+}
+
FINGERPRINTS = {
CAR.XTRAIL: [
{
@@ -55,6 +78,33 @@ FINGERPRINTS = {
]
}
+NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0xc0])
+NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0xc0])
+
+NISSAN_VERSION_REQUEST_KWP = b'\x21\x83'
+NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83'
+
+NISSAN_RX_OFFSET = 0x20
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
+ [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
+ ),
+ Request(
+ [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP],
+ [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP],
+ rx_offset=NISSAN_RX_OFFSET,
+ ),
+ Request(
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
+ [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
+ rx_offset=NISSAN_RX_OFFSET,
+ ),
+ ],
+)
+
FW_VERSIONS = {
CAR.ALTIMA: {
(Ecu.fwdCamera, 0x707, None): [
@@ -75,7 +125,7 @@ FW_VERSIONS = {
b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80',
b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80',
],
- (Ecu.esp, 0x740, None): [
+ (Ecu.abs, 0x740, None): [
b'476605SH1D',
b'476605SK2A',
],
@@ -92,7 +142,7 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x707, None): [
b'284N86FR2A',
],
- (Ecu.esp, 0x740, None): [
+ (Ecu.abs, 0x740, None): [
b'6FU1BD\x11\x02\x00\x02e\x95e\x80iX#\x01\x00\x00\x00\x00\x00\x80',
b'6FU0AD\x11\x02\x00\x02e\x95e\x80iQ#\x01\x00\x00\x00\x00\x00\x80',
],
diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py
index afc91f4755..b5429daef2 100644
--- a/selfdrive/car/subaru/carcontroller.py
+++ b/selfdrive/car/subaru/carcontroller.py
@@ -1,26 +1,33 @@
+from opendbc.can.packer import CANPacker
from selfdrive.car import apply_std_steer_torque_limits
from selfdrive.car.subaru import subarucan
-from selfdrive.car.subaru.values import DBC, PREGLOBAL_CARS, CarControllerParams
-from opendbc.can.packer import CANPacker
+from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CarControllerParams
-class CarController():
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
self.apply_steer_last = 0
- self.es_distance_cnt = -1
+ self.frame = 0
+
self.es_lkas_cnt = -1
+ self.es_distance_cnt = -1
+ self.es_dashstatus_cnt = -1
self.cruise_button_prev = 0
- self.steer_rate_limited = False
+ self.last_cancel_frame = 0
self.p = CarControllerParams(CP)
self.packer = CANPacker(DBC[CP.carFingerprint]['pt'])
- def update(self, c, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart):
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+ pcm_cancel_cmd = CC.cruiseControl.cancel
can_sends = []
# *** steering ***
- if (frame % self.p.STEER_STEP) == 0:
+ if (self.frame % self.p.STEER_STEP) == 0:
apply_steer = int(round(actuators.steer * self.p.STEER_MAX))
@@ -28,23 +35,22 @@ class CarController():
new_steer = int(round(apply_steer))
apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p)
- self.steer_rate_limited = new_steer != apply_steer
- if not c.active:
+ if not CC.latActive:
apply_steer = 0
- if CS.CP.carFingerprint in PREGLOBAL_CARS:
- can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP))
+ if self.CP.carFingerprint in PREGLOBAL_CARS:
+ can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer))
else:
- can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP))
+ can_sends.append(subarucan.create_steering_control(self.packer, apply_steer))
self.apply_steer_last = apply_steer
# *** alerts and pcm cancel ***
- if CS.CP.carFingerprint in PREGLOBAL_CARS:
- if self.es_distance_cnt != CS.es_distance_msg["Counter"]:
+ if self.CP.carFingerprint in PREGLOBAL_CARS:
+ if self.es_distance_cnt != CS.es_distance_msg["COUNTER"]:
# 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep
# disengage ACC when OP is disengaged
if pcm_cancel_cmd:
@@ -61,18 +67,26 @@ class CarController():
self.cruise_button_prev = cruise_button
can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg))
- self.es_distance_cnt = CS.es_distance_msg["Counter"]
+ self.es_distance_cnt = CS.es_distance_msg["COUNTER"]
else:
- if self.es_distance_cnt != CS.es_distance_msg["Counter"]:
- can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, pcm_cancel_cmd))
- self.es_distance_cnt = CS.es_distance_msg["Counter"]
+ if pcm_cancel_cmd and (self.frame - self.last_cancel_frame) > 0.2:
+ bus = 1 if self.CP.carFingerprint in GLOBAL_GEN2 else 0
+ can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, bus, pcm_cancel_cmd))
+ self.last_cancel_frame = self.frame
+
+ if self.es_dashstatus_cnt != CS.es_dashstatus_msg["COUNTER"]:
+ can_sends.append(subarucan.create_es_dashstatus(self.packer, CS.es_dashstatus_msg))
+ self.es_dashstatus_cnt = CS.es_dashstatus_msg["COUNTER"]
- if self.es_lkas_cnt != CS.es_lkas_msg["Counter"]:
- can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart))
- self.es_lkas_cnt = CS.es_lkas_msg["Counter"]
+ if self.es_lkas_cnt != CS.es_lkas_msg["COUNTER"]:
+ can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, CC.enabled, hud_control.visualAlert,
+ hud_control.leftLaneVisible, hud_control.rightLaneVisible,
+ hud_control.leftLaneDepart, hud_control.rightLaneDepart))
+ self.es_lkas_cnt = CS.es_lkas_msg["COUNTER"]
new_actuators = actuators.copy()
new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py
index 1eefb18584..ba873c48d7 100644
--- a/selfdrive/car/subaru/carstate.py
+++ b/selfdrive/car/subaru/carstate.py
@@ -1,10 +1,10 @@
import copy
from cereal import car
from opendbc.can.can_define import CANDefine
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
from selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
-from selfdrive.car.subaru.values import DBC, STEER_THRESHOLD, CAR, PREGLOBAL_CARS
+from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS
class CarState(CarStateBase):
@@ -13,7 +13,7 @@ class CarState(CarStateBase):
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
self.shifter_values = can_define.dv["Transmission"]["Gear"]
- def update(self, cp, cp_cam):
+ def update(self, cp, cp_cam, cp_body):
ret = car.CarState.new_message()
ret.gas = cp.vl["Throttle"]["Throttle_Pedal"] / 255.
@@ -21,22 +21,23 @@ class CarState(CarStateBase):
if self.car_fingerprint in PREGLOBAL_CARS:
ret.brakePressed = cp.vl["Brake_Pedal"]["Brake_Pedal"] > 2
else:
- ret.brakePressed = cp.vl["Brake_Status"]["Brake"] == 1
+ cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
+ ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1
+ cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
ret.wheelSpeeds = self.get_wheel_speeds(
- cp.vl["Wheel_Speeds"]["FL"],
- cp.vl["Wheel_Speeds"]["FR"],
- cp.vl["Wheel_Speeds"]["RL"],
- cp.vl["Wheel_Speeds"]["RR"],
+ cp_wheels.vl["Wheel_Speeds"]["FL"],
+ cp_wheels.vl["Wheel_Speeds"]["FR"],
+ cp_wheels.vl["Wheel_Speeds"]["RL"],
+ cp_wheels.vl["Wheel_Speeds"]["RR"],
)
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
- # Kalman filter, even though Subaru raw wheel speed is heaviliy filtered by default
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
- ret.standstill = ret.vEgoRaw < 0.01
+ ret.standstill = ret.vEgoRaw == 0
# continuous blinker signals for assisted lane change
- ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(
- 50, cp.vl["Dashlights"]["LEFT_BLINKER"], cp.vl["Dashlights"]["RIGHT_BLINKER"])
+ ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"],
+ cp.vl["Dashlights"]["RIGHT_BLINKER"])
if self.CP.enableBsm:
ret.leftBlindspot = (cp.vl["BSD_RCTA"]["L_ADJACENT"] == 1) or (cp.vl["BSD_RCTA"]["L_APPROACHING"] == 1)
@@ -47,10 +48,14 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = cp.vl["Steering_Torque"]["Steering_Angle"]
ret.steeringTorque = cp.vl["Steering_Torque"]["Steer_Torque_Sensor"]
- ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD[self.car_fingerprint]
+ ret.steeringTorqueEps = cp.vl["Steering_Torque"]["Steer_Torque_Output"]
- ret.cruiseState.enabled = cp.vl["CruiseControl"]["Cruise_Activated"] != 0
- ret.cruiseState.available = cp.vl["CruiseControl"]["Cruise_On"] != 0
+ steer_threshold = 75 if self.CP.carFingerprint in PREGLOBAL_CARS else 80
+ ret.steeringPressed = abs(ret.steeringTorque) > steer_threshold
+
+ cp_cruise = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp
+ ret.cruiseState.enabled = cp_cruise.vl["CruiseControl"]["Cruise_Activated"] != 0
+ ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0
ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS
if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \
@@ -62,37 +67,85 @@ class CarState(CarStateBase):
cp.vl["BodyInfo"]["DOOR_OPEN_RL"],
cp.vl["BodyInfo"]["DOOR_OPEN_FR"],
cp.vl["BodyInfo"]["DOOR_OPEN_FL"]])
- ret.steerError = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1
+ ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1
if self.car_fingerprint in PREGLOBAL_CARS:
self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"]
self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"]
else:
- ret.steerWarning = cp.vl["Steering_Torque"]["Steer_Warning"] == 1
+ ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1
ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1
+ ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3
+ ret.stockFcw = cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2
self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"])
- self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"])
+
+ cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam
+ self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"])
+ self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"])
return ret
+ @staticmethod
+ def get_common_global_signals():
+ signals = [
+ ("Cruise_On", "CruiseControl"),
+ ("Cruise_Activated", "CruiseControl"),
+ ("FL", "Wheel_Speeds"),
+ ("FR", "Wheel_Speeds"),
+ ("RL", "Wheel_Speeds"),
+ ("RR", "Wheel_Speeds"),
+ ("Brake", "Brake_Status"),
+ ]
+ checks = [
+ ("CruiseControl", 20),
+ ("Wheel_Speeds", 50),
+ ("Brake_Status", 50),
+ ]
+
+ return signals, checks
+
+ @staticmethod
+ def get_global_es_distance_signals():
+ signals = [
+ ("COUNTER", "ES_Distance"),
+ ("Signal1", "ES_Distance"),
+ ("Cruise_Fault", "ES_Distance"),
+ ("Cruise_Throttle", "ES_Distance"),
+ ("Signal2", "ES_Distance"),
+ ("Car_Follow", "ES_Distance"),
+ ("Signal3", "ES_Distance"),
+ ("Cruise_Soft_Disable", "ES_Distance"),
+ ("Signal7", "ES_Distance"),
+ ("Cruise_Brake_Active", "ES_Distance"),
+ ("Distance_Swap", "ES_Distance"),
+ ("Cruise_EPB", "ES_Distance"),
+ ("Signal4", "ES_Distance"),
+ ("Close_Distance", "ES_Distance"),
+ ("Signal5", "ES_Distance"),
+ ("Cruise_Cancel", "ES_Distance"),
+ ("Cruise_Set", "ES_Distance"),
+ ("Cruise_Resume", "ES_Distance"),
+ ("Signal6", "ES_Distance"),
+ ]
+ checks = [
+ ("ES_Distance", 20),
+ ]
+
+ return signals, checks
+
@staticmethod
def get_can_parser(CP):
signals = [
# sig_name, sig_address
("Steer_Torque_Sensor", "Steering_Torque"),
+ ("Steer_Torque_Output", "Steering_Torque"),
("Steering_Angle", "Steering_Torque"),
("Steer_Error_1", "Steering_Torque"),
- ("Cruise_On", "CruiseControl"),
- ("Cruise_Activated", "CruiseControl"),
("Brake_Pedal", "Brake_Pedal"),
("Throttle_Pedal", "Throttle"),
("LEFT_BLINKER", "Dashlights"),
("RIGHT_BLINKER", "Dashlights"),
("SEATBELT_FL", "Dashlights"),
- ("FL", "Wheel_Speeds"),
- ("FR", "Wheel_Speeds"),
- ("RL", "Wheel_Speeds"),
- ("RR", "Wheel_Speeds"),
("DOOR_OPEN_FR", "BodyInfo"),
("DOOR_OPEN_FL", "BodyInfo"),
("DOOR_OPEN_RR", "BodyInfo"),
@@ -105,7 +158,6 @@ class CarState(CarStateBase):
("Throttle", 100),
("Dashlights", 10),
("Brake_Pedal", 50),
- ("Wheel_Speeds", 50),
("Transmission", 100),
("Steering_Torque", 50),
("BodyInfo", 1),
@@ -121,36 +173,47 @@ class CarState(CarStateBase):
checks.append(("BSD_RCTA", 17))
if CP.carFingerprint not in PREGLOBAL_CARS:
+ if CP.carFingerprint not in GLOBAL_GEN2:
+ signals += CarState.get_common_global_signals()[0]
+ checks += CarState.get_common_global_signals()[1]
+
signals += [
("Steer_Warning", "Steering_Torque"),
- ("Brake", "Brake_Status"),
("UNITS", "Dashlights"),
]
checks += [
("Dashlights", 10),
("BodyInfo", 10),
- ("Brake_Status", 50),
- ("CruiseControl", 20),
]
else:
- signals.append(("UNITS", "Dash_State2"))
-
- checks.append(("Dash_State2", 1))
-
- if CP.carFingerprint == CAR.FORESTER_PREGLOBAL:
- checks += [
- ("Dashlights", 20),
- ("BodyInfo", 1),
- ("CruiseControl", 50),
+ signals += [
+ ("FL", "Wheel_Speeds"),
+ ("FR", "Wheel_Speeds"),
+ ("RL", "Wheel_Speeds"),
+ ("RR", "Wheel_Speeds"),
+ ("UNITS", "Dash_State2"),
+ ("Cruise_On", "CruiseControl"),
+ ("Cruise_Activated", "CruiseControl"),
]
-
- if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
checks += [
- ("Dashlights", 10),
- ("CruiseControl", 50),
+ ("Wheel_Speeds", 50),
+ ("Dash_State2", 1),
]
+ if CP.carFingerprint == CAR.FORESTER_PREGLOBAL:
+ checks += [
+ ("Dashlights", 20),
+ ("BodyInfo", 1),
+ ("CruiseControl", 50),
+ ]
+
+ if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
+ checks += [
+ ("Dashlights", 10),
+ ("CruiseControl", 50),
+ ]
+
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)
@staticmethod
@@ -173,7 +236,7 @@ class CarState(CarStateBase):
("Standstill_2", "ES_Distance"),
("Cruise_Fault", "ES_Distance"),
("Signal5", "ES_Distance"),
- ("Counter", "ES_Distance"),
+ ("COUNTER", "ES_Distance"),
("Signal6", "ES_Distance"),
("Cruise_Button", "ES_Distance"),
("Signal7", "ES_Distance"),
@@ -185,28 +248,34 @@ class CarState(CarStateBase):
]
else:
signals = [
- ("Cruise_Set_Speed", "ES_DashStatus"),
+ ("Counter", "ES_DashStatus"),
+ ("PCB_Off", "ES_DashStatus"),
+ ("LDW_Off", "ES_DashStatus"),
+ ("Signal1", "ES_DashStatus"),
+ ("Cruise_State_Msg", "ES_DashStatus"),
+ ("LKAS_State_Msg", "ES_DashStatus"),
+ ("Signal2", "ES_DashStatus"),
+ ("Cruise_Soft_Disable", "ES_DashStatus"),
+ ("EyeSight_Status_Msg", "ES_DashStatus"),
+ ("Signal3", "ES_DashStatus"),
+ ("Cruise_Distance", "ES_DashStatus"),
+ ("Signal4", "ES_DashStatus"),
("Conventional_Cruise", "ES_DashStatus"),
+ ("Signal5", "ES_DashStatus"),
+ ("Cruise_Disengaged", "ES_DashStatus"),
+ ("Cruise_Activated", "ES_DashStatus"),
+ ("Signal6", "ES_DashStatus"),
+ ("Cruise_Set_Speed", "ES_DashStatus"),
+ ("Cruise_Fault", "ES_DashStatus"),
+ ("Cruise_On", "ES_DashStatus"),
+ ("Display_Own_Car", "ES_DashStatus"),
+ ("Brake_Lights", "ES_DashStatus"),
+ ("Car_Follow", "ES_DashStatus"),
+ ("Signal7", "ES_DashStatus"),
+ ("Far_Distance", "ES_DashStatus"),
+ ("Cruise_State", "ES_DashStatus"),
- ("Counter", "ES_Distance"),
- ("Signal1", "ES_Distance"),
- ("Cruise_Fault", "ES_Distance"),
- ("Cruise_Throttle", "ES_Distance"),
- ("Signal2", "ES_Distance"),
- ("Car_Follow", "ES_Distance"),
- ("Signal3", "ES_Distance"),
- ("Cruise_Brake_Active", "ES_Distance"),
- ("Distance_Swap", "ES_Distance"),
- ("Cruise_EPB", "ES_Distance"),
- ("Signal4", "ES_Distance"),
- ("Close_Distance", "ES_Distance"),
- ("Signal5", "ES_Distance"),
- ("Cruise_Cancel", "ES_Distance"),
- ("Cruise_Set", "ES_Distance"),
- ("Cruise_Resume", "ES_Distance"),
- ("Signal6", "ES_Distance"),
-
- ("Counter", "ES_LKAS_State"),
+ ("COUNTER", "ES_LKAS_State"),
("LKAS_Alert_Msg", "ES_LKAS_State"),
("Signal1", "ES_LKAS_State"),
("LKAS_ACTIVE", "ES_LKAS_State"),
@@ -225,8 +294,21 @@ class CarState(CarStateBase):
checks = [
("ES_DashStatus", 10),
- ("ES_Distance", 20),
("ES_LKAS_State", 10),
]
+ if CP.carFingerprint not in GLOBAL_GEN2:
+ signals += CarState.get_global_es_distance_signals()[0]
+ checks += CarState.get_global_es_distance_signals()[1]
+
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
+
+ @staticmethod
+ def get_body_can_parser(CP):
+ if CP.carFingerprint in GLOBAL_GEN2:
+ signals, checks = CarState.get_common_global_signals()
+ signals += CarState.get_global_es_distance_signals()[0]
+ checks += CarState.get_global_es_distance_signals()[1]
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 1)
+
+ return None
diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py
index 8c6d188643..22468801ec 100644
--- a/selfdrive/car/subaru/interface.py
+++ b/selfdrive/car/subaru/interface.py
@@ -1,29 +1,32 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.car.subaru.values import CAR, PREGLOBAL_CARS
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from panda import Panda
+from selfdrive.car import STD_CARGO_KG, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
+from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS
+
class CarInterface(CarInterfaceBase):
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
-
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "subaru"
ret.radarOffCan = True
+ ret.dashcamOnly = candidate in PREGLOBAL_CARS
+ ret.autoResumeSng = False
if candidate in PREGLOBAL_CARS:
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruLegacy)]
ret.enableBsm = 0x25c in fingerprint[0]
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaruLegacy)]
else:
- ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)]
ret.enableBsm = 0x228 in fingerprint[0]
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.subaru)]
+ if candidate in GLOBAL_GEN2:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_SUBARU_GEN2
- ret.dashcamOnly = candidate in PREGLOBAL_CARS
-
- ret.steerRateCost = 0.7
ret.steerLimitTimer = 0.4
+ ret.steerActuatorDelay = 0.1
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
if candidate == CAR.ASCENT:
ret.mass = 2031. + STD_CARGO_KG
@@ -31,100 +34,83 @@ class CarInterface(CarInterfaceBase):
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 13.5
ret.steerActuatorDelay = 0.3 # end-to-end angle controller
+ ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00003
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]]
- if candidate == CAR.IMPREZA:
+ elif candidate == CAR.IMPREZA:
ret.mass = 1568. + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 15
ret.steerActuatorDelay = 0.4 # end-to-end angle controller
+ ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]]
- if candidate == CAR.IMPREZA_2020:
+ elif candidate == CAR.IMPREZA_2020:
ret.mass = 1480. + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock
- ret.steerActuatorDelay = 0.1
+ ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]]
- if candidate == CAR.FORESTER:
+ elif candidate == CAR.FORESTER:
ret.mass = 1568. + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock
- ret.steerActuatorDelay = 0.1
+ ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kf = 0.000038
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]]
- if candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
+ elif candidate in (CAR.OUTBACK, CAR.LEGACY):
+ ret.mass = 1568. + STD_CARGO_KG
+ ret.wheelbase = 2.67
+ ret.centerToFront = ret.wheelbase * 0.5
+ ret.steerRatio = 17
+ ret.steerActuatorDelay = 0.1
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
+ elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018):
ret.safetyConfigs[0].safetyParam = 1 # Outback 2018-2019 and Forester have reversed driver torque signal
ret.mass = 1568 + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 20 # learned, 14 stock
- ret.steerActuatorDelay = 0.1
- ret.lateralTuning.pid.kf = 0.000039
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 10., 20.], [0., 10., 20.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.05, 0.2], [0.003, 0.018, 0.025]]
- if candidate == CAR.LEGACY_PREGLOBAL:
+ elif candidate == CAR.LEGACY_PREGLOBAL:
ret.mass = 1568 + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 12.5 # 14.5 stock
ret.steerActuatorDelay = 0.15
- ret.lateralTuning.pid.kf = 0.00005
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.1, 0.2], [0.01, 0.02]]
- if candidate == CAR.OUTBACK_PREGLOBAL:
+ elif candidate == CAR.OUTBACK_PREGLOBAL:
ret.mass = 1568 + STD_CARGO_KG
ret.wheelbase = 2.67
ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 20 # learned, 14 stock
- ret.steerActuatorDelay = 0.1
- ret.lateralTuning.pid.kf = 0.000039
- ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 10., 20.], [0., 10., 20.]]
- ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.05, 0.2], [0.003, 0.018, 0.025]]
-
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
- # TODO: start from empirically derived lateral slip stiffness for the civic and scale by
- # mass and CG position, so all cars will have approximately similar dyn behaviors
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront)
+ else:
+ raise ValueError(f"unknown car: {candidate}")
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
+ def _update(self, c):
- ret = self.CS.update(self.cp, self.cp_cam)
-
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
+ ret = self.CS.update(self.cp, self.cp_cam, self.cp_body)
ret.events = self.create_common_events(ret).to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- hud_control = c.hudControl
- ret = self.CC.update(c, c.enabled, self.CS, self.frame, c.actuators,
- c.cruiseControl.cancel, hud_control.visualAlert,
- hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py
index 86ec5e8bd4..d83b639a41 100644
--- a/selfdrive/car/subaru/subarucan.py
+++ b/selfdrive/car/subaru/subarucan.py
@@ -3,29 +3,23 @@ from cereal import car
VisualAlert = car.CarControl.HUDControl.VisualAlert
-def create_steering_control(packer, apply_steer, frame, steer_step):
-
- idx = (frame / steer_step) % 16
-
+def create_steering_control(packer, apply_steer):
values = {
- "Counter": idx,
"LKAS_Output": apply_steer,
"LKAS_Request": 1 if apply_steer != 0 else 0,
"SET_1": 1
}
-
return packer.make_can_msg("ES_LKAS", 0, values)
-def create_steering_status(packer, apply_steer, frame, steer_step):
+def create_steering_status(packer):
return packer.make_can_msg("ES_LKAS_State", 0, {})
-def create_es_distance(packer, es_distance_msg, pcm_cancel_cmd):
-
+def create_es_distance(packer, es_distance_msg, bus, pcm_cancel_cmd):
values = copy.copy(es_distance_msg)
+ values["COUNTER"] = (values["COUNTER"] + 1) % 0x10
if pcm_cancel_cmd:
values["Cruise_Cancel"] = 1
-
- return packer.make_can_msg("ES_Distance", 0, values)
+ return packer.make_can_msg("ES_Distance", bus, values)
def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart):
@@ -39,6 +33,18 @@ def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_
if values["LKAS_Alert"] == 27:
values["LKAS_Alert"] = 0
+ # Filter the stock LKAS sending an audible alert when "Keep hands on wheel" alert is active (2020+ models)
+ if values["LKAS_Alert"] == 28 and values["LKAS_Alert_Msg"] == 7:
+ values["LKAS_Alert"] = 0
+
+ # Filter the stock LKAS sending an audible alert when "Keep hands on wheel OFF" alert is active (2020+ models)
+ if values["LKAS_Alert"] == 30:
+ values["LKAS_Alert"] = 0
+
+ # Filter the stock LKAS sending "Keep hands on wheel OFF" alert (2020+ models)
+ if values["LKAS_Alert_Msg"] == 7:
+ values["LKAS_Alert_Msg"] = 0
+
# Show Keep hands on wheel alert for openpilot steerRequired alert
if visual_alert == VisualAlert.steerRequired:
values["LKAS_Alert_Msg"] = 1
@@ -54,25 +60,30 @@ def create_es_lkas(packer, es_lkas_msg, enabled, visual_alert, left_line, right_
values["LKAS_ACTIVE"] = 1 # Show LKAS lane lines
values["LKAS_Dash_State"] = 2 # Green enabled indicator
else:
- values["LKAS_Dash_State"] = 0 # LKAS Not enabled
+ values["LKAS_Dash_State"] = 0 # LKAS Not enabled
values["LKAS_Left_Line_Visible"] = int(left_line)
values["LKAS_Right_Line_Visible"] = int(right_line)
return packer.make_can_msg("ES_LKAS_State", 0, values)
+def create_es_dashstatus(packer, dashstatus_msg):
+ values = copy.copy(dashstatus_msg)
+
+ # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts
+ if values["LKAS_State_Msg"] in [2, 3]:
+ values["LKAS_State_Msg"] = 0
+
+ return packer.make_can_msg("ES_DashStatus", 0, values)
+
# *** Subaru Pre-global ***
def subaru_preglobal_checksum(packer, values, addr):
dat = packer.make_can_msg(addr, 0, values)[2]
return (sum(dat[:7])) % 256
-def create_preglobal_steering_control(packer, apply_steer, frame, steer_step):
-
- idx = (frame / steer_step) % 8
-
+def create_preglobal_steering_control(packer, apply_steer):
values = {
- "Counter": idx,
"LKAS_Command": apply_steer,
"LKAS_Active": 1 if apply_steer != 0 else 0
}
diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py
index 5c25bc10b4..6ac2637fa2 100644
--- a/selfdrive/car/subaru/values.py
+++ b/selfdrive/car/subaru/values.py
@@ -1,48 +1,95 @@
-from selfdrive.car import dbc_dict
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Union
+
from cereal import car
+from panda.python import uds
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
+
Ecu = car.CarParams.Ecu
+
class CarControllerParams:
def __init__(self, CP):
- if CP.carFingerprint == CAR.IMPREZA_2020:
- self.STEER_MAX = 1439
- else:
- self.STEER_MAX = 2047
self.STEER_STEP = 2 # how often we update the steer cmd
self.STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max
self.STEER_DELTA_DOWN = 70 # torque decrease per refresh
self.STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting
- self.STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily
+ self.STEER_DRIVER_MULTIPLIER = 50 # weight driver torque heavily
self.STEER_DRIVER_FACTOR = 1 # from dbc
+ if CP.carFingerprint in GLOBAL_GEN2:
+ self.STEER_MAX = 1000
+ self.STEER_DELTA_UP = 40
+ self.STEER_DELTA_DOWN = 40
+ elif CP.carFingerprint == CAR.IMPREZA_2020:
+ self.STEER_MAX = 1439
+ else:
+ self.STEER_MAX = 2047
+
+
class CAR:
+ # Global platform
ASCENT = "SUBARU ASCENT LIMITED 2019"
IMPREZA = "SUBARU IMPREZA LIMITED 2019"
IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020"
FORESTER = "SUBARU FORESTER 2019"
+ OUTBACK = "SUBARU OUTBACK 6TH GEN"
+ LEGACY = "SUBARU LEGACY 7TH GEN"
+
+ # Pre-global
FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018"
LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018"
OUTBACK_PREGLOBAL = "SUBARU OUTBACK 2015 - 2017"
OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019"
-FINGERPRINTS = {
- CAR.IMPREZA: [{
- 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 827: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8
- }],
- CAR.IMPREZA_2020: [{
- 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1617: 8, 1632: 8, 1650: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8, 1968: 8, 1976: 8, 2015: 8, 2016: 8, 2024: 8
- },
- {
- 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 544: 8, 545: 8, 546: 8, 554: 8, 557: 8, 576: 8, 577: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8
- }],
- CAR.FORESTER: [{
- 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 961: 8, 984: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1651: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1698: 8, 1722: 8, 1743: 8, 1759: 8, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8
- }],
+
+@dataclass
+class SubaruCarInfo(CarInfo):
+ package: str = "EyeSight Driver Assistance"
+ harness: Enum = Harness.subaru_a
+
+
+CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = {
+ CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"),
+ CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", harness=Harness.subaru_b),
+ CAR.LEGACY: SubaruCarInfo("Subaru Legacy 2020-22", "All", harness=Harness.subaru_b),
+ CAR.IMPREZA: [
+ SubaruCarInfo("Subaru Impreza 2017-19"),
+ SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
+ SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
+ ],
+ CAR.IMPREZA_2020: [
+ SubaruCarInfo("Subaru Impreza 2020-22"),
+ SubaruCarInfo("Subaru Crosstrek 2020-23"),
+ SubaruCarInfo("Subaru XV 2020-21"),
+ ],
+ CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"),
+ CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"),
+ CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
+ CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),
+ CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"),
}
+SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
+SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
+ ),
+ ],
+)
+
FW_VERSIONS = {
CAR.ASCENT: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\xa5 \x19\x02\x00',
b'\xa5 !\002\000',
b'\xf1\x82\xa5 \x19\x02\x00',
@@ -56,6 +103,8 @@ FW_VERSIONS = {
b'\x00\x00d\xb9\x1f@ \x10',
b'\000\000e~\037@ \'',
b'\x00\x00e@\x1f@ $',
+ b'\x00\x00d\xb9\x00\x00\x00\x00',
+ b'\x00\x00e@\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\xbb,\xa0t\a',
@@ -63,32 +112,64 @@ FW_VERSIONS = {
b'\xf1\x82\xbb,\xa0t\a',
b'\xf1\x82\xd9,\xa0@\a',
b'\xf1\x82\xd1,\xa0q\x07',
+ b'\xd1,\xa0q\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\x00\xfe\xf7\x00\x00',
b'\001\xfe\xf9\000\000',
b'\x01\xfe\xf7\x00\x00',
+ b'\x01\xfe\xfa\x00\x00',
+ ],
+ },
+ CAR.LEGACY: {
+ (Ecu.abs, 0x7b0, None): [
+ b'\xa1\\ x04\x01',
+ b'\xa1 \x03\x03',
+ b'\xa1 \x02\x01',
+ ],
+ (Ecu.eps, 0x746, None): [
+ b'\x9b\xc0\x11\x00',
+ b'\x9b\xc0\x11\x02',
+ ],
+ (Ecu.fwdCamera, 0x787, None): [
+ b'\x00\x00e\x80\x00\x1f@ \x19\x00',
+ b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xde\"a0\x07',
+ b'\xe2"aq\x07',
+ b'\xde,\xa0@\x07',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xa5\xf6\x05@\x00',
+ b'\xa7\xf6\x04@\x00',
+ b'\xa5\xfe\xc7@\x00',
],
},
CAR.IMPREZA: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x7a\x94\x3f\x90\x00',
b'\xa2 \x185\x00',
b'\xa2 \x193\x00',
+ b'\xa2 \x194\x00',
b'z\x94.\x90\x00',
b'z\x94\b\x90\x01',
b'\xa2 \x19`\x00',
b'z\x94\f\x90\001',
b'z\x9c\x19\x80\x01',
b'z\x94\x08\x90\x00',
+ b'z\x84\x19\x90\x00',
+ b'\xf1\x00\xb2\x06\x04',
+ b'z\x94\x0c\x90\x00',
],
(Ecu.eps, 0x746, None): [
b'\x7a\xc0\x0c\x00',
- b'z\xc0\b\x00',
+ b'z\xc0\x08\x00',
b'\x8a\xc0\x00\x00',
b'z\xc0\x04\x00',
b'z\xc0\x00\x00',
b'\x8a\xc0\x10\x00',
+ b'z\xc0\n\x00',
],
(Ecu.fwdCamera, 0x787, None): [
b'\x00\x00\x64\xb5\x1f\x40\x20\x0e',
@@ -101,6 +182,9 @@ FW_VERSIONS = {
b'\000\000e\002\037@ \024',
b'\x00\x00d)\x00\x00\x00\x00',
b'\x00\x00c\xf4\x00\x00\x00\x00',
+ b'\x00\x00d\xdc\x00\x00\x00\x00',
+ b'\x00\x00dd\x00\x00\x00\x00',
+ b'\x00\x00c\xf4\x1f@ \x07',
],
(Ecu.engine, 0x7e0, None): [
b'\xaa\x61\x66\x73\x07',
@@ -110,9 +194,9 @@ FW_VERSIONS = {
b'\xaa!`u\a',
b'\xaa!dq\a',
b'\xaa!dt\a',
- b'\xf1\x00\xa2\x10\t',
b'\xc5!ar\a',
b'\xbe!as\a',
+ b'\xc5!as\x07',
b'\xc5!ds\a',
b'\xc5!`s\a',
b'\xaa!au\a',
@@ -120,6 +204,7 @@ FW_VERSIONS = {
b'\xaa\x00Bu\x07',
b'\xc5!dr\x07',
b'\xaa!aw\x07',
+ b'\xaa!av\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xe3\xe5\x46\x31\x00',
@@ -135,23 +220,29 @@ FW_VERSIONS = {
b'\xe4\xf5\002\000\000',
b'\xe3\xd0\x081\x00',
b'\xe3\xf5\x06\x00\x00',
- b'\xf1\x00\xa4\x10@',
],
},
CAR.IMPREZA_2020: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\xa2 \0314\000',
b'\xa2 \0313\000',
b'\xa2 !i\000',
b'\xa2 !`\000',
+ b'\xf1\x00\xb2\x06\x04',
+ b'\xa2 `\x00',
],
(Ecu.eps, 0x746, None): [
b'\x9a\xc0\000\000',
b'\n\xc0\004\000',
+ b'\x9a\xc0\x04\x00',
+ b'\n\xc0\x04\x01',
],
(Ecu.fwdCamera, 0x787, None): [
b'\000\000eb\037@ \"',
b'\000\000e\x8f\037@ )',
+ b'\x00\x00eq\x1f@ "',
+ b'\x00\x00eq\x00\x00\x00\x00',
+ b'\x00\x00e\x8f\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\xca!ap\a',
@@ -159,47 +250,57 @@ FW_VERSIONS = {
b'\xca!`0\a',
b'\xcc\"f0\a',
b'\xcc!fp\a',
+ b'\xca!f@\x07',
+ b'\xca!fp\x07',
+ b'\xf3"f@\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\xe6\xf5\004\000\000',
b'\xe6\xf5$\000\000',
b'\xe7\xf6B0\000',
b'\xe7\xf5D0\000',
+ b'\xf1\x00\xd7\x10@',
+ b'\xe6\xf5D0\x00',
+ b'\xe9\xf6F0\x00',
],
},
CAR.FORESTER: {
- (Ecu.esp, 0x7b0, None): [
- b'\xa3 \030\024\000',
+ (Ecu.abs, 0x7b0, None): [
+ b'\xa3 \x18\x14\x00',
b'\xa3 \024\000',
b'\xa3 \031\024\000',
- b'\xa3 \024\001',
+ b'\xa3 \x14\x01',
+ b'\xf1\x00\xbb\r\x05',
],
(Ecu.eps, 0x746, None): [
- b'\x8d\xc0\004\000',
+ b'\x8d\xc0\x04\x00',
],
(Ecu.fwdCamera, 0x787, None): [
- b'\000\000e!\037@ \021',
- b'\000\000e\x97\037@ 0',
+ b'\x00\x00e!\x1f@ \x11',
+ b'\x00\x00e\x97\x1f@ 0',
b'\000\000e`\037@ ',
b'\xf1\x00\xac\x02\x00',
+ b'\x00\x00e!\x00\x00\x00\x00',
+ b'\x00\x00e\x97\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
- b'\xb6\"`A\a',
- b'\xcf"`0\a',
+ b'\xb6"`A\x07',
+ b'\xcf"`0\x07',
b'\xcb\"`@\a',
b'\xcb\"`p\a',
b'\xf1\x00\xa2\x10\n',
+ b'\xcf"`p\x07',
],
(Ecu.transmission, 0x7e1, None): [
b'\032\xf6B0\000',
- b'\032\xf6F`\000',
+ b'\x1a\xf6F`\x00',
b'\032\xf6b`\000',
- b'\032\xf6B`\000',
- b'\xf1\x00\xa4\x10@',
+ b'\x1a\xf6B`\x00',
+ b'\x1a\xf6b0\x00',
],
},
CAR.FORESTER_PREGLOBAL: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x7d\x97\x14\x40',
b'\xf1\x00\xbb\x0c\x04',
],
@@ -224,10 +325,11 @@ FW_VERSIONS = {
b'\xda\xfd\xe0\x80\x00',
b'\xdc\xf2`\x81\000',
b'\xdc\xf2`\x80\x00',
+ b'\x1a\xf6F`\x00',
],
},
CAR.LEGACY_PREGLOBAL: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'k\x97D\x00',
b'[\xba\xc4\x03',
b'{\x97D\x00',
@@ -257,7 +359,7 @@ FW_VERSIONS = {
],
},
CAR.OUTBACK_PREGLOBAL: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'{\x9a\xac\x00',
b'k\x97\xac\x00',
b'\x5b\xf7\xbc\x03',
@@ -311,7 +413,7 @@ FW_VERSIONS = {
],
},
CAR.OUTBACK_PREGLOBAL_2018: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x8b\x97\xac\x00',
b'\x8b\x9a\xac\x00',
b'\x9b\x97\xac\x00',
@@ -353,17 +455,49 @@ FW_VERSIONS = {
b'\xbb\xfb\xe0`\000',
],
},
-}
-
-STEER_THRESHOLD = {
- CAR.ASCENT: 80,
- CAR.IMPREZA: 80,
- CAR.IMPREZA_2020: 80,
- CAR.FORESTER: 80,
- CAR.FORESTER_PREGLOBAL: 75,
- CAR.LEGACY_PREGLOBAL: 75,
- CAR.OUTBACK_PREGLOBAL: 75,
- CAR.OUTBACK_PREGLOBAL_2018: 75,
+ CAR.OUTBACK: {
+ (Ecu.abs, 0x7b0, None): [
+ b'\xa1 \x06\x01',
+ b'\xa1 \a\x00',
+ b'\xa1 \b\001',
+ b'\xa1 \x06\x00',
+ b'\xa1 "\t\x01',
+ b'\xa1 \x08\x02',
+ b'\xa1 \x06\x02',
+ b'\xa1 \x08\x00',
+ b'\xa1 "\t\x00',
+ ],
+ (Ecu.eps, 0x746, None): [
+ b'\x9b\xc0\x10\x00',
+ b'\x9b\xc0\x20\x00',
+ b'\x1b\xc0\x10\x00',
+ ],
+ (Ecu.fwdCamera, 0x787, None): [
+ b'\x00\x00eJ\x00\x1f@ \x19\x00',
+ b'\000\000e\x80\000\037@ \031\000',
+ b'\x00\x00e\x9a\x00\x1f@ 1\x00',
+ b'\x00\x00eJ\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x7e0, None): [
+ b'\xbc,\xa0q\x07',
+ b'\xbc\"`@\a',
+ b'\xde"`0\a',
+ b'\xf1\x82\xbc,\xa0q\a',
+ b'\xf1\x82\xe3,\xa0@\x07',
+ b'\xe2"`p\x07',
+ b'\xf1\x82\xe2,\xa0@\x07',
+ b'\xbc"`q\x07',
+ b'\xe3,\xa0@\x07',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xa5\xfe\xf7@\x00',
+ b'\xa5\xf6D@\x00',
+ b'\xa5\xfe\xf6@\x00',
+ b'\xa7\x8e\xf40\x00',
+ b'\xf1\x82\xa7\xf6D@\x00',
+ b'\xa7\xfe\xf4@\x00',
+ ],
+ },
}
DBC = {
@@ -371,10 +505,13 @@ DBC = {
CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None),
CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None),
+ CAR.OUTBACK: dbc_dict('subaru_global_2017_generated', None),
+ CAR.LEGACY: dbc_dict('subaru_global_2017_generated', None),
CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None),
CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None),
CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None),
}
-PREGLOBAL_CARS = [CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018]
+GLOBAL_GEN2 = (CAR.OUTBACK, CAR.LEGACY)
+PREGLOBAL_CARS = (CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018)
diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py
index 03f09f2407..cf43b8ef00 100644
--- a/selfdrive/car/tesla/carcontroller.py
+++ b/selfdrive/car/tesla/carcontroller.py
@@ -3,66 +3,67 @@ from opendbc.can.packer import CANPacker
from selfdrive.car.tesla.teslacan import TeslaCAN
from selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams
-class CarController():
+
+class CarController:
def __init__(self, dbc_name, CP, VM):
self.CP = CP
+ self.frame = 0
self.last_angle = 0
- self.long_control_counter = 0
self.packer = CANPacker(dbc_name)
self.pt_packer = CANPacker(DBC[CP.carFingerprint]['pt'])
self.tesla_can = TeslaCAN(self.packer, self.pt_packer)
- def update(self, c, enabled, CS, frame, actuators, cruise_cancel):
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ pcm_cancel_cmd = CC.cruiseControl.cancel
+
can_sends = []
# Temp disable steering on a hands_on_fault, and allow for user override
- hands_on_fault = (CS.steer_warning == "EAC_ERROR_HANDS_ON" and CS.hands_on_level >= 3)
- lkas_enabled = c.active and (not hands_on_fault)
+ hands_on_fault = CS.steer_warning == "EAC_ERROR_HANDS_ON" and CS.hands_on_level >= 3
+ lkas_enabled = CC.latActive and not hands_on_fault
if lkas_enabled:
apply_angle = actuators.steeringAngleDeg
# Angular rate limit based on speed
- steer_up = (self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle))
+ steer_up = self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle)
rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN
max_angle_diff = interp(CS.out.vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points)
- apply_angle = clip(apply_angle, (self.last_angle - max_angle_diff), (self.last_angle + max_angle_diff))
+ apply_angle = clip(apply_angle, self.last_angle - max_angle_diff, self.last_angle + max_angle_diff)
# To not fault the EPS
- apply_angle = clip(apply_angle, (CS.out.steeringAngleDeg - 20), (CS.out.steeringAngleDeg + 20))
+ apply_angle = clip(apply_angle, CS.out.steeringAngleDeg - 20, CS.out.steeringAngleDeg + 20)
else:
apply_angle = CS.out.steeringAngleDeg
self.last_angle = apply_angle
- can_sends.append(self.tesla_can.create_steering_control(apply_angle, lkas_enabled, frame))
+ can_sends.append(self.tesla_can.create_steering_control(apply_angle, lkas_enabled, self.frame))
- # Longitudinal control (40Hz)
- if self.CP.openpilotLongitudinalControl and ((frame % 5) in (0, 2)):
+ # Longitudinal control (in sync with stock message, about 40Hz)
+ if self.CP.openpilotLongitudinalControl:
target_accel = actuators.accel
target_speed = max(CS.out.vEgo + (target_accel * CarControllerParams.ACCEL_TO_SPEED_MULTIPLIER), 0)
max_accel = 0 if target_accel < 0 else target_accel
min_accel = 0 if target_accel > 0 else target_accel
- can_sends.extend(self.tesla_can.create_longitudinal_commands(CS.acc_state, target_speed, min_accel, max_accel, self.long_control_counter))
- self.long_control_counter += 1
+ while len(CS.das_control_counters) > 0:
+ can_sends.extend(self.tesla_can.create_longitudinal_commands(CS.acc_state, target_speed, min_accel, max_accel, CS.das_control_counters.popleft()))
# Cancel on user steering override, since there is no steering torque blending
if hands_on_fault:
- cruise_cancel = True
-
- # Cancel when openpilot is not enabled anymore
- if not enabled and bool(CS.out.cruiseState.enabled):
- cruise_cancel = True
+ pcm_cancel_cmd = True
- if ((frame % 10) == 0 and cruise_cancel):
+ if self.frame % 10 == 0 and pcm_cancel_cmd:
# Spam every possible counter value, otherwise it might not be accepted
for counter in range(16):
- can_sends.append(self.tesla_can.create_action_request(CS.msg_stw_actn_req, cruise_cancel, CANBUS.chassis, counter))
- can_sends.append(self.tesla_can.create_action_request(CS.msg_stw_actn_req, cruise_cancel, CANBUS.autopilot_chassis, counter))
+ can_sends.append(self.tesla_can.create_action_request(CS.msg_stw_actn_req, pcm_cancel_cmd, CANBUS.chassis, counter))
+ can_sends.append(self.tesla_can.create_action_request(CS.msg_stw_actn_req, pcm_cancel_cmd, CANBUS.autopilot_chassis, counter))
# TODO: HUD control
new_actuators = actuators.copy()
new_actuators.steeringAngleDeg = apply_angle
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/tesla/carstate.py b/selfdrive/car/tesla/carstate.py
index 51ae43ad1b..0f373842f2 100644
--- a/selfdrive/car/tesla/carstate.py
+++ b/selfdrive/car/tesla/carstate.py
@@ -1,10 +1,11 @@
import copy
+from collections import deque
from cereal import car
+from common.conversions import Conversions as CV
from selfdrive.car.tesla.values import DBC, CANBUS, GEAR_MAP, DOORS, BUTTONS
from selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
from opendbc.can.can_define import CANDefine
-from selfdrive.config import Conversions as CV
class CarState(CarStateBase):
def __init__(self, CP):
@@ -17,6 +18,7 @@ class CarState(CarStateBase):
self.hands_on_level = 0
self.steer_warning = None
self.acc_state = 0
+ self.das_control_counters = deque(maxlen=32)
def update(self, cp, cp_cam):
ret = car.CarState.new_message()
@@ -43,8 +45,8 @@ class CarState(CarStateBase):
ret.steeringRateDeg = -cp.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"] # This is from a different angle sensor, and at different rate
ret.steeringTorque = -cp.vl["EPAS_sysStatus"]["EPAS_torsionBarTorque"]
ret.steeringPressed = (self.hands_on_level > 0)
- ret.steerError = steer_status == "EAC_FAULT"
- ret.steerWarning = self.steer_warning != "EAC_ERROR_IDLE"
+ ret.steerFaultPermanent = steer_status == "EAC_FAULT"
+ ret.steerFaultTemporary = (self.steer_warning not in ("EAC_ERROR_IDLE", "EAC_ERROR_HANDS_ON"))
# Cruise state
cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp.vl["DI_state"]["DI_cruiseState"]), None)
@@ -87,9 +89,13 @@ class CarState(CarStateBase):
# TODO: blindspot
+ # AEB
+ ret.stockAeb = (cp_cam.vl["DAS_control"]["DAS_aebEvent"] == 1)
+
# Messages needed by carcontroller
self.msg_stw_actn_req = copy.copy(cp.vl["STW_ACTN_RQ"])
self.acc_state = cp_cam.vl["DAS_control"]["DAS_accState"]
+ self.das_control_counters.extend(cp_cam.vl_all["DAS_control"]["DAS_controlCounter"])
return ret
@@ -177,6 +183,8 @@ class CarState(CarStateBase):
signals = [
# sig_name, sig_address
("DAS_accState", "DAS_control"),
+ ("DAS_aebEvent", "DAS_control"),
+ ("DAS_controlCounter", "DAS_control"),
]
checks = [
# sig_address, frequency
diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py
index 03012bc52e..49e06d8923 100755
--- a/selfdrive/car/tesla/interface.py
+++ b/selfdrive/car/tesla/interface.py
@@ -2,14 +2,13 @@
from cereal import car
from panda import Panda
from selfdrive.car.tesla.values import CANBUS, CAR
-from selfdrive.car import STD_CARGO_KG, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, get_safety_config
+from selfdrive.car import STD_CARGO_KG, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
class CarInterface(CarInterfaceBase):
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "tesla"
# There is no safe way to do steer blending with user torque,
@@ -41,36 +40,24 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.tesla, 0)]
ret.steerLimitTimer = 1.0
- ret.steerActuatorDelay = 0.1
- ret.steerRateCost = 0.5
+ ret.steerActuatorDelay = 0.25
if candidate in (CAR.AP2_MODELS, CAR.AP1_MODELS):
ret.mass = 2100. + STD_CARGO_KG
ret.wheelbase = 2.959
ret.centerToFront = ret.wheelbase * 0.5
- ret.steerRatio = 13.5
+ ret.steerRatio = 15.0
else:
raise ValueError(f"Unsupported car: {candidate}")
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront)
-
return ret
- def update(self, c, can_strings):
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
- events = self.create_common_events(ret)
+ ret.events = self.create_common_events(ret).to_msg()
- ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- ret = self.CC.update(c, c.enabled, self.CS, self.frame, c.actuators, c.cruiseControl.cancel)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/tesla/teslacan.py b/selfdrive/car/tesla/teslacan.py
index 1301802860..e5d904f80e 100644
--- a/selfdrive/car/tesla/teslacan.py
+++ b/selfdrive/car/tesla/teslacan.py
@@ -1,6 +1,7 @@
import copy
import crcmod
-from selfdrive.config import Conversions as CV
+
+from common.conversions import Conversions as CV
from selfdrive.car.tesla.values import CANBUS, CarControllerParams
@@ -50,7 +51,7 @@ class TeslaCAN:
"DAS_jerkMax": CarControllerParams.JERK_LIMIT_MAX,
"DAS_accelMin": min_accel,
"DAS_accelMax": max_accel,
- "DAS_controlCounter": (cnt % 8),
+ "DAS_controlCounter": cnt,
"DAS_controlChecksum": 0,
}
diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py
index 616933789e..750fe885e8 100644
--- a/selfdrive/car/tesla/values.py
+++ b/selfdrive/car/tesla/values.py
@@ -1,20 +1,28 @@
from collections import namedtuple
-from selfdrive.car import dbc_dict
+from typing import Dict, List, Union
+
from cereal import car
+from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarInfo
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
+
+Ecu = car.CarParams.Ecu
Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points'])
+
class CAR:
AP1_MODELS = 'TESLA AP1 MODEL S'
AP2_MODELS = 'TESLA AP2 MODEL S'
+
+CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
+ CAR.AP1_MODELS: CarInfo("Tesla AP1 Model S", "All"),
+ CAR.AP2_MODELS: CarInfo("Tesla AP2 Model S", "All"),
+}
+
FINGERPRINTS = {
- CAR.AP2_MODELS: [
- {
- 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 518: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 583: 8, 584: 4, 585: 8, 590: 8, 601: 8, 606: 8, 608: 1, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 692: 8, 693: 8, 695: 8, 696: 8, 697: 8, 699: 8, 700: 8, 701: 8, 702: 8, 703: 8, 704: 8, 708: 8, 709: 8, 710: 8, 711: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 811: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 845: 8, 846: 5, 848: 8, 852: 8, 853: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 876: 8, 877: 8, 879: 8, 880: 8, 882: 8, 884: 8, 888: 8, 893: 8, 894: 8, 901: 6, 904: 3, 905: 8, 906: 8, 908: 2, 909: 8, 910: 8, 912: 8, 920: 8, 921: 8, 925: 4, 926: 6, 936: 8, 941: 8, 949: 8, 952: 8, 953: 6, 968: 8, 969: 6, 970: 8, 971: 8, 977: 8, 984: 8, 987: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1007: 8, 1008: 8, 1010: 6, 1014: 1, 1015: 8, 1016: 8, 1017: 8, 1018: 8, 1020: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1049: 8, 1061: 8, 1064: 8, 1065: 8, 1070: 8, 1080: 8, 1081: 8, 1097: 8, 1113: 8, 1129: 8, 1145: 8, 1160: 4, 1177: 8, 1281: 8, 1328: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1353: 8, 1368: 8, 1412: 8, 1436: 8, 1476: 8, 1481: 8, 1497: 8, 1513: 8, 1519: 8, 1601: 8, 1605: 8, 1617: 8, 1621: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1824: 8, 1828: 8, 1831: 8, 1832: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4
- },
- ],
CAR.AP1_MODELS: [
{
1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4
@@ -27,6 +35,42 @@ DBC = {
CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'),
}
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.eps],
+ rx_offset=0x08,
+ bus=0,
+ ),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.adas, Ecu.electricBrakeBooster, Ecu.fwdRadar],
+ rx_offset=0x10,
+ bus=0,
+ ),
+ ]
+)
+
+FW_VERSIONS = {
+ CAR.AP2_MODELS: {
+ (Ecu.adas, 0x649, None): [
+ b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11',
+ ],
+ (Ecu.electricBrakeBooster, 0x64d, None): [
+ b'1037123-00-A',
+ ],
+ (Ecu.fwdRadar, 0x671, None): [
+ b'\x01\x00W\x00\x00\x00\x07\x00\x00\x00\x00\x08\x01\x00\x00\x00\x07\xff\xfe',
+ ],
+ (Ecu.eps, 0x730, None): [
+ b'\x10#\x01',
+ ],
+ },
+}
+
class CANBUS:
# Lateral harness
chassis = 0
@@ -55,8 +99,8 @@ BUTTONS = [
Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]),
Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]),
Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]),
- Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]),
- Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]),
]
class CarControllerParams:
diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py
new file mode 100644
index 0000000000..89b895864a
--- /dev/null
+++ b/selfdrive/car/tests/routes.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+from collections import namedtuple
+
+from selfdrive.car.chrysler.values import CAR as CHRYSLER
+from selfdrive.car.gm.values import CAR as GM
+from selfdrive.car.ford.values import CAR as FORD
+from selfdrive.car.honda.values import CAR as HONDA
+from selfdrive.car.hyundai.values import CAR as HYUNDAI
+from selfdrive.car.nissan.values import CAR as NISSAN
+from selfdrive.car.mazda.values import CAR as MAZDA
+from selfdrive.car.subaru.values import CAR as SUBARU
+from selfdrive.car.toyota.values import CAR as TOYOTA
+from selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN
+from selfdrive.car.tesla.values import CAR as TESLA
+from selfdrive.car.body.values import CAR as COMMA
+
+# TODO: add routes for these cars
+non_tested_cars = [
+ FORD.ESCAPE_MK4,
+ FORD.FOCUS_MK4,
+ GM.CADILLAC_ATS,
+ GM.HOLDEN_ASTRA,
+ GM.MALIBU,
+ GM.EQUINOX,
+ HYUNDAI.GENESIS_G90,
+ HYUNDAI.KIA_OPTIMA_H,
+ HONDA.ODYSSEY_CHN,
+]
+
+CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,))
+
+routes = [
+ CarTestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY),
+
+ CarTestRoute("0c94aa1e1296d7c6|2021-05-05--19-48-37", CHRYSLER.JEEP_CHEROKEE),
+ CarTestRoute("91dfedae61d7bd75|2021-05-22--20-07-52", CHRYSLER.JEEP_CHEROKEE_2019),
+ CarTestRoute("420a8e183f1aed48|2020-03-05--07-15-29", CHRYSLER.PACIFICA_2017_HYBRID),
+ CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.PACIFICA_2018),
+ CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.PACIFICA_2018_HYBRID),
+ CarTestRoute("8190c7275a24557b|2020-01-29--08-33-58", CHRYSLER.PACIFICA_2019_HYBRID),
+ CarTestRoute("3d84727705fecd04|2021-05-25--08-38-56", CHRYSLER.PACIFICA_2020),
+ CarTestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500),
+ CarTestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD, segment=6),
+
+ CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6),
+ #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION),
+
+ CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA),
+ CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL),
+ CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
+ CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
+ CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1),
+ CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV
+ CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO),
+
+ CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G),
+ CarTestRoute("a74b011b32b51b56|2020-07-26--17-09-36", HONDA.CIVIC),
+ CarTestRoute("a859a044a447c2b0|2020-03-03--18-42-45", HONDA.CRV_EU),
+ CarTestRoute("68aac44ad69f838e|2021-05-18--20-40-52", HONDA.CRV),
+ CarTestRoute("14fed2e5fa0aa1a5|2021-05-25--14-59-42", HONDA.CRV_HYBRID),
+ CarTestRoute("52f3e9ae60c0d886|2021-05-23--15-59-43", HONDA.FIT),
+ CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED),
+ CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV),
+ CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX),
+ CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T
+ CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T
+ CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs
+ CarTestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.ACCORDH),
+ CarTestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORDH), # 2021 with new style HUD msgs
+ CarTestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.CRV_5G),
+ CarTestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.ODYSSEY),
+ CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL),
+ CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.INSIGHT),
+ CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.PILOT),
+ CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PASSPORT),
+ CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.CIVIC_BOSCH),
+ CarTestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX),
+ CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE),
+ CarTestRoute("2d5808fae0b38ac6|2021-09-01--17-14-11", HONDA.HONDA_E),
+ CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022),
+
+ CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
+ CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
+ CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN),
+ CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80),
+ CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN),
+ CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE),
+ CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022),
+ CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022),
+ CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1),
+ CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED),
+ CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4),
+ CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0),
+ CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS),
+ CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN),
+ CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA),
+ CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF),
+ CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON),
+ CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023
+ CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN),
+ CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO),
+ CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN),
+ CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE),
+ CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2
+ CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019),
+ CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV),
+ CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020),
+ CarTestRoute("610ebb9faaad6b43|2020-06-13--15-28-36", HYUNDAI.IONIQ_EV_LTD),
+ CarTestRoute("2c5cf2dd6102e5da|2020-06-26--16-00-08", HYUNDAI.IONIQ),
+ CarTestRoute("ab59fe909f626921|2021-10-18--18-34-28", HYUNDAI.IONIQ_HEV_2022),
+ CarTestRoute("22d955b2cd499c22|2020-08-10--19-58-21", HYUNDAI.KONA),
+ CarTestRoute("efc48acf44b1e64d|2021-05-28--21-05-04", HYUNDAI.KONA_EV),
+ CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11),
+ CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV),
+ CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER),
+ CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022),
+ CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER),
+ CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2
+ CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1
+ CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021),
+ CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV),
+ CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV),
+ CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021),
+ CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
+ CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
+ CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
+ CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA), # 2019 Elantra GT
+ CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021),
+ CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021),
+ CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID),
+ CarTestRoute("715ac05b594e9c59|2021-10-27--23-24-56", HYUNDAI.GENESIS_G70_2020),
+
+ CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.ALPHARD_TSS2),
+ CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARDH_TSS2),
+ CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.AVALON),
+ CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.AVALON_2019),
+ CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALONH_2019),
+ CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2),
+ CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALONH_TSS2),
+ CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY),
+ CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2),
+ CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRYH_TSS2),
+ CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRYH),
+ CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.COROLLA),
+ CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.COROLLA_TSS2),
+ CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.COROLLAH_TSS2),
+ CarTestRoute("d2525c22173da58b|2021-04-25--16-47-04", TOYOTA.PRIUS),
+ CarTestRoute("b14c5b4742e6fc85|2020-07-28--19-50-11", TOYOTA.RAV4),
+ CarTestRoute("32a7df20486b0f70|2020-02-06--16-06-50", TOYOTA.RAV4H),
+ CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2),
+ CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022),
+ CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4H_TSS2),
+ CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4H_TSS2_2022),
+ CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2),
+ CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3),
+ CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ESH_TSS2),
+ CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ESH),
+ CarTestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC),
+ CarTestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX),
+ CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RXH),
+ CarTestRoute("01b22eb2ed121565|2020-02-02--11-25-51", TOYOTA.LEXUS_RX_TSS2),
+ CarTestRoute("b74758c690a49668|2020-05-20--15-58-57", TOYOTA.LEXUS_RXH_TSS2),
+ CarTestRoute("ec429c0f37564e3c|2020-02-01--17-28-12", TOYOTA.LEXUS_NXH),
+ CarTestRoute("964c09eb11ca8089|2020-11-03--22-04-00", TOYOTA.LEXUS_NX),
+ CarTestRoute("3fd5305f8b6ca765|2021-04-28--19-26-49", TOYOTA.LEXUS_NX_TSS2),
+ CarTestRoute("09ae96064ed85a14|2022-06-09--12-22-31", TOYOTA.LEXUS_NXH_TSS2),
+ CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2),
+ CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDERH_TSS2),
+ CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER),
+ CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDERH),
+ CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA),
+ CarTestRoute("2e07163a1ba9a780|2019-08-25--13-15-13", TOYOTA.LEXUS_IS),
+ CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2),
+ CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI),
+ CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR),
+ CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH),
+ CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V),
+
+ CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1),
+ CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1),
+ CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC
+ CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal
+ CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7),
+ CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8),
+ CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS),
+ CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6),
+ CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.SHARAN_MK2),
+ CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1),
+ CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1),
+ CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2),
+ CarTestRoute("a589dcc642fdb10a|2021-06-14--20-54-26", VOLKSWAGEN.TOURAN_MK2),
+ CarTestRoute("a459f4556782eba1|2021-09-19--09-48-00", VOLKSWAGEN.TRANSPORTER_T61),
+ CarTestRoute("0cd0b7f7e31a3853|2021-11-18--00-38-32", VOLKSWAGEN.TROC_MK1),
+ CarTestRoute("07667b885add75fd|2021-01-23--19-48-42", VOLKSWAGEN.AUDI_A3_MK3),
+ CarTestRoute("6c6b466346192818|2021-06-06--14-17-47", VOLKSWAGEN.AUDI_Q2_MK1),
+ CarTestRoute("0cd0b7f7e31a3853|2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2),
+ CarTestRoute("8f205bdd11bcbb65|2021-03-26--01-00-17", VOLKSWAGEN.SEAT_ATECA_MK1),
+ CarTestRoute("fc6b6c9a3471c846|2021-05-27--13-39-56", VOLKSWAGEN.SEAT_LEON_MK3),
+ CarTestRoute("12d6ae3057c04b0d|2021-09-15--00-04-07", VOLKSWAGEN.SKODA_KAMIQ_MK1),
+ CarTestRoute("12d6ae3057c04b0d|2021-09-04--21-21-21", VOLKSWAGEN.SKODA_KAROQ_MK1),
+ CarTestRoute("90434ff5d7c8d603|2021-03-15--12-07-31", VOLKSWAGEN.SKODA_KODIAQ_MK1),
+ CarTestRoute("66e5edc3a16459c5|2021-05-25--19-00-29", VOLKSWAGEN.SKODA_OCTAVIA_MK3),
+ CarTestRoute("026b6d18fba6417f|2021-03-26--09-17-04", VOLKSWAGEN.SKODA_SCALA_MK1),
+ CarTestRoute("b2e9858e29db492b|2021-03-26--16-58-42", VOLKSWAGEN.SKODA_SUPERB_MK3),
+
+ CarTestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.ASCENT),
+ CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER),
+ CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA),
+ CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020),
+ CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10),
+ CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3),
+ # Pre-global, dashcam
+ CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL),
+ CarTestRoute("df5ca7660000fba8|2020-06-16--17-37-19", SUBARU.LEGACY_PREGLOBAL),
+ CarTestRoute("5ab784f361e19b78|2020-06-08--16-30-41", SUBARU.OUTBACK_PREGLOBAL),
+ CarTestRoute("e19eb5d5353b1ac1|2020-08-09--14-37-56", SUBARU.OUTBACK_PREGLOBAL_2018),
+
+ CarTestRoute("fbbfa6af821552b9|2020-03-03--08-09-43", NISSAN.XTRAIL),
+ CarTestRoute("5b7c365c50084530|2020-03-25--22-10-13", NISSAN.LEAF),
+ CarTestRoute("22c3dcce2dd627eb|2020-12-30--16-38-48", NISSAN.LEAF_IC),
+ CarTestRoute("059ab9162e23198e|2020-05-30--09-41-01", NISSAN.ROGUE),
+ CarTestRoute("b72d3ec617c0a90f|2020-12-11--15-38-17", NISSAN.ALTIMA),
+
+ CarTestRoute("32a319f057902bb3|2020-04-27--15-18-58", MAZDA.CX5),
+ CarTestRoute("10b5a4b380434151|2020-08-26--17-11-45", MAZDA.CX9),
+ CarTestRoute("74f1038827005090|2020-08-26--20-05-50", MAZDA.MAZDA3),
+ CarTestRoute("fb53c640f499b73d|2021-06-01--04-17-56", MAZDA.MAZDA6),
+ CarTestRoute("f6d5b1a9d7a1c92e|2021-07-08--06-56-59", MAZDA.CX9_2021),
+ CarTestRoute("a4af1602d8e668ac|2022-02-03--12-17-07", MAZDA.CX5_2022),
+
+ CarTestRoute("6c14ee12b74823ce|2021-06-30--11-49-02", TESLA.AP1_MODELS),
+ CarTestRoute("bb50caf5f0945ab1|2021-06-19--17-20-18", TESLA.AP2_MODELS),
+
+ # Segments that test specific issues
+ # Controls mismatch due to interceptor threshold
+ CarTestRoute("cfb32f0fb91b173b|2022-04-06--14-54-45", HONDA.CIVIC, segment=21),
+ CarTestRoute("5a8762b91fc70467|2022-04-14--21-26-20", TOYOTA.RAV4, segment=2),
+ # Controls mismatch due to standstill threshold
+ CarTestRoute("bec2dcfde6a64235|2022-04-08--14-21-32", HONDA.CRV_HYBRID, segment=22),
+]
diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py
index 930619ee67..48d85584b3 100755
--- a/selfdrive/car/tests/test_car_interfaces.py
+++ b/selfdrive/car/tests/test_car_interfaces.py
@@ -1,9 +1,11 @@
#!/usr/bin/env python3
+import math
import unittest
import importlib
from parameterized import parameterized
from cereal import car
+from selfdrive.car import gen_empty_fingerprint
from selfdrive.car.fingerprints import all_known_cars
from selfdrive.car.car_helpers import interfaces
from selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
@@ -12,18 +14,14 @@ class TestCarInterfaces(unittest.TestCase):
@parameterized.expand([(car,) for car in all_known_cars()])
def test_car_interfaces(self, car_name):
- print(car_name)
if car_name in FINGERPRINTS:
fingerprint = FINGERPRINTS[car_name][0]
else:
fingerprint = {}
CarInterface, CarController, CarState = interfaces[car_name]
- fingerprints = {
- 0: fingerprint,
- 1: fingerprint,
- 2: fingerprint,
- }
+ fingerprints = gen_empty_fingerprint()
+ fingerprints.update({k: fingerprint for k in fingerprints.keys()})
car_fw = []
@@ -33,16 +31,23 @@ class TestCarInterfaces(unittest.TestCase):
assert car_interface
self.assertGreater(car_params.mass, 1)
- self.assertGreater(car_params.steerRateCost, 1e-3)
+ self.assertGreater(car_params.wheelbase, 0)
+ self.assertGreater(car_params.centerToFront, 0)
+ self.assertGreater(car_params.maxLateralAccel, 0)
if car_params.steerControlType != car.CarParams.SteerControlType.angle:
- tuning = car_params.lateralTuning.which()
- if tuning == 'pid':
- self.assertTrue(len(car_params.lateralTuning.pid.kpV))
- elif tuning == 'lqr':
- self.assertTrue(len(car_params.lateralTuning.lqr.a))
- elif tuning == 'indi':
- self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV))
+ tune = car_params.lateralTuning
+ if tune.which() == 'pid':
+ self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0)
+ self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP))
+ self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP))
+
+ elif tune.which() == 'torque':
+ self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0)
+ self.assertTrue(not math.isnan(tune.torque.friction))
+
+ elif tune.which() == 'indi':
+ self.assertTrue(len(tune.indi.outerLoopGainV))
# Run car interface
CC = car.CarControl.new_message()
@@ -65,7 +70,8 @@ class TestCarInterfaces(unittest.TestCase):
# Run radar interface once
radar_interface.update([])
- if not car_params.radarOffCan and hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'):
+ if not car_params.radarOffCan and radar_interface.rcp is not None and \
+ hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'):
radar_interface._update([radar_interface.trigger_msg])
if __name__ == "__main__":
diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py
new file mode 100755
index 0000000000..e56f98f7a8
--- /dev/null
+++ b/selfdrive/car/tests/test_docs.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+from collections import defaultdict
+import re
+import unittest
+
+from selfdrive.car.car_helpers import interfaces, get_interface_attr
+from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info
+from selfdrive.car.docs_definitions import Column, Harness, Star
+from selfdrive.car.honda.values import CAR as HONDA
+
+
+class TestCarDocs(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.all_cars = get_all_car_info()
+
+ def test_generator(self):
+ generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
+ with open(CARS_MD_OUT, "r") as f:
+ current_cars_md = f.read()
+
+ self.assertEqual(generated_cars_md, current_cars_md,
+ "Run selfdrive/car/docs.py to update the compatibility documentation")
+
+ def test_duplicate_years(self):
+ make_model_years = defaultdict(list)
+ for car in self.all_cars:
+ with self.subTest(car_info_name=car.name):
+ make_model = (car.make, car.model)
+ for year in car.year_list:
+ self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
+ make_model_years[make_model].append(year)
+
+ def test_missing_car_info(self):
+ all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys()
+ for platform in sorted(interfaces.keys()):
+ with self.subTest(platform=platform):
+ self.assertTrue(platform in all_car_info_platforms, "Platform: {} doesn't exist in CarInfo".format(platform))
+
+ def test_naming_conventions(self):
+ # Asserts market-standard car naming conventions by brand
+ for car in self.all_cars:
+ with self.subTest(car=car):
+ tokens = car.model.lower().split(" ")
+ if car.car_name == "hyundai":
+ self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
+ self.assertNotIn("hev", tokens, "Use `Hybrid`")
+ if "plug-in hybrid" in car.model.lower():
+ self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
+ if car.make != "Kia":
+ self.assertNotIn("ev", tokens, "Use `Electric`")
+ elif car.car_name == "toyota":
+ if "rav4" in tokens:
+ self.assertIn("RAV4", car.model, "Use correct capitalization")
+
+ def test_torque_star(self):
+ # Asserts brand-specific assumptions around steering torque star
+ for car in self.all_cars:
+ with self.subTest(car=car):
+ # honda sanity check, it's the definition of a no torque star
+ if car.car_fingerprint in (HONDA.ACCORD, HONDA.CIVIC, HONDA.CRV, HONDA.ODYSSEY, HONDA.PILOT):
+ self.assertEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has full torque star")
+ elif car.car_name in ("toyota", "hyundai"):
+ self.assertNotEqual(car.row[Column.STEERING_TORQUE], Star.EMPTY, f"{car.name} has no torque star")
+
+ def test_year_format(self):
+ for car in self.all_cars:
+ with self.subTest(car=car):
+ self.assertIsNone(re.search(r"\d{4}-\d{4}", car.name), f"Format years correctly: {car.name}")
+
+ def test_harnesses(self):
+ for car in self.all_cars:
+ with self.subTest(car=car):
+ if car.name == "comma body":
+ raise unittest.SkipTest
+
+ self.assertNotIn(car.harness, [None, Harness.none], f"Need to specify car harness: {car.name}")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/selfdrive/test/test_fingerprints.py b/selfdrive/car/tests/test_fingerprints.py
similarity index 97%
rename from selfdrive/test/test_fingerprints.py
rename to selfdrive/car/tests/test_fingerprints.py
index 01c7d753c3..26ade29e4a 100755
--- a/selfdrive/test/test_fingerprints.py
+++ b/selfdrive/car/tests/test_fingerprints.py
@@ -37,12 +37,12 @@ def check_fingerprint_consistency(f1, f2):
is_f1_in_f2 = True
for k in f1:
if (k not in f2 or f1[k] != f2[k]) and k < max_msg:
- is_f1_in_f2 = False
+ is_f1_in_f2 = False
is_f2_in_f1 = True
for k in f2:
if (k not in f1 or f2[k] != f1[k]) and k < max_msg:
- is_f2_in_f1 = False
+ is_f2_in_f1 = False
return not is_f1_in_f2 and not is_f2_in_f1
@@ -94,4 +94,4 @@ if __name__ == "__main__":
print("TEST FAILED")
sys.exit(1)
else:
- print("TEST SUCESSFUL")
+ print("TEST SUCCESSFUL")
diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py
index 97683b3709..2e43103852 100755
--- a/selfdrive/car/tests/test_fw_fingerprint.py
+++ b/selfdrive/car/tests/test_fw_fingerprint.py
@@ -1,16 +1,20 @@
#!/usr/bin/env python3
import random
import unittest
+from collections import defaultdict
from parameterized import parameterized
from cereal import car
+from selfdrive.car.car_helpers import get_interface_attr, interfaces
from selfdrive.car.fingerprints import FW_VERSIONS
-from selfdrive.car.fw_versions import match_fw_to_car
+from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, match_fw_to_car
CarFw = car.CarParams.CarFw
Ecu = car.CarParams.Ecu
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
+VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True)
+
class TestFwFingerprint(unittest.TestCase):
def assertFingerprints(self, candidates, expected):
@@ -18,29 +22,87 @@ class TestFwFingerprint(unittest.TestCase):
self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}")
self.assertEqual(candidates[0], expected)
- @parameterized.expand([(k, v) for k, v in FW_VERSIONS.items()])
- def test_fw_fingerprint(self, car_model, ecus):
+ @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e])
+ def test_fw_fingerprint(self, brand, car_model, ecus):
CP = car.CarParams.new_message()
for _ in range(200):
fw = []
for ecu, fw_versions in ecus.items():
+ if not len(fw_versions):
+ raise unittest.SkipTest("Car model has no FW versions")
ecu_name, addr, sub_addr = ecu
- fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions),
+ fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
CP.carFw = fw
_, matches = match_fw_to_car(CP.carFw)
self.assertFingerprints(matches, car_model)
def test_no_duplicate_fw_versions(self):
- passed = True
for car_model, ecus in FW_VERSIONS.items():
- for ecu, ecu_fw in ecus.items():
- duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
- if len(duplicates):
- print(car_model, ECU_NAME[ecu[0]], duplicates)
- passed = False
+ with self.subTest(car_model=car_model):
+ for ecu, ecu_fw in ecus.items():
+ with self.subTest(ecu):
+ duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
+ self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}")
+
+ def test_all_addrs_map_to_one_ecu(self):
+ for brand, cars in VERSIONS.items():
+ addr_to_ecu = defaultdict(set)
+ for ecus in cars.values():
+ for ecu_type, addr, sub_addr in ecus.keys():
+ addr_to_ecu[(addr, sub_addr)].add(ecu_type)
+ ecus_for_addr = addr_to_ecu[(addr, sub_addr)]
+ ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr])
+ self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})")
+
+ def test_data_collection_ecus(self):
+ # Asserts no extra ECUs are in the fingerprinting database
+ for brand, config in FW_QUERY_CONFIGS.items():
+ for car_model, ecus in VERSIONS[brand].items():
+ bad_ecus = set(ecus).intersection(config.extra_ecus)
+ with self.subTest(car_model=car_model):
+ self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}')
+
+ def test_blacklisted_ecus(self):
+ blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu
+ for car_model, ecus in FW_VERSIONS.items():
+ with self.subTest(car_model=car_model):
+ CP = interfaces[car_model][0].get_params(car_model)
+ if CP.carName == 'subaru':
+ for ecu in ecus.keys():
+ self.assertNotIn(ecu[1], blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})')
+
+ elif CP.carName == "chrysler":
+ # Some HD trucks have a combined TCM and ECM
+ if CP.carFingerprint.startswith("RAM HD"):
+ for ecu in ecus.keys():
+ self.assertNotEqual(ecu[0], Ecu.transmission, f"{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})")
+
+ def test_missing_versions_and_configs(self):
+ brand_versions = set(VERSIONS.keys())
+ brand_configs = set(FW_QUERY_CONFIGS.keys())
+ if len(brand_configs - brand_versions):
+ with self.subTest():
+ self.fail(f"Brands do not implement FW_VERSIONS: {brand_configs - brand_versions}")
+
+ if len(brand_versions - brand_configs):
+ with self.subTest():
+ self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}")
+
+ def test_fw_request_ecu_whitelist(self):
+ for brand, config in FW_QUERY_CONFIGS.items():
+ with self.subTest(brand=brand):
+ whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus}
+ brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw}
+ brand_ecus |= {ecu[0] for ecu in config.extra_ecus}
+
+ # each ecu in brand's fw versions + extra ecus needs to be whitelisted at least once
+ ecus_not_whitelisted = brand_ecus - whitelisted_ecus
+
+ ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted])
+ self.assertFalse(len(whitelisted_ecus) and len(ecus_not_whitelisted),
+ f'{brand.title()}: FW query whitelist missing ecus: {ecu_strings}')
- self.assertTrue(passed, "Duplicate FW versions found")
if __name__ == "__main__":
unittest.main()
diff --git a/selfdrive/test/test_models.py b/selfdrive/car/tests/test_models.py
similarity index 54%
rename from selfdrive/test/test_models.py
rename to selfdrive/car/tests/test_models.py
index 3be0f96b37..641c109316 100755
--- a/selfdrive/test/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -8,32 +8,24 @@ from typing import List, Optional, Tuple
from parameterized import parameterized_class
from cereal import log, car
-from common.params import Params
from common.realtime import DT_CTRL
-from selfdrive.boardd.boardd import can_capnp_to_can_list, can_list_to_can_capnp
from selfdrive.car.fingerprints import all_known_cars
from selfdrive.car.car_helpers import interfaces
from selfdrive.car.gm.values import CAR as GM
from selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH
from selfdrive.car.hyundai.values import CAR as HYUNDAI
-from selfdrive.car.toyota.values import CAR as TOYOTA
-from selfdrive.test.test_routes import routes, non_tested_cars
+from selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute
from selfdrive.test.openpilotci import get_url
from tools.lib.logreader import LogReader
+from tools.lib.route import Route
-from panda.tests.safety import libpandasafety_py
-from panda.tests.safety.common import package_can_msg
+from panda.tests.libpanda import libpanda_py
PandaType = log.PandaState.PandaType
NUM_JOBS = int(os.environ.get("NUM_JOBS", "1"))
JOB_ID = int(os.environ.get("JOB_ID", "0"))
-# TODO: get updated routes for these cars
-ignore_can_valid = [
- HYUNDAI.SANTA_FE,
-]
-
ignore_addr_checks_valid = [
GM.BUICK_REGAL,
HYUNDAI.GENESIS_G70_2020,
@@ -42,36 +34,54 @@ ignore_addr_checks_valid = [
# build list of test cases
routes_by_car = defaultdict(set)
for r in routes:
- routes_by_car[r.car_fingerprint].add(r.route)
+ routes_by_car[r.car_model].add(r)
-test_cases: List[Tuple[str, Optional[str]]] = []
+test_cases: List[Tuple[str, Optional[CarTestRoute]]] = []
for i, c in enumerate(sorted(all_known_cars())):
if i % NUM_JOBS == JOB_ID:
test_cases.extend((c, r) for r in routes_by_car.get(c, (None, )))
+SKIP_ENV_VAR = "SKIP_LONG_TESTS"
+
-@parameterized_class(('car_model', 'route'), test_cases)
-class TestCarModel(unittest.TestCase):
+class TestCarModelBase(unittest.TestCase):
+ car_model = None
+ test_route = None
+ ci = True
+ @unittest.skipIf(SKIP_ENV_VAR in os.environ, f"Long running test skipped. Unset {SKIP_ENV_VAR} to run")
@classmethod
def setUpClass(cls):
- if cls.route is None:
+ if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'):
+ raise unittest.SkipTest
+
+ if 'FILTER' in os.environ:
+ if not cls.car_model.startswith(tuple(os.environ.get('FILTER').split(','))):
+ raise unittest.SkipTest
+
+ if cls.test_route is None:
if cls.car_model in non_tested_cars:
print(f"Skipping tests for {cls.car_model}: missing route")
raise unittest.SkipTest
raise Exception(f"missing test route for {cls.car_model}")
- params = Params()
- params.clear_all()
+ experimental_long = False
+ test_segs = (2, 1, 0)
+ if cls.test_route.segment is not None:
+ test_segs = (cls.test_route.segment,)
- for seg in [2, 1, 0]:
+ for seg in test_segs:
try:
- lr = LogReader(get_url(cls.route, seg))
+ if cls.ci:
+ lr = LogReader(get_url(cls.test_route.route, seg))
+ else:
+ lr = LogReader(Route(cls.test_route.route).log_paths()[seg])
except Exception:
continue
+ car_fw = []
can_msgs = []
- fingerprint = {i: dict() for i in range(3)}
+ fingerprint = defaultdict(dict)
for msg in lr:
if msg.which() == "can":
for m in msg.can:
@@ -79,29 +89,38 @@ class TestCarModel(unittest.TestCase):
fingerprint[m.src][m.address] = len(m.dat)
can_msgs.append(msg)
elif msg.which() == "carParams":
+ car_fw = msg.carParams.carFw
if msg.carParams.openpilotLongitudinalControl:
- params.put_bool("DisableRadar", True)
+ experimental_long = True
+ if cls.car_model is None and not cls.ci:
+ cls.car_model = msg.carParams.carFingerprint
if len(can_msgs) > int(50 / DT_CTRL):
break
else:
- raise Exception(f"Route {repr(cls.route)} not found or no CAN msgs found. Is it uploaded?")
+ raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?")
cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime)
cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model]
- cls.CP = cls.CarInterface.get_params(cls.car_model, fingerprint, [])
+ cls.CP = cls.CarInterface.get_params(cls.car_model, fingerprint, car_fw, experimental_long)
assert cls.CP
assert cls.CP.carFingerprint == cls.car_model
+ @classmethod
+ def tearDownClass(cls):
+ del cls.can_msgs
+
def setUp(self):
self.CI = self.CarInterface(self.CP, self.CarController, self.CarState)
assert self.CI
# TODO: check safetyModel is in release panda build
- self.safety = libpandasafety_py.libpandasafety
- set_status = self.safety.set_safety_hooks(self.CP.safetyConfigs[0].safetyModel.raw, self.CP.safetyConfigs[0].safetyParam)
- self.assertEqual(0, set_status, f"failed to set safetyModel {self.CP.safetyConfigs[0].safetyModel}")
+ self.safety = libpanda_py.libpanda
+
+ cfg = self.CP.safetyConfigs[-1]
+ set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
+ self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
self.safety.init_tests()
def test_car_params(self):
@@ -110,33 +129,36 @@ class TestCarModel(unittest.TestCase):
# make sure car params are within a valid range
self.assertGreater(self.CP.mass, 1)
- self.assertGreater(self.CP.steerRateCost, 1e-3)
if self.CP.steerControlType != car.CarParams.SteerControlType.angle:
tuning = self.CP.lateralTuning.which()
if tuning == 'pid':
self.assertTrue(len(self.CP.lateralTuning.pid.kpV))
- elif tuning == 'lqr':
- self.assertTrue(len(self.CP.lateralTuning.lqr.a))
+ elif tuning == 'torque':
+ self.assertTrue(self.CP.lateralTuning.torque.kf > 0)
elif tuning == 'indi':
self.assertTrue(len(self.CP.lateralTuning.indi.outerLoopGainV))
else:
- raise Exception("unkown tuning")
+ raise Exception("unknown tuning")
def test_car_interface(self):
- # TODO: also check for checkusm and counter violations from can parser
+ # TODO: also check for checksum violations from can parser
can_invalid_cnt = 0
+ can_valid = False
CC = car.CarControl.new_message()
+
for i, msg in enumerate(self.can_msgs):
CS = self.CI.update(CC, (msg.as_builder().to_bytes(),))
self.CI.apply(CC)
- # wait 2s for low frequency msgs to be seen
- if i > 200:
+ if CS.canValid:
+ can_valid = True
+
+ # wait max of 2s for low frequency msgs to be seen
+ if i > 200 or can_valid:
can_invalid_cnt += not CS.canValid
- if self.car_model not in ignore_can_valid:
- self.assertLess(can_invalid_cnt, 50)
+ self.assertEqual(can_invalid_cnt, 0)
def test_radar_interface(self):
os.environ['NO_RADAR_SLEEP'] = "1"
@@ -145,11 +167,11 @@ class TestCarModel(unittest.TestCase):
assert RI
error_cnt = 0
- for msg in self.can_msgs:
- radar_data = RI.update((msg.as_builder().to_bytes(),))
- if radar_data is not None:
- error_cnt += car.RadarData.Error.canError in radar_data.errors
- self.assertLess(error_cnt, 20)
+ for i, msg in enumerate(self.can_msgs):
+ rr = RI.update((msg.as_builder().to_bytes(),))
+ if rr is not None and i > 50:
+ error_cnt += car.RadarData.Error.canError in rr.errors
+ self.assertEqual(error_cnt, 0)
def test_panda_safety_rx_valid(self):
if self.CP.dashcamOnly:
@@ -168,7 +190,7 @@ class TestCarModel(unittest.TestCase):
if msg.src >= 64:
continue
- to_send = package_can_msg([msg.address, 0, msg.dat, msg.src])
+ to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
if self.safety.safety_rx_hook(to_send) != 1:
failed_addrs[hex(msg.address)] += 1
@@ -190,30 +212,35 @@ class TestCarModel(unittest.TestCase):
# warm up pass, as initial states may be different
for can in self.can_msgs[:300]:
- for msg in can_capnp_to_can_list(can.can, src_filter=range(64)):
- to_send = package_can_msg(msg)
+ self.CI.update(CC, (can.as_builder().to_bytes(), ))
+ for msg in filter(lambda m: m.src in range(64), can.can):
+ to_send = libpanda_py.make_CANPacket(msg.address, msg.src, msg.dat)
self.safety.safety_rx_hook(to_send)
- self.CI.update(CC, (can_list_to_can_capnp([msg, ]), ))
+ controls_allowed_prev = False
+ CS_prev = car.CarState.new_message()
checks = defaultdict(lambda: 0)
- for can in self.can_msgs:
+ for idx, can in enumerate(self.can_msgs):
CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
- for msg in can_capnp_to_can_list(can.can, src_filter=range(64)):
- to_send = package_can_msg(msg)
+ for msg in filter(lambda m: m.src in range(64), can.can):
+ to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
ret = self.safety.safety_rx_hook(to_send)
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}")
+ # Skip first frame so CS_prev is properly initialized
+ if idx == 0:
+ CS_prev = CS
+ # Button may be left pressed in warm up period
+ if not self.CP.pcmCruise:
+ self.safety.set_controls_allowed(0)
+ continue
+
# TODO: check rest of panda's carstate (steering, ACC main on, etc.)
- # TODO: make the interceptor thresholds in openpilot and panda match, then remove this exception
- gas_pressed = CS.gasPressed
- if self.CP.enableGasInterceptor and gas_pressed and not self.safety.get_gas_pressed_prev():
- # panda intentionally has a higher threshold
- if self.CP.carName == "toyota" and 15 < CS.gas < 15*1.5:
- gas_pressed = False
- if self.CP.carName == "honda":
- gas_pressed = False
- checks['gasPressed'] += gas_pressed != self.safety.get_gas_pressed_prev()
+ checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
+ if self.CP.carName not in ("hyundai", "volkswagen", "body"):
+ # TODO: fix standstill mismatches for other makes
+ checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()
# TODO: remove this exception once this mismatch is resolved
brake_pressed = CS.brakePressed
@@ -221,23 +248,40 @@ class TestCarModel(unittest.TestCase):
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05:
brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
+ checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
if self.CP.pcmCruise:
- checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed()
+ # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.
+ # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but
+ # openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages).
+ if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH:
+ # only the rising edges are expected to match
+ if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled:
+ checks['controlsAllowed'] += not self.safety.get_controls_allowed()
+ else:
+ checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed()
+ else:
+ # Check for enable events on rising edge of controls allowed
+ button_enable = any(evt.enable for evt in CS.events)
+ mismatch = button_enable != (self.safety.get_controls_allowed() and not controls_allowed_prev)
+ checks['controlsAllowed'] += mismatch
+ controls_allowed_prev = self.safety.get_controls_allowed()
+ if button_enable and not mismatch:
+ self.safety.set_controls_allowed(False)
if self.CP.carName == "honda":
checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on()
- # TODO: add flag to toyota safety
- if self.CP.carFingerprint == TOYOTA.SIENNA and checks['brakePressed'] < 25:
- checks['brakePressed'] = 0
-
- # Honda Nidec uses button enable in panda, but pcm enable in openpilot
- if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH and checks['controlsAllowed'] < 25:
- checks['controlsAllowed'] = 0
+ CS_prev = CS
failed_checks = {k: v for k, v in checks.items() if v > 0}
self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
+
+@parameterized_class(('car_model', 'test_route'), test_cases)
+class TestCarModel(TestCarModelBase):
+ pass
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml
new file mode 100644
index 0000000000..1eda931e3e
--- /dev/null
+++ b/selfdrive/car/torque_data/override.yaml
@@ -0,0 +1,43 @@
+legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION]
+### angle control
+# Nissan appears to have torque
+NISSAN X-TRAIL 2017: [.nan, 1.5, .nan]
+NISSAN ALTIMA 2020: [.nan, 1.5, .nan]
+NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan]
+NISSAN LEAF 2018: [.nan, 1.5, .nan]
+NISSAN ROGUE 2019: [.nan, 1.5, .nan]
+
+# Tesla has high torque
+TESLA AP1 MODEL S: [.nan, 2.5, .nan]
+TESLA AP2 MODEL S: [.nan, 2.5, .nan]
+
+# Guess
+FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan]
+FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan]
+FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
+###
+
+# No steering wheel
+COMMA BODY: [.nan, 1000, .nan]
+
+# Totally new cars
+RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
+RAM HD 5TH GEN: [1.4, 1.4, 0.0]
+SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11]
+CHEVROLET BOLT EV 2022: [2.0, 2.0, 0.05]
+CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05]
+CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112]
+CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05]
+VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1]
+VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1]
+HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1]
+KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1]
+KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1]
+GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1]
+KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1]
+
+# Dashcam or fallback configured as ideal car
+mock: [10.0, 10, 0.0]
+
+# Manually checked
+HONDA CIVIC 2022: [2.5, 1.2, 0.15]
diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml
new file mode 100644
index 0000000000..3b212c6507
--- /dev/null
+++ b/selfdrive/car/torque_data/params.yaml
@@ -0,0 +1,100 @@
+ACURA ILX 2016: [1.524988973896102, 0.519011053086259, 0.34236219253028]
+ACURA RDX 2018: [0.9987728568686902, 0.5323765166196301, 0.303218805715844]
+ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598]
+AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905]
+AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097]
+CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158]
+CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022]
+CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085]
+CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627]
+CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823]
+CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716]
+GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221]
+GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644]
+HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807]
+HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149]
+HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094]
+HONDA CIVIC 2016: [1.6528895627785531, 0.4018518740819229, 0.25458812851328544]
+HONDA CR-V 2016: [0.7667141440182675, 0.5927571534745969, 0.40909087636157127]
+HONDA CR-V 2017: [2.01323205142022, 0.2700612209345081, 0.2238412881331528]
+HONDA CR-V HYBRID 2019: [2.072034634644233, 0.7152085160516978, 0.20237105008376083]
+HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281]
+HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094]
+HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876]
+HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822]
+HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284]
+HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763]
+HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328]
+HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819]
+HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171]
+HYUNDAI IONIQ 5 2022: [3.172929, 3.0, 0.096019]
+HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276]
+HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778]
+HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482]
+HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 3.2961956260770484, 0.12359762054065548]
+HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651]
+HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945]
+HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963]
+HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0.12672855941458458]
+HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393]
+HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593]
+HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382]
+HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272]
+JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185]
+JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003]
+KIA EV6 2022: [3.2, 3.0, 0.05]
+KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716]
+KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267]
+KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558]
+KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202]
+LEXUS ES 2019: [1.935835, 2.134803912579666, 0.093439]
+LEXUS ES HYBRID 2019: [2.135678, 1.863360677810788, 0.109627]
+LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838]
+LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067]
+LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017]
+LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142]
+LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.164117]
+LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893]
+LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114]
+MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195]
+SKODA SUPERB 3RD GEN: [1.166437404652981, 1.1686163012668165, 0.12194533036948708]
+SUBARU FORESTER 2019: [3.6617001649776793, 2.342197172531713, 0.11075960785398745]
+SUBARU IMPREZA LIMITED 2019: [1.0670704910352047, 0.8234374840709592, 0.20986563268614938]
+SUBARU IMPREZA SPORT 2020: [2.6068223389108303, 2.134872342760203, 0.15261513193561627]
+TOYOTA AVALON 2016: [2.5185770183845646, 1.7153346784214922, 0.10603968787111022]
+TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492]
+TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605]
+TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193]
+TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509]
+TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506]
+TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615]
+TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565]
+TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257]
+TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652]
+TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514]
+TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352]
+TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796]
+TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457]
+TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600]
+TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993]
+TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565]
+TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515]
+TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968]
+TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975]
+TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822]
+TOYOTA RAV4 2019 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918]
+TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198]
+TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406]
+TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632]
+TOYOTA RAV4 HYBRID 2019 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137]
+TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774]
+TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174]
+TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736]
+TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456]
+VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032]
+VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367]
+VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586]
+VOLKSWAGEN JETTA 7TH GEN: [1.2271623034089392, 1.216955117387, 0.19437384688370712]
+VOLKSWAGEN PASSAT 8TH GEN: [1.3432120736752917, 1.7087275587362314, 0.19444383787326647]
+VOLKSWAGEN TIGUAN 2ND GEN: [0.9711965500094828, 1.0001565939459098, 0.1465626137072916]
+legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION]
diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml
new file mode 100644
index 0000000000..aeb2e6f280
--- /dev/null
+++ b/selfdrive/car/torque_data/substitute.yaml
@@ -0,0 +1,79 @@
+MAZDA 3: MAZDA CX-9 2021
+MAZDA 6: MAZDA CX-9 2021
+MAZDA CX-5: MAZDA CX-9 2021
+MAZDA CX-5 2022: MAZDA CX-9 2021
+MAZDA CX-9: MAZDA CX-9 2021
+
+TOYOTA ALPHARD HYBRID 2021 : TOYOTA SIENNA 2018
+TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018
+TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017
+TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022
+TOYOTA C-HR HYBRID 2018: TOYOTA C-HR 2018
+LEXUS IS 2018: LEXUS NX 2018
+LEXUS CT HYBRID 2018 : LEXUS NX 2018
+LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018
+LEXUS NX HYBRID 2020: LEXUS NX 2020
+LEXUS RC 2020: LEXUS NX 2020
+TOYOTA AVALON HYBRID 2019: TOYOTA AVALON 2019
+TOYOTA AVALON HYBRID 2022: TOYOTA AVALON 2022
+
+KIA OPTIMA 4TH GEN: HYUNDAI SONATA 2020
+KIA OPTIMA 4TH GEN FACELIFT: HYUNDAI SONATA 2020
+KIA OPTIMA HYBRID 2017 & SPORTS 2019: HYUNDAI SONATA 2020
+KIA FORTE E 2018 & GT 2021: HYUNDAI SONATA 2020
+KIA CEED INTRO ED 2019: HYUNDAI SONATA 2020
+KIA SELTOS 2021: HYUNDAI SONATA 2020
+KIA NIRO HYBRID 2019: KIA NIRO EV 2020
+KIA NIRO HYBRID 2021: KIA NIRO EV 2020
+HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019
+HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019
+HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019
+HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019
+HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019
+HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019
+HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019
+HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019
+HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020
+HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019
+HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN
+HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022
+KIA STINGER 2022: KIA STINGER GT2 2018
+GENESIS G90 2017: GENESIS G70 2018
+GENESIS G80 2017: GENESIS G70 2018
+GENESIS G70 2020: HYUNDAI SONATA 2020
+
+HONDA FREED 2020: HONDA ODYSSEY 2018
+HONDA CR-V EU 2016: HONDA CR-V 2016
+HONDA CIVIC SEDAN 1.6 DIESEL 2019: HONDA CIVIC (BOSCH) 2019
+HONDA E 2020: HONDA CIVIC (BOSCH) 2019
+HONDA ODYSSEY CHN 2019: HONDA ODYSSEY 2018
+
+BUICK REGAL ESSENCE 2018: CHEVROLET VOLT PREMIER 2017
+CADILLAC ESCALADE ESV 2016: CHEVROLET VOLT PREMIER 2017
+CADILLAC ATS Premium Performance 2018: CHEVROLET VOLT PREMIER 2017
+CHEVROLET MALIBU PREMIER 2017: CHEVROLET VOLT PREMIER 2017
+HOLDEN ASTRA RS-V BK 2017: CHEVROLET VOLT PREMIER 2017
+
+SKODA OCTAVIA 3RD GEN: SKODA SUPERB 3RD GEN
+SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN
+SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN
+SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN
+SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN
+VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
+VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
+VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN
+VOLKSWAGEN TRANSPORTER T6.1: VOLKSWAGEN TIGUAN 2ND GEN
+AUDI Q2 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
+VOLKSWAGEN TAOS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
+VOLKSWAGEN POLO 6TH GEN: VOLKSWAGEN GOLF 7TH GEN
+SEAT LEON 3RD GEN: VOLKSWAGEN GOLF 7TH GEN
+SEAT ATECA 1ST GEN: VOLKSWAGEN GOLF 7TH GEN
+
+SUBARU LEGACY 7TH GEN: SUBARU OUTBACK 6TH GEN
+
+# Old subarus don't have much data guessing it's like low torque impreza
+SUBARU OUTBACK 2018 - 2019: SUBARU IMPREZA LIMITED 2019
+SUBARU OUTBACK 2015 - 2017: SUBARU IMPREZA LIMITED 2019
+SUBARU FORESTER 2017 - 2018: SUBARU IMPREZA LIMITED 2019
+SUBARU LEGACY 2015 - 2018: SUBARU IMPREZA LIMITED 2019
+SUBARU ASCENT LIMITED 2019: SUBARU FORESTER 2019
diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py
index 9062fea6c1..c5bfdc4b22 100644
--- a/selfdrive/car/toyota/carcontroller.py
+++ b/selfdrive/car/toyota/carcontroller.py
@@ -4,34 +4,49 @@ from selfdrive.car import apply_toyota_steer_torque_limits, create_gas_intercept
from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_command, \
create_accel_command, create_acc_cancel_command, \
create_fcw_command, create_lta_steer_command
-from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, \
- MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams
+from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \
+ MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, \
+ UNSUPPORTED_DSU_CAR
from opendbc.can.packer import CANPacker
+
VisualAlert = car.CarControl.HUDControl.VisualAlert
+# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
+MAX_STEER_RATE = 100 # deg/s
+MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
+
+# EPS allows user torque above threshold for 50 frames before permanently faulting
+MAX_USER_TORQUE = 500
+
-class CarController():
+class CarController:
def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
+ self.torque_rate_limits = CarControllerParams(self.CP)
+ self.frame = 0
self.last_steer = 0
self.alert_active = False
self.last_standstill = False
self.standstill_req = False
- self.steer_rate_limited = False
+ self.steer_rate_counter = 0
self.packer = CANPacker(dbc_name)
self.gas = 0
self.accel = 0
- def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_alert,
- left_line, right_line, lead, left_lane_depart, right_lane_depart):
+ def update(self, CC, CS):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
+ pcm_cancel_cmd = CC.cruiseControl.cancel
+ lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
# gas and brake
- if CS.CP.enableGasInterceptor and active:
+ if self.CP.enableGasInterceptor and CC.longActive:
MAX_INTERCEPTOR_GAS = 0.5
# RAV4 has very sensitive gas pedal
- if CS.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH):
+ if self.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH):
PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])
- elif CS.CP.carFingerprint in (CAR.COROLLA,):
+ elif self.CP.carFingerprint in (CAR.COROLLA,):
PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])
else:
PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0])
@@ -45,23 +60,29 @@ class CarController():
# steer torque
new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX))
- apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, CarControllerParams)
- self.steer_rate_limited = new_steer != apply_steer
+ apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.torque_rate_limits)
+
+ # Count up to MAX_STEER_RATE_FRAMES, at which point we need to cut torque to avoid a steering fault
+ if lat_active and abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE:
+ self.steer_rate_counter += 1
+ else:
+ self.steer_rate_counter = 0
- # Cut steering while we're in a known fault state (2s)
- if not active or CS.steer_state in (9, 25):
+ apply_steer_req = 1
+ if not lat_active:
apply_steer = 0
apply_steer_req = 0
- else:
- apply_steer_req = 1
+ elif self.steer_rate_counter > MAX_STEER_RATE_FRAMES:
+ apply_steer_req = 0
+ self.steer_rate_counter = 0
# TODO: probably can delete this. CS.pcm_acc_status uses a different signal
# than CS.cruiseState.enabled. confirm they're not meaningfully different
- if not enabled and CS.pcm_acc_status:
+ if not CC.enabled and CS.pcm_acc_status:
pcm_cancel_cmd = 1
# on entering standstill, send standstill request
- if CS.out.standstill and not self.last_standstill and CS.CP.carFingerprint not in NO_STOP_TIMER_CAR:
+ if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor):
self.standstill_req = True
if CS.pcm_acc_status != 8:
# pcm entered standstill or it's disabled
@@ -72,68 +93,73 @@ class CarController():
can_sends = []
- #*** control msgs ***
- #print("steer {0} {1} {2} {3}".format(apply_steer, min_lim, max_lim, CS.steer_torque_motor)
+ # *** control msgs ***
+ # print("steer {0} {1} {2} {3}".format(apply_steer, min_lim, max_lim, CS.steer_torque_motor)
# toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2;
# sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed
# on consecutive messages
- # can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req, frame))
- # if frame % 2 == 0 and CS.CP.carFingerprint in TSS2_CAR:
- # can_sends.append(create_lta_steer_command(self.packer, 0, 0, frame // 2))
+ """
+ can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req))
+ if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR:
+ can_sends.append(create_lta_steer_command(self.packer, 0, 0, self.frame // 2))
+ """
# LTA mode. Set ret.steerControlType = car.CarParams.SteerControlType.angle and whitelist 0x191 in the panda
- can_sends.append(create_steer_command(self.packer, 0, 0, frame))
+ can_sends.append(create_steer_command(self.packer, 0, 0))
# On TSS2 cars, the LTA and STEER_TORQUE_SENSOR messages use relative steering angle signals that start
# at 0 degrees, so we need to offset the commanded angle as well.
can_sends.append(create_lta_steer_command(self.packer, actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg,
CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg,
CS.out.steeringTorque,
- apply_steer_req, frame))
+ apply_steer_req, self.frame))
# we can spam can to cancel the system even if we are using lat only control
- if (frame % 3 == 0 and CS.CP.openpilotLongitudinalControl) or pcm_cancel_cmd:
- lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged
+ if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd:
+ lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged
# Lexus IS uses a different cancellation message
- if pcm_cancel_cmd and CS.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
+ if pcm_cancel_cmd and self.CP.carFingerprint in UNSUPPORTED_DSU_CAR:
can_sends.append(create_acc_cancel_command(self.packer))
- elif CS.CP.openpilotLongitudinalControl:
+ elif self.CP.openpilotLongitudinalControl:
can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type))
self.accel = pcm_accel_cmd
else:
can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type))
- if frame % 2 == 0 and CS.CP.enableGasInterceptor and CS.CP.openpilotLongitudinalControl:
+ if self.frame % 2 == 0 and self.CP.enableGasInterceptor and self.CP.openpilotLongitudinalControl:
# send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd.
# This prevents unexpected pedal range rescaling
- can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2))
+ can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, self.frame // 2))
self.gas = interceptor_gas_cmd
- # ui mesg is at 1Hz but we send asap if:
- # - there is something to display
- # - there is something to stop displaying
- fcw_alert = hud_alert == VisualAlert.fcw
- steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw)
-
- send_ui = False
- if ((fcw_alert or steer_alert) and not self.alert_active) or \
- (not (fcw_alert or steer_alert) and self.alert_active):
- send_ui = True
- self.alert_active = not self.alert_active
- elif pcm_cancel_cmd:
- # forcing the pcm to disengage causes a bad fault sound so play a good sound instead
- send_ui = True
-
- if (frame % 100 == 0 or send_ui):
- can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart, enabled))
-
- if frame % 100 == 0 and CS.CP.enableDsu:
- can_sends.append(create_fcw_command(self.packer, fcw_alert))
+ if self.CP.carFingerprint != CAR.PRIUS_V:
+ # ui mesg is at 1Hz but we send asap if:
+ # - there is something to display
+ # - there is something to stop displaying
+ fcw_alert = hud_control.visualAlert == VisualAlert.fcw
+ steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)
+
+ send_ui = False
+ if ((fcw_alert or steer_alert) and not self.alert_active) or \
+ (not (fcw_alert or steer_alert) and self.alert_active):
+ send_ui = True
+ self.alert_active = not self.alert_active
+ elif pcm_cancel_cmd:
+ # forcing the pcm to disengage causes a bad fault sound so play a good sound instead
+ send_ui = True
+
+ if self.frame % 100 == 0 or send_ui:
+ can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
+ hud_control.rightLaneVisible, hud_control.leftLaneDepart,
+ hud_control.rightLaneDepart, CC.enabled))
+
+ if (self.frame % 100 == 0 or send_ui) and self.CP.enableDsu:
+ can_sends.append(create_fcw_command(self.packer, fcw_alert))
# *** static msgs ***
- for (addr, cars, bus, fr_step, vl) in STATIC_DSU_MSGS:
- if frame % fr_step == 0 and CS.CP.enableDsu and CS.CP.carFingerprint in cars:
+ for addr, cars, bus, fr_step, vl in STATIC_DSU_MSGS:
+ if self.frame % fr_step == 0 and self.CP.enableDsu and self.CP.carFingerprint in cars:
can_sends.append(make_can_msg(addr, vl, bus))
new_actuators = actuators.copy()
@@ -141,4 +167,5 @@ class CarController():
new_actuators.accel = self.accel
new_actuators.gas = self.gas
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py
index 328dbdeef4..b31a6313f3 100644
--- a/selfdrive/car/toyota/carstate.py
+++ b/selfdrive/car/toyota/carstate.py
@@ -1,12 +1,12 @@
from cereal import car
+from common.conversions import Conversions as CV
from common.numpy_fast import mean
from common.filter_simple import FirstOrderFilter
from common.realtime import DT_CTRL
from opendbc.can.can_define import CANDefine
-from selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
-from selfdrive.config import Conversions as CV
-from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, EPS_SCALE
+from selfdrive.car.interfaces import CarStateBase
+from selfdrive.car.toyota.values import ToyotaFlags, DBC, STEER_THRESHOLD, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR
class CarState(CarStateBase):
@@ -15,11 +15,12 @@ class CarState(CarStateBase):
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"]
self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100.
+ self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2.
+ self.cluster_min_speed = CV.KPH_TO_MS / 2.
# On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]
# the signal is zeroed to where the steering angle is at start.
# Need to apply an offset as soon as the steering angle measurements are both received
- self.needs_angle_offset = True
self.accurate_steer_angle_seen = False
self.angle_offset = FirstOrderFilter(None, 60.0, DT_CTRL, initialized=False)
@@ -33,12 +34,13 @@ class CarState(CarStateBase):
ret.doorOpen = any([cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_FL"], cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_FR"],
cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_RL"], cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_RR"]])
ret.seatbeltUnlatched = cp.vl["BODY_CONTROL_STATE"]["SEATBELT_DRIVER_UNLATCHED"] != 0
+ ret.parkingBrake = cp.vl["BODY_CONTROL_STATE"]["PARKING_BRAKE"] == 1
ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0
ret.brakeHoldActive = cp.vl["ESP_CONTROL"]["BRAKE_HOLD_ACTIVE"] == 1
if self.CP.enableGasInterceptor:
- ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
- ret.gasPressed = ret.gas > 15
+ ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2
+ ret.gasPressed = ret.gas > 805
else:
# TODO: find a new, common signal
msg = "GAS_PEDAL_HYBRID" if (self.CP.flags & ToyotaFlags.HYBRID) else "GAS_PEDAL"
@@ -53,14 +55,15 @@ class CarState(CarStateBase):
)
ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.vEgoCluster = ret.vEgo * 1.015 # minimum of all the cars
- ret.standstill = ret.vEgoRaw < 0.001
+ ret.standstill = ret.vEgoRaw == 0
ret.steeringAngleDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_ANGLE"] + cp.vl["STEER_ANGLE_SENSOR"]["STEER_FRACTION"]
self.torque_sensor_angle_deg = cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]
- # Some newer models have a more accurate angle measurement in the TORQUE_SENSOR message. Use if non-zero
- if abs(self.torque_sensor_angle_deg) > 1e-3:
+ # On some cars, the angle measurement is non-zero while initializing
+ if abs(self.torque_sensor_angle_deg) > 1e-3 and not bool(cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE_INITIALIZING"]):
self.accurate_steer_angle_seen = True
if self.accurate_steer_angle_seen:
@@ -83,42 +86,54 @@ class CarState(CarStateBase):
ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] * self.eps_torque_scale
# we could use the override bit from dbc, but it's triggered at too high torque values
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
- ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in (1, 5)
-
- if self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
+ # steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds
+ # lka msg drop out: goes to 9 then 11 for a combined total of 2 seconds
+ ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 11, 21, 25)
+ # 17 is a fault from a prolonged high torque delta between cmd and user
+ # 3 is a fault from the lka command message not being received by the EPS
+ ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17)
+
+ if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR:
+ # TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH
ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0
ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS
+ cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"]
else:
+ ret.accFaulted = cp.vl["PCM_CRUISE_2"]["ACC_FAULTED"] != 0
ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0
ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS
+ cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"]
- if self.CP.carFingerprint in TSS2_CAR:
- self.acc_type = cp_cam.vl["ACC_CONTROL"]["ACC_TYPE"]
+ # UI_SET_SPEED is always non-zero when main is on, hide until first enable
+ if ret.cruiseState.speed != 0:
+ is_metric = cp.vl["BODY_CONTROL_STATE_2"]["UNITS"] in (1, 2)
+ conversion_factor = CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS
+ ret.cruiseState.speedCluster = cluster_set_speed * conversion_factor
+
+ cp_acc = cp_cam if self.CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR) else cp
+
+ if self.CP.carFingerprint in (TSS2_CAR | RADAR_ACC_CAR):
+ self.acc_type = cp_acc.vl["ACC_CONTROL"]["ACC_TYPE"]
+ ret.stockFcw = bool(cp_acc.vl["ACC_HUD"]["FCW"])
# some TSS2 cars have low speed lockout permanently set, so ignore on those cars
# these cars are identified by an ACC_TYPE value of 2.
# TODO: it is possible to avoid the lockout and gain stop and go if you
# send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1
- if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in (CAR.LEXUS_IS, CAR.LEXUS_RC)) or \
+ if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR) or \
(self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1):
self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2
self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"]
- if self.CP.carFingerprint in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor:
- # ignore standstill in hybrid vehicles, since pcm allows to restart without
- # receiving any special command. Also if interceptor is detected
- ret.cruiseState.standstill = False
- else:
- ret.cruiseState.standstill = self.pcm_acc_status == 7
+ ret.cruiseState.standstill = self.pcm_acc_status == 7
ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"])
ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6)
ret.genericToggle = bool(cp.vl["LIGHT_STALK"]["AUTO_HIGH_BEAM"])
- ret.stockAeb = bool(cp_cam.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_cam.vl["PRE_COLLISION"]["FORCE"] < -1e-5)
-
ret.espDisabled = cp.vl["ESP_CONTROL"]["TC_DISABLED"] != 0
- # 2 is standby, 10 is active. TODO: check that everything else is really a faulty state
- self.steer_state = cp.vl["EPS_STATUS"]["LKA_STATE"]
+
+ if not self.CP.enableDsu:
+ ret.stockAeb = bool(cp_acc.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_acc.vl["PRE_COLLISION"]["FORCE"] < -1e-5)
if self.CP.enableBsm:
ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1)
@@ -142,6 +157,8 @@ class CarState(CarStateBase):
("DOOR_OPEN_RL", "BODY_CONTROL_STATE"),
("DOOR_OPEN_RR", "BODY_CONTROL_STATE"),
("SEATBELT_DRIVER_UNLATCHED", "BODY_CONTROL_STATE"),
+ ("PARKING_BRAKE", "BODY_CONTROL_STATE"),
+ ("UNITS", "BODY_CONTROL_STATE_2"),
("TC_DISABLED", "ESP_CONTROL"),
("BRAKE_HOLD_ACTIVE", "ESP_CONTROL"),
("STEER_FRACTION", "STEER_ANGLE_SENSOR"),
@@ -149,9 +166,11 @@ class CarState(CarStateBase):
("CRUISE_ACTIVE", "PCM_CRUISE"),
("CRUISE_STATE", "PCM_CRUISE"),
("GAS_RELEASED", "PCM_CRUISE"),
+ ("UI_SET_SPEED", "PCM_CRUISE_SM"),
("STEER_TORQUE_DRIVER", "STEER_TORQUE_SENSOR"),
("STEER_TORQUE_EPS", "STEER_TORQUE_SENSOR"),
("STEER_ANGLE", "STEER_TORQUE_SENSOR"),
+ ("STEER_ANGLE_INITIALIZING", "STEER_TORQUE_SENSOR"),
("TURN_SIGNALS", "BLINKERS_STATE"),
("LKA_STATE", "EPS_STATUS"),
("AUTO_HIGH_BEAM", "LIGHT_STALK"),
@@ -162,12 +181,14 @@ class CarState(CarStateBase):
("LIGHT_STALK", 1),
("BLINKERS_STATE", 0.15),
("BODY_CONTROL_STATE", 3),
+ ("BODY_CONTROL_STATE_2", 2),
("ESP_CONTROL", 3),
("EPS_STATUS", 25),
("BRAKE_MODULE", 40),
("WHEEL_SPEEDS", 80),
("STEER_ANGLE_SENSOR", 80),
("PCM_CRUISE", 33),
+ ("PCM_CRUISE_SM", 1),
("STEER_TORQUE_SENSOR", 50),
]
@@ -178,13 +199,16 @@ class CarState(CarStateBase):
signals.append(("GAS_PEDAL", "GAS_PEDAL"))
checks.append(("GAS_PEDAL", 33))
- if CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
+ if CP.carFingerprint in UNSUPPORTED_DSU_CAR:
signals.append(("MAIN_ON", "DSU_CRUISE"))
signals.append(("SET_SPEED", "DSU_CRUISE"))
+ signals.append(("UI_SET_SPEED", "PCM_CRUISE_ALT"))
checks.append(("DSU_CRUISE", 5))
+ checks.append(("PCM_CRUISE_ALT", 1))
else:
signals.append(("MAIN_ON", "PCM_CRUISE_2"))
signals.append(("SET_SPEED", "PCM_CRUISE_2"))
+ signals.append(("ACC_FAULTED", "PCM_CRUISE_2"))
signals.append(("LOW_SPEED_LOCKOUT", "PCM_CRUISE_2"))
checks.append(("PCM_CRUISE_2", 33))
@@ -203,23 +227,43 @@ class CarState(CarStateBase):
]
checks.append(("BSM", 1))
+ if CP.carFingerprint in RADAR_ACC_CAR:
+ signals += [
+ ("ACC_TYPE", "ACC_CONTROL"),
+ ("FCW", "ACC_HUD"),
+ ]
+ checks += [
+ ("ACC_CONTROL", 33),
+ ("ACC_HUD", 1),
+ ]
+
+ if CP.carFingerprint not in (TSS2_CAR - RADAR_ACC_CAR) and not CP.enableDsu:
+ signals += [
+ ("FORCE", "PRE_COLLISION"),
+ ("PRECOLLISION_ACTIVE", "PRE_COLLISION"),
+ ]
+ checks += [
+ ("PRE_COLLISION", 33),
+ ]
+
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)
@staticmethod
def get_cam_can_parser(CP):
- signals = [
- ("FORCE", "PRE_COLLISION"),
- ("PRECOLLISION_ACTIVE", "PRE_COLLISION"),
- ]
+ signals = []
+ checks = []
- # use steering message to check if panda is connected to frc
- checks = [
- ("STEERING_LKA", 42),
- ("PRE_COLLISION", 0), # TODO: figure out why freq is inconsistent
- ]
-
- if CP.carFingerprint in TSS2_CAR:
- signals.append(("ACC_TYPE", "ACC_CONTROL"))
- checks.append(("ACC_CONTROL", 33))
+ if CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR):
+ signals += [
+ ("PRECOLLISION_ACTIVE", "PRE_COLLISION"),
+ ("FORCE", "PRE_COLLISION"),
+ ("ACC_TYPE", "ACC_CONTROL"),
+ ("FCW", "ACC_HUD"),
+ ]
+ checks += [
+ ("PRE_COLLISION", 33),
+ ("ACC_CONTROL", 33),
+ ("ACC_HUD", 1),
+ ]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py
index 9d16d140c1..9b9b82292d 100644
--- a/selfdrive/car/toyota/interface.py
+++ b/selfdrive/car/toyota/interface.py
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
from cereal import car
-from selfdrive.config import Conversions as CV
-from selfdrive.car.toyota.tunes import LatTunes, LongTunes, set_long_tune, set_lat_tune
-from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from common.conversions import Conversions as CV
+from panda import Panda
+from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, CarControllerParams, NO_STOP_TIMER_CAR
+from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
EventName = car.CarEvent.EventName
@@ -15,18 +15,21 @@ class CarInterface(CarInterfaceBase):
return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # pylint: disable=dangerous-default-value
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
-
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "toyota"
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)]
ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate]
+ if candidate in (CAR.RAV4, CAR.PRIUS_V, CAR.COROLLA, CAR.LEXUS_ESH, CAR.LEXUS_CTH):
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE
+
ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay
ret.steerLimitTimer = 0.4
ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop
stop_and_go = False
+ steering_angle_deadzone_deg = 0.0
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg)
if candidate == CAR.PRIUS:
stop_and_go = True
@@ -34,17 +37,19 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.74 # unknown end-to-end spec
tire_stiffness_factor = 0.6371 # hand-tune
ret.mass = 3045. * CV.LB_TO_KG + STD_CARGO_KG
-
- set_lat_tune(ret.lateralTuning, LatTunes.INDI_PRIUS)
- ret.steerActuatorDelay = 0.3
+ # Only give steer angle deadzone to for bad angle sensor prius
+ for fw in car_fw:
+ if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00':
+ steering_angle_deadzone_deg = 0.2
+ ret.steerActuatorDelay = 0.25
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg)
elif candidate == CAR.PRIUS_V:
stop_and_go = True
ret.wheelbase = 2.78
ret.steerRatio = 17.4
tire_stiffness_factor = 0.5533
- ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.LQR_RAV4)
+ ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG
elif candidate in (CAR.RAV4, CAR.RAV4H):
stop_and_go = True if (candidate in CAR.RAV4H) else False
@@ -52,14 +57,12 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.88 # 14.5 is spec end-to-end
tire_stiffness_factor = 0.5533
ret.mass = 3650. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
- set_lat_tune(ret.lateralTuning, LatTunes.LQR_RAV4)
elif candidate == CAR.COROLLA:
ret.wheelbase = 2.70
ret.steerRatio = 18.27
tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 2860. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
- set_lat_tune(ret.lateralTuning, LatTunes.PID_A)
elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2):
stop_and_go = True
@@ -68,7 +71,6 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.035
tire_stiffness_factor = 0.5533
ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
- set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate in (CAR.CHR, CAR.CHRH):
stop_and_go = True
@@ -76,7 +78,6 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.6
tire_stiffness_factor = 0.7933
ret.mass = 3300. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_F)
elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2):
stop_and_go = True
@@ -84,44 +85,43 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.7
tire_stiffness_factor = 0.7933
ret.mass = 3400. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
- set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
-
- elif candidate in (CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2):
- stop_and_go = True
- ret.wheelbase = 2.84988 # 112.2 in = 2.84988 m
- ret.steerRatio = 16.0
- tire_stiffness_factor = 0.8
- ret.mass = 4700. * CV.LB_TO_KG + STD_CARGO_KG # 4260 + 4-5 people
- set_lat_tune(ret.lateralTuning, LatTunes.PID_G)
- elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH):
+ elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2):
stop_and_go = True
- ret.wheelbase = 2.78
+ ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in
ret.steerRatio = 16.0
tire_stiffness_factor = 0.8
- ret.mass = 4607. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid limited
- set_lat_tune(ret.lateralTuning, LatTunes.PID_G)
+ ret.mass = 4516. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
- elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2):
+ elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2):
+ # starting from 2019, all Avalon variants have stop and go
+ # https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf
+ stop_and_go = candidate != CAR.AVALON
ret.wheelbase = 2.82
ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download
tire_stiffness_factor = 0.7983
ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
- set_lat_tune(ret.lateralTuning, LatTunes.PID_H)
- elif candidate in (CAR.RAV4_TSS2, CAR.RAV4H_TSS2):
+ elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022):
stop_and_go = True
ret.wheelbase = 2.68986
ret.steerRatio = 14.3
tire_stiffness_factor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid
- set_lat_tune(ret.lateralTuning, LatTunes.PID_D)
-
- # 2019+ Rav4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
+ ret.lateralTuning.init('pid')
+ ret.lateralTuning.pid.kiBP = [0.0]
+ ret.lateralTuning.pid.kpBP = [0.0]
+ ret.lateralTuning.pid.kpV = [0.6]
+ ret.lateralTuning.pid.kiV = [0.1]
+ ret.lateralTuning.pid.kf = 0.00007818594
+
+ # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
# See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891
for fw in car_fw:
if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']):
- set_lat_tune(ret.lateralTuning, LatTunes.PID_I)
+ ret.lateralTuning.pid.kpV = [0.15]
+ ret.lateralTuning.pid.kiV = [0.05]
+ ret.lateralTuning.pid.kf = 0.00004
break
elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2):
@@ -131,7 +131,6 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 3060. * CV.LB_TO_KG + STD_CARGO_KG
ret.steerControlType = car.CarParams.SteerControlType.angle
- # set_lat_tune(ret.lateralTuning, LatTunes.PID_D)
elif candidate in (CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_ESH):
stop_and_go = True
@@ -139,7 +138,6 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.0 # not optimized
tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 3677. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
- set_lat_tune(ret.lateralTuning, LatTunes.PID_D)
elif candidate == CAR.SIENNA:
stop_and_go = True
@@ -147,14 +145,12 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.5
tire_stiffness_factor = 0.444
ret.mass = 4590. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_J)
elif candidate in (CAR.LEXUS_IS, CAR.LEXUS_RC):
ret.wheelbase = 2.79908
ret.steerRatio = 13.3
tire_stiffness_factor = 0.444
ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_L)
elif candidate == CAR.LEXUS_CTH:
stop_and_go = True
@@ -162,15 +158,13 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 18.6
tire_stiffness_factor = 0.517
ret.mass = 3108 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
- set_lat_tune(ret.lateralTuning, LatTunes.PID_M)
- elif candidate in (CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2):
+ elif candidate in (CAR.LEXUS_NX, CAR.LEXUS_NXH, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2):
stop_and_go = True
ret.wheelbase = 2.66
ret.steerRatio = 14.7
tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 4070 * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate == CAR.PRIUS_TSS2:
stop_and_go = True
@@ -178,7 +172,6 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.4 # True steerRatio from older prius
tire_stiffness_factor = 0.6371 # hand-tune
ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_N)
elif candidate == CAR.MIRAI:
stop_and_go = True
@@ -186,23 +179,16 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.8
tire_stiffness_factor = 0.8
ret.mass = 4300. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
- elif candidate == CAR.ALPHARD_TSS2:
+ elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2):
stop_and_go = True
ret.wheelbase = 3.00
ret.steerRatio = 14.2
tire_stiffness_factor = 0.444
ret.mass = 4305. * CV.LB_TO_KG + STD_CARGO_KG
- set_lat_tune(ret.lateralTuning, LatTunes.PID_J)
- ret.steerRateCost = 1.
ret.centerToFront = ret.wheelbase * 0.44
- # TODO: get actual value, for now starting with reasonable value for
- # civic and scaling by mass and wheelbase
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
-
# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
@@ -213,10 +199,14 @@ class CarInterface(CarInterfaceBase):
smartDsu = 0x2FF in fingerprint[0]
# In TSS2 cars the camera does long control
found_ecus = [fw.ecu for fw in car_fw]
- ret.enableDsu = (len(found_ecus) > 0) and (Ecu.dsu not in found_ecus) and (candidate not in NO_DSU_CAR) and (not smartDsu)
+ ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not smartDsu
ret.enableGasInterceptor = 0x201 in fingerprint[0]
# if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected")
- ret.openpilotLongitudinalControl = smartDsu or ret.enableDsu or candidate in TSS2_CAR
+ ret.openpilotLongitudinalControl = smartDsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR)
+ ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR
+
+ if not ret.openpilotLongitudinalControl:
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL
# we can't use the fingerprint to detect this reliably, since
# the EV gas pedal signal can take a couple seconds to appear
@@ -227,55 +217,50 @@ class CarInterface(CarInterfaceBase):
# to a negative value, so it won't matter.
ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else MIN_ACC_SPEED
- if ret.enableGasInterceptor:
- set_long_tune(ret.longitudinalTuning, LongTunes.PEDAL)
- elif candidate in TSS2_CAR:
- set_long_tune(ret.longitudinalTuning, LongTunes.TSS2)
- ret.stoppingDecelRate = 0.3 # reach stopping target smoothly
+ tune = ret.longitudinalTuning
+ tune.deadzoneBP = [0., 9.]
+ tune.deadzoneV = [.0, .15]
+ if candidate in TSS2_CAR or ret.enableGasInterceptor:
+ tune.kpBP = [0., 5., 20.]
+ tune.kpV = [1.3, 1.0, 0.7]
+ tune.kiBP = [0., 5., 12., 20., 27.]
+ tune.kiV = [.35, .23, .20, .17, .1]
+ if candidate in TSS2_CAR:
+ ret.stoppingDecelRate = 0.3 # reach stopping target smoothly
else:
- set_long_tune(ret.longitudinalTuning, LongTunes.TSS)
+ tune.kpBP = [0., 5., 35.]
+ tune.kiBP = [0., 35.]
+ tune.kpV = [3.6, 2.4, 1.5]
+ tune.kiV = [0.54, 0.36]
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- # ******************* do can recv *******************
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
-
# events
events = self.create_common_events(ret)
- if self.CS.low_speed_lockout and self.CP.openpilotLongitudinalControl:
- events.add(EventName.lowSpeedLockout)
- if ret.vEgo < self.CP.minEnableSpeed and self.CP.openpilotLongitudinalControl:
- events.add(EventName.belowEngageSpeed)
- if c.actuators.accel > 0.3:
- # some margin on the actuator to not false trigger cancellation while stopping
- events.add(EventName.speedTooLow)
- if ret.vEgo < 0.001:
- # while in standstill, send a user alert
- events.add(EventName.manualRestart)
+ if self.CP.openpilotLongitudinalControl:
+ if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor:
+ events.add(EventName.resumeRequired)
+ if self.CS.low_speed_lockout:
+ events.add(EventName.lowSpeedLockout)
+ if ret.vEgo < self.CP.minEnableSpeed:
+ events.add(EventName.belowEngageSpeed)
+ if c.actuators.accel > 0.3:
+ # some margin on the actuator to not false trigger cancellation while stopping
+ events.add(EventName.speedTooLow)
+ if ret.vEgo < 0.001:
+ # while in standstill, send a user alert
+ events.add(EventName.manualRestart)
ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
# pass in a car.CarControl
# to be called @ 100hz
def apply(self, c):
- hud_control = c.hudControl
- ret = self.CC.update(c.enabled, c.active, self.CS, self.frame,
- c.actuators, c.cruiseControl.cancel,
- hud_control.visualAlert, hud_control.leftLaneVisible,
- hud_control.rightLaneVisible, hud_control.leadVisible,
- hud_control.leftLaneDepart, hud_control.rightLaneDepart)
-
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS)
diff --git a/selfdrive/car/toyota/radar_interface.py b/selfdrive/car/toyota/radar_interface.py
index 590840851d..8c87704ff2 100755
--- a/selfdrive/car/toyota/radar_interface.py
+++ b/selfdrive/car/toyota/radar_interface.py
@@ -6,6 +6,9 @@ from selfdrive.car.interfaces import RadarInterfaceBase
def _create_radar_can_parser(car_fingerprint):
+ if DBC[car_fingerprint]['radar'] is None:
+ return None
+
if car_fingerprint in TSS2_CAR:
RADAR_A_MSGS = list(range(0x180, 0x190))
RADAR_B_MSGS = list(range(0x190, 0x1a0))
@@ -48,7 +51,7 @@ class RadarInterface(RadarInterfaceBase):
self.no_radar = CP.carFingerprint in NO_DSU_CAR and CP.carFingerprint not in TSS2_CAR
def update(self, can_strings):
- if self.no_radar:
+ if self.no_radar or self.rcp is None:
return super().update(None)
vls = self.rcp.update_strings(can_strings)
diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py
index e22f8ba610..36e6f5aa4d 100644
--- a/selfdrive/car/toyota/toyotacan.py
+++ b/selfdrive/car/toyota/toyotacan.py
@@ -1,12 +1,12 @@
from common.numpy_fast import interp
-def create_steer_command(packer, steer, steer_req, raw_cnt):
+
+def create_steer_command(packer, steer, steer_req):
"""Creates a CAN message for the Toyota Steer Command."""
values = {
"STEER_REQUEST": steer_req,
"STEER_TORQUE_CMD": steer,
- "COUNTER": raw_cnt,
"SET_ME_1": 1,
}
return packer.make_can_msg("STEERING_LKA", 0, values)
diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py
deleted file mode 100644
index f26fc72a09..0000000000
--- a/selfdrive/car/toyota/tunes.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python3
-from enum import Enum
-
-
-class LongTunes(Enum):
- PEDAL = 0
- TSS2 = 1
- TSS = 2
-
-class LatTunes(Enum):
- INDI_PRIUS = 0
- LQR_RAV4 = 1
- PID_A = 2
- PID_B = 3
- PID_C = 4
- PID_D = 5
- PID_E = 6
- PID_F = 7
- PID_G = 8
- PID_I = 9
- PID_H = 10
- PID_J = 11
- PID_K = 12
- PID_L = 13
- PID_M = 14
- PID_N = 15
-
-
-###### LONG ######
-def set_long_tune(tune, name):
- # Improved longitudinal tune
- if name == LongTunes.TSS2 or name == LongTunes.PEDAL:
- tune.deadzoneBP = [0., 8.05]
- tune.deadzoneV = [.0, .14]
- tune.kpBP = [0., 5., 20.]
- tune.kpV = [1.3, 1.0, 0.7]
- tune.kiBP = [0., 5., 12., 20., 27.]
- tune.kiV = [.35, .23, .20, .17, .1]
- # Default longitudinal tune
- elif name == LongTunes.TSS:
- tune.deadzoneBP = [0., 9.]
- tune.deadzoneV = [0., .15]
- tune.kpBP = [0., 5., 35.]
- tune.kiBP = [0., 35.]
- tune.kpV = [3.6, 2.4, 1.5]
- tune.kiV = [0.54, 0.36]
- else:
- raise NotImplementedError('This longitudinal tune does not exist')
-
-
-###### LAT ######
-def set_lat_tune(tune, name):
- if name == LatTunes.INDI_PRIUS:
- tune.init('indi')
- tune.indi.innerLoopGainBP = [0.]
- tune.indi.innerLoopGainV = [4.0]
- tune.indi.outerLoopGainBP = [0.]
- tune.indi.outerLoopGainV = [3.0]
- tune.indi.timeConstantBP = [0.]
- tune.indi.timeConstantV = [1.0]
- tune.indi.actuatorEffectivenessBP = [0.]
- tune.indi.actuatorEffectivenessV = [1.0]
-
- elif name == LatTunes.LQR_RAV4:
- tune.init('lqr')
- tune.lqr.scale = 1500.0
- tune.lqr.ki = 0.05
- tune.lqr.a = [0., 1., -0.22619643, 1.21822268]
- tune.lqr.b = [-1.92006585e-04, 3.95603032e-05]
- tune.lqr.c = [1., 0.]
- tune.lqr.k = [-110.73572306, 451.22718255]
- tune.lqr.l = [0.3233671, 0.3185757]
- tune.lqr.dcGain = 0.002237852961363602
-
- elif 'PID' in str(name):
- tune.init('pid')
- tune.pid.kiBP = [0.0]
- tune.pid.kpBP = [0.0]
- if name == LatTunes.PID_A:
- tune.pid.kpV = [0.2]
- tune.pid.kiV = [0.05]
- tune.pid.kf = 0.00003
- elif name == LatTunes.PID_C:
- tune.pid.kpV = [0.6]
- tune.pid.kiV = [0.1]
- tune.pid.kf = 0.00006
- elif name == LatTunes.PID_D:
- tune.pid.kpV = [0.6]
- tune.pid.kiV = [0.1]
- tune.pid.kf = 0.00007818594
- elif name == LatTunes.PID_F:
- tune.pid.kpV = [0.723]
- tune.pid.kiV = [0.0428]
- tune.pid.kf = 0.00006
- elif name == LatTunes.PID_G:
- tune.pid.kpV = [0.18]
- tune.pid.kiV = [0.015]
- tune.pid.kf = 0.00012
- elif name == LatTunes.PID_H:
- tune.pid.kpV = [0.17]
- tune.pid.kiV = [0.03]
- tune.pid.kf = 0.00006
- elif name == LatTunes.PID_I:
- tune.pid.kpV = [0.15]
- tune.pid.kiV = [0.05]
- tune.pid.kf = 0.00004
- elif name == LatTunes.PID_J:
- tune.pid.kpV = [0.19]
- tune.pid.kiV = [0.02]
- tune.pid.kf = 0.00007818594
- elif name == LatTunes.PID_L:
- tune.pid.kpV = [0.3]
- tune.pid.kiV = [0.05]
- tune.pid.kf = 0.00006
- elif name == LatTunes.PID_M:
- tune.pid.kpV = [0.3]
- tune.pid.kiV = [0.05]
- tune.pid.kf = 0.00007
- elif name == LatTunes.PID_N:
- tune.pid.kpV = [0.35]
- tune.pid.kiV = [0.15]
- tune.pid.kf = 0.00007818594
- else:
- raise NotImplementedError('This PID tune does not exist')
- else:
- raise NotImplementedError('This lateral tune does not exist')
diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py
index b8a6ae7e0d..56bb2697c7 100644
--- a/selfdrive/car/toyota/values.py
+++ b/selfdrive/car/toyota/values.py
@@ -1,9 +1,13 @@
from collections import defaultdict
-from enum import IntFlag
+from dataclasses import dataclass
+from enum import Enum, IntFlag
+from typing import Dict, List, Union
from cereal import car
+from common.conversions import Conversions as CV
from selfdrive.car import dbc_dict
-from selfdrive.config import Conversions as CV
+from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = car.CarParams.Ecu
MIN_ACC_SPEED = 19. * CV.MPH_TO_MS
@@ -15,10 +19,16 @@ class CarControllerParams:
ACCEL_MIN = -3.5 # m/s2
STEER_MAX = 1500
- STEER_DELTA_UP = 10 # 1.5s time to peak torque
- STEER_DELTA_DOWN = 25 # always lower than 45 otherwise the Rav4 faults (Prius seems ok with 50)
STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor
+ def __init__(self, CP):
+ if CP.lateralTuning.which == 'torque':
+ self.STEER_DELTA_UP = 15 # 1.0s time to peak torque
+ self.STEER_DELTA_DOWN = 25 # always lower than 45 otherwise the Rav4 faults (Prius seems ok with 50)
+ else:
+ self.STEER_DELTA_UP = 10 # 1.5s time to peak torque
+ self.STEER_DELTA_DOWN = 25 # always lower than 45 otherwise the Rav4 faults (Prius seems ok with 50)
+
class ToyotaFlags(IntFlag):
HYBRID = 1
@@ -27,10 +37,12 @@ class ToyotaFlags(IntFlag):
class CAR:
# Toyota
ALPHARD_TSS2 = "TOYOTA ALPHARD 2020"
+ ALPHARDH_TSS2 = "TOYOTA ALPHARD HYBRID 2021"
AVALON = "TOYOTA AVALON 2016"
AVALON_2019 = "TOYOTA AVALON 2019"
AVALONH_2019 = "TOYOTA AVALON HYBRID 2019"
- AVALON_TSS2 = "TOYOTA AVALON 2022"
+ AVALON_TSS2 = "TOYOTA AVALON 2022" # TSS 2.5
+ AVALONH_TSS2 = "TOYOTA AVALON HYBRID 2022"
CAMRY = "TOYOTA CAMRY 2018"
CAMRYH = "TOYOTA CAMRY HYBRID 2018"
CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5
@@ -51,8 +63,10 @@ class CAR:
RAV4 = "TOYOTA RAV4 2017"
RAV4H = "TOYOTA RAV4 HYBRID 2017"
RAV4_TSS2 = "TOYOTA RAV4 2019"
+ RAV4_TSS2_2022 = "TOYOTA RAV4 2022"
RAV4H_TSS2 = "TOYOTA RAV4 HYBRID 2019"
- MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5
+ RAV4H_TSS2_2022 = "TOYOTA RAV4 HYBRID 2022"
+ MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5
SIENNA = "TOYOTA SIENNA 2018"
# Lexus
@@ -64,12 +78,107 @@ class CAR:
LEXUS_NX = "LEXUS NX 2018"
LEXUS_NXH = "LEXUS NX HYBRID 2018"
LEXUS_NX_TSS2 = "LEXUS NX 2020"
+ LEXUS_NXH_TSS2 = "LEXUS NX HYBRID 2020"
LEXUS_RC = "LEXUS RC 2020"
LEXUS_RX = "LEXUS RX 2016"
LEXUS_RXH = "LEXUS RX HYBRID 2017"
LEXUS_RX_TSS2 = "LEXUS RX 2020"
LEXUS_RXH_TSS2 = "LEXUS RX HYBRID 2020"
+
+class Footnote(Enum):
+ CAMRY = CarFootnote(
+ "openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.",
+ Column.FSR_LONGITUDINAL)
+
+
+@dataclass
+class ToyotaCarInfo(CarInfo):
+ package: str = "All"
+ harness: Enum = Harness.toyota
+
+
+CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
+ # Toyota
+ CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"),
+ CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"),
+ CAR.AVALON: [
+ ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"),
+ ToyotaCarInfo("Toyota Avalon 2017-18"),
+ ],
+ CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21"),
+ CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"),
+ CAR.AVALON_TSS2: ToyotaCarInfo("Toyota Avalon 2022"),
+ CAR.AVALONH_TSS2: ToyotaCarInfo("Toyota Avalon Hybrid 2022"),
+ CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]),
+ CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
+ CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]),
+ CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"),
+ CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"),
+ CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"),
+ CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"),
+ CAR.COROLLA_TSS2: [
+ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
+ ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5),
+ ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
+ ],
+ CAR.COROLLAH_TSS2: [
+ ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"),
+ ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5),
+ ToyotaCarInfo("Lexus UX Hybrid 2019-22"),
+ ],
+ CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"),
+ CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"),
+ CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"),
+ CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"),
+ CAR.PRIUS: [
+ ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"),
+ ],
+ CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED),
+ CAR.PRIUS_TSS2: [
+ ToyotaCarInfo("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"),
+ ToyotaCarInfo("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"),
+ ],
+ CAR.RAV4: [
+ ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P"),
+ ToyotaCarInfo("Toyota RAV4 2017-18")
+ ],
+ CAR.RAV4H: [
+ ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26"),
+ ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26")
+ ],
+ CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"),
+ CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"),
+ CAR.RAV4H_TSS2: ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"),
+ CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"),
+ CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"),
+ CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED),
+
+ # Lexus
+ CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"),
+ CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+"),
+ CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"),
+ CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"),
+ CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"),
+ CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19"),
+ CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19"),
+ CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"),
+ CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"),
+ CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"),
+ CAR.LEXUS_RX: [
+ ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"),
+ ToyotaCarInfo("Lexus RX 2017-19"),
+ ],
+ CAR.LEXUS_RXH: [
+ ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+"),
+ ToyotaCarInfo("Lexus RX Hybrid 2017-19"),
+ ],
+ CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"),
+ CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"),
+}
+
# (addr, cars, bus, 1/freq*100, vl)
STATIC_DSU_MSGS = [
(0x128, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON), 1, 3, b'\xf4\x01\x90\x83\x00\x37'),
@@ -92,9 +201,41 @@ STATIC_DSU_MSGS = [
(0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'),
]
+TOYOTA_VERSION_REQUEST = b'\x1a\x88\x01'
+TOYOTA_VERSION_RESPONSE = b'\x5a\x88\x01'
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST],
+ [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps],
+ bus=0,
+ ),
+ Request(
+ [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST],
+ [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine],
+ bus=0,
+ ),
+ Request(
+ [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST],
+ [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps],
+ bus=0,
+ ),
+ ],
+ non_essential_ecus={
+ # FIXME: On some models, abs can sometimes be missing
+ Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS],
+ # On some models, the engine can show on two different addresses
+ Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.LEXUS_IS],
+ }
+)
+
FW_VERSIONS = {
CAR.AVALON: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152607060\x00\x00\x00\x00\x00\x00',
],
(Ecu.dsu, 0x791, None): [
@@ -119,7 +260,7 @@ FW_VERSIONS = {
],
},
CAR.AVALON_2019: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152607140\x00\x00\x00\x00\x00\x00',
b'F152607171\x00\x00\x00\x00\x00\x00',
b'F152607110\x00\x00\x00\x00\x00\x00',
@@ -147,7 +288,7 @@ FW_VERSIONS = {
],
},
CAR.AVALONH_2019: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152641040\x00\x00\x00\x00\x00\x00',
b'F152641061\x00\x00\x00\x00\x00\x00',
b'F152641050\x00\x00\x00\x00\x00\x00',
@@ -174,7 +315,8 @@ FW_VERSIONS = {
],
},
CAR.AVALON_TSS2: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
+ b'\x01F152607240\x00\x00\x00\x00\x00\x00',
b'\x01F152607280\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
@@ -183,11 +325,31 @@ FW_VERSIONS = {
(Ecu.engine, 0x700, None): [
b'\x01896630742000\x00\x00\x00\x00',
],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F6201200\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
+ b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
+ ],
+ },
+ CAR.AVALONH_TSS2: {
+ (Ecu.abs, 0x7b0, None): [
+ b'F152641080\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'8965B41110\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x700, None): [
+ b'\x018966306Q6000\x00\x00\x00\x00',
+ ],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F6201200\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
+ b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.CAMRY: {
@@ -229,7 +391,7 @@ FW_VERSIONS = {
b'8821F0608200 ',
b'8821F0609100 ',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152606210\x00\x00\x00\x00\x00\x00',
b'F152606230\x00\x00\x00\x00\x00\x00',
b'F152606270\x00\x00\x00\x00\x00\x00',
@@ -293,7 +455,7 @@ FW_VERSIONS = {
b'\x028966306S0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
b'\x028966306S1100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152633214\x00\x00\x00\x00\x00\x00',
b'F152633660\x00\x00\x00\x00\x00\x00',
b'F152633712\x00\x00\x00\x00\x00\x00',
@@ -359,7 +521,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [
b'8965B33630\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F152606370\x00\x00\x00\x00\x00\x00',
b'\x01F152606390\x00\x00\x00\x00\x00\x00',
b'\x01F152606400\x00\x00\x00\x00\x00\x00',
@@ -384,21 +546,28 @@ FW_VERSIONS = {
CAR.CAMRYH_TSS2: {
(Ecu.eps, 0x7a1, None): [
b'8965B33630\x00\x00\x00\x00\x00\x00',
+ b'8965B33650\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152633D00\x00\x00\x00\x00\x00\x00',
+ b'F152633D60\x00\x00\x00\x00\x00\x00',
+ b'F152633310\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x018966306Q6000\x00\x00\x00\x00',
b'\x018966306Q7000\x00\x00\x00\x00',
+ b'\x018966306V1000\x00\x00\x00\x00',
+ b'\x01896633T20000\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 15): [
b'\x018821F6201200\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 109): [
b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
+ b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.CHR: {
@@ -419,7 +588,7 @@ FW_VERSIONS = {
b'8821FF406000 ',
b'8821FF407100 ',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152610020\x00\x00\x00\x00\x00\x00',
b'F152610153\x00\x00\x00\x00\x00\x00',
b'F152610210\x00\x00\x00\x00\x00\x00',
@@ -441,6 +610,7 @@ FW_VERSIONS = {
b'\x033F401100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203102\x00\x00\x00\x00',
b'\x033F401200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00',
b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00',
+ b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'8821F0W01000 ',
@@ -470,7 +640,7 @@ FW_VERSIONS = {
b'\x0289663F431000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x0189663F438000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152610012\x00\x00\x00\x00\x00\x00',
b'F152610013\x00\x00\x00\x00\x00\x00',
b'F152610014\x00\x00\x00\x00\x00\x00',
@@ -529,7 +699,7 @@ FW_VERSIONS = {
b'881510201100\x00\x00\x00\x00',
b'881510201200\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152602190\x00\x00\x00\x00\x00\x00',
b'F152602191\x00\x00\x00\x00\x00\x00',
],
@@ -549,6 +719,7 @@ FW_VERSIONS = {
},
CAR.COROLLA_TSS2: {
(Ecu.engine, 0x700, None): [
+ b'\x01896630A22000\x00\x00\x00\x00',
b'\x01896630ZG2000\x00\x00\x00\x00',
b'\x01896630ZG5000\x00\x00\x00\x00',
b'\x01896630ZG5100\x00\x00\x00\x00',
@@ -557,6 +728,8 @@ FW_VERSIONS = {
b'\x01896630ZP1000\x00\x00\x00\x00',
b'\x01896630ZP2000\x00\x00\x00\x00',
b'\x01896630ZQ5000\x00\x00\x00\x00',
+ b'\x01896630ZU9000\x00\x00\x00\x00',
+ b'\x01896630ZX4000\x00\x00\x00\x00',
b'\x018966312L8000\x00\x00\x00\x00',
b'\x018966312M0000\x00\x00\x00\x00',
b'\x018966312M9000\x00\x00\x00\x00',
@@ -575,6 +748,7 @@ FW_VERSIONS = {
b'\x018966312S7000\x00\x00\x00\x00',
b'\x018966312W3000\x00\x00\x00\x00',
b'\x018966312W9000\x00\x00\x00\x00',
+ b'\x01896637644000\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -587,7 +761,10 @@ FW_VERSIONS = {
b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00',
b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00',
b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00',
+ b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00',
+ b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00',
b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'\x018965B12350\x00\x00\x00\x00\x00\x00',
@@ -599,9 +776,11 @@ FW_VERSIONS = {
b'\x018965B1255000\x00\x00\x00\x00',
b'8965B12361\x00\x00\x00\x00\x00\x00',
b'8965B16011\x00\x00\x00\x00\x00\x00',
- b'\x018965B12510\x00\x00\x00\x00\x00\x00'
+ b'8965B76012\x00\x00\x00\x00\x00\x00',
+ b'\x018965B12510\x00\x00\x00\x00\x00\x00',
+ b'\x018965B1256000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F152602280\x00\x00\x00\x00\x00\x00',
b'\x01F152602560\x00\x00\x00\x00\x00\x00',
b'\x01F152602590\x00\x00\x00\x00\x00\x00',
@@ -621,6 +800,9 @@ FW_VERSIONS = {
b'\x01F152612C00\x00\x00\x00\x00\x00\x00',
b'F152602191\x00\x00\x00\x00\x00\x00',
b'\x01F152612862\x00\x00\x00\x00\x00\x00',
+ b'\x01F152612B91\x00\x00\x00\x00\x00\x00',
+ b'\x01F15260A070\x00\x00\x00\x00\x00\x00',
+ b'\x01F152676250\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00',
@@ -638,6 +820,7 @@ FW_VERSIONS = {
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
+ b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
CAR.COROLLAH_TSS2: {
@@ -647,8 +830,11 @@ FW_VERSIONS = {
b'\x01896637621000\x00\x00\x00\x00',
b'\x01896637624000\x00\x00\x00\x00',
b'\x01896637626000\x00\x00\x00\x00',
+ b'\x01896637639000\x00\x00\x00\x00',
b'\x01896637648000\x00\x00\x00\x00',
b'\x01896637643000\x00\x00\x00\x00',
+ b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
+ b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
@@ -666,6 +852,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [
b'8965B12361\x00\x00\x00\x00\x00\x00',
b'8965B12451\x00\x00\x00\x00\x00\x00',
+ b'8965B16011\x00\x00\x00\x00\x00\x00',
b'8965B76012\x00\x00\x00\x00\x00\x00',
b'8965B76050\x00\x00\x00\x00\x00\x00',
b'\x018965B12350\x00\x00\x00\x00\x00\x00',
@@ -676,7 +863,7 @@ FW_VERSIONS = {
b'\x018965B12520\x00\x00\x00\x00\x00\x00',
b'\x018965B12530\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152612590\x00\x00\x00\x00\x00\x00',
b'F152612691\x00\x00\x00\x00\x00\x00',
b'F152612692\x00\x00\x00\x00\x00\x00',
@@ -686,14 +873,17 @@ FW_VERSIONS = {
b'F152612800\x00\x00\x00\x00\x00\x00',
b'F152612820\x00\x00\x00\x00\x00\x00',
b'F152612840\x00\x00\x00\x00\x00\x00',
+ b'F152612842\x00\x00\x00\x00\x00\x00',
b'F152612890\x00\x00\x00\x00\x00\x00',
b'F152612A00\x00\x00\x00\x00\x00\x00',
b'F152612A10\x00\x00\x00\x00\x00\x00',
+ b'F152612D00\x00\x00\x00\x00\x00\x00',
+ b'F152616011\x00\x00\x00\x00\x00\x00',
+ b'F152616060\x00\x00\x00\x00\x00\x00',
b'F152642540\x00\x00\x00\x00\x00\x00',
b'F152676293\x00\x00\x00\x00\x00\x00',
b'F152676303\x00\x00\x00\x00\x00\x00',
b'F152676304\x00\x00\x00\x00\x00\x00',
- b'F152612D00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00',
@@ -709,6 +899,7 @@ FW_VERSIONS = {
b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
+ b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00",
b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
@@ -744,7 +935,7 @@ FW_VERSIONS = {
b'8965B48150\x00\x00\x00\x00\x00\x00',
b'8965B48210\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [b'F15260E011\x00\x00\x00\x00\x00\x00'],
+ (Ecu.abs, 0x7b0, None): [b'F15260E011\x00\x00\x00\x00\x00\x00'],
(Ecu.dsu, 0x791, None): [
b'881510E01100\x00\x00\x00\x00',
b'881510E01200\x00\x00\x00\x00',
@@ -762,7 +953,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [
b'8965B48160\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152648541\x00\x00\x00\x00\x00\x00',
b'F152648542\x00\x00\x00\x00\x00\x00',
],
@@ -787,10 +978,11 @@ FW_VERSIONS = {
b'8965B48310\x00\x00\x00\x00\x00\x00',
b'8965B48320\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F15260E051\x00\x00\x00\x00\x00\x00',
b'\x01F15260E061\x00\x00\x00\x00\x00\x00',
b'\x01F15260E110\x00\x00\x00\x00\x00\x00',
+ b'\x01F15260E170\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x01896630E62100\x00\x00\x00\x00',
@@ -805,15 +997,19 @@ FW_VERSIONS = {
b'\x01896630EB2200\x00\x00\x00\x00',
b'\x01896630EC4000\x00\x00\x00\x00',
b'\x01896630ED9000\x00\x00\x00\x00',
+ b'\x01896630ED9100\x00\x00\x00\x00',
b'\x01896630EE1000\x00\x00\x00\x00',
+ b'\x01896630EE1100\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301400\x00\x00\x00\x00',
b'\x018821F6201200\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
+ b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.HIGHLANDERH_TSS2: {
@@ -821,30 +1017,38 @@ FW_VERSIONS = {
b'8965B48241\x00\x00\x00\x00\x00\x00',
b'8965B48310\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F15264872300\x00\x00\x00\x00',
b'\x01F15264872400\x00\x00\x00\x00',
b'\x01F15264872500\x00\x00\x00\x00',
b'\x01F15264873500\x00\x00\x00\x00',
b'\x01F152648C6300\x00\x00\x00\x00',
+ b'\x01F152648J4000\x00\x00\x00\x00',
+ b'\x01F152648J5000\x00\x00\x00\x00',
+ b'\x01F152648J6000\x00\x00\x00\x00',
],
(Ecu.engine, 0x700, None): [
b'\x01896630E67000\x00\x00\x00\x00',
b'\x01896630EA1000\x00\x00\x00\x00',
b'\x01896630EE4000\x00\x00\x00\x00',
- b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
+ b'\x01896630EE4100\x00\x00\x00\x00',
+ b'\x01896630EE5000\x00\x00\x00\x00',
+ b'\x01896630EE6000\x00\x00\x00\x00',
b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
+ b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
+ b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
- b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301400\x00\x00\x00\x00',
b'\x018821F6201200\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',
+ b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.LEXUS_IS: {
@@ -863,7 +1067,7 @@ FW_VERSIONS = {
b'\x02353P7000\x00\x00\x00\x00\x00\x00\x00\x00530J5000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02353P9000\x00\x00\x00\x00\x00\x00\x00\x00553C1000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152653300\x00\x00\x00\x00\x00\x00',
b'F152653301\x00\x00\x00\x00\x00\x00',
b'F152653310\x00\x00\x00\x00\x00\x00',
@@ -947,7 +1151,7 @@ FW_VERSIONS = {
b'8965B47050\x00\x00\x00\x00\x00\x00',
b'8965B47060\x00\x00\x00\x00\x00\x00', # This is the EPS with good angle sensor
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152647290\x00\x00\x00\x00\x00\x00',
b'F152647300\x00\x00\x00\x00\x00\x00',
b'F152647310\x00\x00\x00\x00\x00\x00',
@@ -988,7 +1192,7 @@ FW_VERSIONS = {
],
},
CAR.PRIUS_V: {
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152647280\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
@@ -1021,7 +1225,7 @@ FW_VERSIONS = {
b'8965B42082\x00\x00\x00\x00\x00\x00',
b'8965B42083\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F15260R102\x00\x00\x00\x00\x00\x00',
b'F15260R103\x00\x00\x00\x00\x00\x00',
b'F152642493\x00\x00\x00\x00\x00\x00',
@@ -1058,7 +1262,7 @@ FW_VERSIONS = {
b'8965B42162\x00\x00\x00\x00\x00\x00',
b'8965B42163\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152642090\x00\x00\x00\x00\x00\x00',
b'F152642110\x00\x00\x00\x00\x00\x00',
b'F152642120\x00\x00\x00\x00\x00\x00',
@@ -1118,20 +1322,24 @@ FW_VERSIONS = {
b'\x02896634A18100\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00',
b'\x02896634A43000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00',
b'\x02896634A47000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00',
+ b'\x028966342Z8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F15260R210\x00\x00\x00\x00\x00\x00',
b'\x01F15260R220\x00\x00\x00\x00\x00\x00',
b'\x01F15260R290\x00\x00\x00\x00\x00\x00',
b'\x01F15260R300\x00\x00\x00\x00\x00\x00',
+ b'\x01F15260R302\x00\x00\x00\x00\x00\x00',
b'\x01F152642551\x00\x00\x00\x00\x00\x00',
b'\x01F152642561\x00\x00\x00\x00\x00\x00',
+ b'\x01F152642601\x00\x00\x00\x00\x00\x00',
b'\x01F152642700\x00\x00\x00\x00\x00\x00',
b'\x01F152642701\x00\x00\x00\x00\x00\x00',
b'\x01F152642710\x00\x00\x00\x00\x00\x00',
b'\x01F152642711\x00\x00\x00\x00\x00\x00',
b'\x01F152642750\x00\x00\x00\x00\x00\x00',
b'\x01F152642751\x00\x00\x00\x00\x00\x00',
+ b'\x01F15260R292\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B42170\x00\x00\x00\x00\x00\x00',
@@ -1157,6 +1365,29 @@ FW_VERSIONS = {
b'\x028646F4203800\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
],
},
+ CAR.RAV4_TSS2_2022: {
+ (Ecu.abs, 0x7b0, None): [
+ b'\x01F15260R350\x00\x00\x00\x00\x00\x00',
+ b'\x01F15260R361\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x700, None): [
+ b'\x01896634AA0000\x00\x00\x00\x00',
+ b'\x01896634AA0100\x00\x00\x00\x00',
+ b'\x01896634AA1000\x00\x00\x00\x00',
+ b'\x01896634A88000\x00\x00\x00\x00',
+ b'\x01896634A89000\x00\x00\x00\x00',
+ b'\x01896634A89100\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F0R01100\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00',
+ ],
+ },
CAR.RAV4H_TSS2: {
(Ecu.engine, 0x700, None): [
b'\x01896634A15000\x00\x00\x00\x00',
@@ -1166,6 +1397,7 @@ FW_VERSIONS = {
b'\x018966342X6000\x00\x00\x00\x00',
b'\x01896634A25000\x00\x00\x00\x00',
b'\x018966342W5000\x00\x00\x00\x00',
+ b'\x018966342W7000\x00\x00\x00\x00',
b'\x028966342W4001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A13000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
@@ -1173,10 +1405,11 @@ FW_VERSIONS = {
b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
+ b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152642291\x00\x00\x00\x00\x00\x00',
b'F152642290\x00\x00\x00\x00\x00\x00',
b'F152642322\x00\x00\x00\x00\x00\x00',
@@ -1214,6 +1447,34 @@ FW_VERSIONS = {
b'\x028646F4203800\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
],
},
+ CAR.RAV4H_TSS2_2022: {
+ (Ecu.abs, 0x7b0, None): [
+ b'\x01F15264283100\x00\x00\x00\x00',
+ b'\x01F15264286200\x00\x00\x00\x00',
+ b'\x01F15264286100\x00\x00\x00\x00',
+ b'\x01F15264283200\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00',
+ b'8965B42182\x00\x00\x00\x00\x00\x00',
+ b'8965B42172\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.engine, 0x700, None): [
+ b'\x01896634A02001\x00\x00\x00\x00',
+ b'\x01896634A03000\x00\x00\x00\x00',
+ b'\x01896634A08000\x00\x00\x00\x00',
+ b'\x01896634A61000\x00\x00\x00\x00',
+ b'\x01896634A62000\x00\x00\x00\x00',
+ b'\x01896634A62100\x00\x00\x00\x00',
+ b'\x01896634A63000\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F0R01100\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00',
+ ],
+ },
CAR.SIENNA: {
(Ecu.engine, 0x700, None): [
b'\x01896630832100\x00\x00\x00\x00',
@@ -1235,7 +1496,7 @@ FW_VERSIONS = {
b'8965B45080\x00\x00\x00\x00\x00\x00',
b'8965B45082\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152608130\x00\x00\x00\x00\x00\x00',
],
(Ecu.dsu, 0x791, None): [
@@ -1254,7 +1515,7 @@ FW_VERSIONS = {
(Ecu.dsu, 0x791, None): [
b'881517601100\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152676144\x00\x00\x00\x00\x00\x00',
],
(Ecu.engine, 0x7e0, None): [
@@ -1269,13 +1530,14 @@ FW_VERSIONS = {
},
CAR.LEXUS_ES_TSS2: {
(Ecu.engine, 0x700, None): [
+ b'\x018966306U6000\x00\x00\x00\x00',
b'\x01896630EC9100\x00\x00\x00\x00',
b'\x018966333T5000\x00\x00\x00\x00',
b'\x018966333T5100\x00\x00\x00\x00',
b'\x018966333X6000\x00\x00\x00\x00',
b'\x01896633T07000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F152606281\x00\x00\x00\x00\x00\x00',
b'\x01F152606340\x00\x00\x00\x00\x00\x00',
b'\x01F152606461\x00\x00\x00\x00\x00\x00',
@@ -1285,18 +1547,21 @@ FW_VERSIONS = {
b'8965B33252\x00\x00\x00\x00\x00\x00',
b'8965B33590\x00\x00\x00\x00\x00\x00',
b'8965B33690\x00\x00\x00\x00\x00\x00',
+ b'8965B33721\x00\x00\x00\x00\x00\x00',
b'8965B48271\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00',
b'\x018821F3301200\x00\x00\x00\x00',
b'\x018821F3301400\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
+ b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
@@ -1307,22 +1572,26 @@ FW_VERSIONS = {
b'\x028966333T0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00',
b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00',
+ b'\x01896633T38000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152633423\x00\x00\x00\x00\x00\x00',
b'F152633680\x00\x00\x00\x00\x00\x00',
b'F152633681\x00\x00\x00\x00\x00\x00',
+ b'F152633F50\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B33252\x00\x00\x00\x00\x00\x00',
b'8965B33590\x00\x00\x00\x00\x00\x00',
b'8965B33690\x00\x00\x00\x00\x00\x00',
+ b'8965B33721\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00',
b'\x018821F3301200\x00\x00\x00\x00',
b'\x018821F3301300\x00\x00\x00\x00',
b'\x018821F3301400\x00\x00\x00\x00',
+ b'\x018821F6201300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
@@ -1331,13 +1600,14 @@ FW_VERSIONS = {
b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
+ b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00',
],
},
CAR.LEXUS_ESH: {
(Ecu.engine, 0x7e0, None): [
b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152633171\x00\x00\x00\x00\x00\x00',
],
(Ecu.dsu, 0x791, None): [
@@ -1363,7 +1633,7 @@ FW_VERSIONS = {
b'\x01896637854000\x00\x00\x00\x00',
b'\x01896637878000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152678130\x00\x00\x00\x00\x00\x00',
b'F152678140\x00\x00\x00\x00\x00\x00',
],
@@ -1387,9 +1657,10 @@ FW_VERSIONS = {
CAR.LEXUS_NX_TSS2: {
(Ecu.engine, 0x700, None): [
b'\x018966378B2100\x00\x00\x00\x00',
+ b'\x018966378B3000\x00\x00\x00\x00',
b'\x018966378G3000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F152678221\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
@@ -1397,12 +1668,31 @@ FW_VERSIONS = {
],
(Ecu.fwdRadar, 0x750, 0xf): [
b"\x018821F3301400\x00\x00\x00\x00",
+ b'\x018821F3301200\x00\x00\x00\x00',
+ b'\x018821F3301300\x00\x00\x00\x00',
],
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F7803100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
+ CAR.LEXUS_NXH_TSS2: {
+ (Ecu.engine, 0x7e0, None): [
+ b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x7b0, None): [
+ b'F152678210\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'8965B78120\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F3301400\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
+ ],
+ },
CAR.LEXUS_NXH: {
(Ecu.engine, 0x7e0, None): [
b'\x0237841000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
@@ -1411,7 +1701,7 @@ FW_VERSIONS = {
b'\x0237882000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0237886000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152678160\x00\x00\x00\x00\x00\x00',
b'F152678170\x00\x00\x00\x00\x00\x00',
b'F152678171\x00\x00\x00\x00\x00\x00',
@@ -1438,7 +1728,7 @@ FW_VERSIONS = {
(Ecu.engine, 0x7e0, None): [
b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152624221\x00\x00\x00\x00\x00\x00',
],
(Ecu.dsu, 0x791, None): [
@@ -1474,7 +1764,7 @@ FW_VERSIONS = {
b'\x018966348R8500\x00\x00\x00\x00',
b'\x018966348W1300\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152648472\x00\x00\x00\x00\x00\x00',
b'F152648473\x00\x00\x00\x00\x00\x00',
b'F152648492\x00\x00\x00\x00\x00\x00',
@@ -1521,7 +1811,7 @@ FW_VERSIONS = {
b'\x02348V6000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02348Z3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152648361\x00\x00\x00\x00\x00\x00',
b'F152648501\x00\x00\x00\x00\x00\x00',
b'F152648502\x00\x00\x00\x00\x00\x00',
@@ -1560,13 +1850,16 @@ FW_VERSIONS = {
b'\x01896630EB0000\x00\x00\x00\x00',
b'\x01896630EC9000\x00\x00\x00\x00',
b'\x01896630ED0000\x00\x00\x00\x00',
+ b'\x01896630ED0100\x00\x00\x00\x00',
b'\x01896630ED6000\x00\x00\x00\x00',
b'\x018966348W5100\x00\x00\x00\x00',
b'\x018966348W9000\x00\x00\x00\x00',
b'\x01896634D12000\x00\x00\x00\x00',
b'\x01896634D12100\x00\x00\x00\x00',
+ b'\x01896634D43000\x00\x00\x00\x00',
+ b'\x01896634D44000\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'\x01F15260E031\x00\x00\x00\x00\x00\x00',
b'\x01F15260E041\x00\x00\x00\x00\x00\x00',
b'\x01F152648781\x00\x00\x00\x00\x00\x00',
@@ -1585,21 +1878,27 @@ FW_VERSIONS = {
b'\x028646F4810100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F4810300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
+ b'\x028646F4810400\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
CAR.LEXUS_RXH_TSS2: {
(Ecu.engine, 0x7e0, None): [
b'\x02348X8000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\x02348Y3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0234D14000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x0234D16000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
+ b'\x02348X4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152648831\x00\x00\x00\x00\x00\x00',
+ b'F152648891\x00\x00\x00\x00\x00\x00',
b'F152648D00\x00\x00\x00\x00\x00\x00',
b'F152648D60\x00\x00\x00\x00\x00\x00',
+ b'F152648811\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7a1, None): [
b'8965B48271\x00\x00\x00\x00\x00\x00',
+ b'8965B48261\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301400\x00\x00\x00\x00',
@@ -1612,6 +1911,7 @@ FW_VERSIONS = {
CAR.PRIUS_TSS2: {
(Ecu.engine, 0x700, None): [
b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
+ b'\x028966347C4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x028966347C6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00',
@@ -1619,7 +1919,7 @@ FW_VERSIONS = {
b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00',
b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00',
],
- (Ecu.esp, 0x7b0, None): [
+ (Ecu.abs, 0x7b0, None): [
b'F152647500\x00\x00\x00\x00\x00\x00',
b'F152647510\x00\x00\x00\x00\x00\x00',
b'F152647520\x00\x00\x00\x00\x00\x00',
@@ -1635,11 +1935,15 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F4707000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F4710000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
+ b'\x028646F4712000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
],
},
CAR.MIRAI: {
- (Ecu.esp, 0x7D1, None): [b'\x01898A36203000\x00\x00\x00\x00',],
- (Ecu.esp, 0x7B0, None): [b'\x01F15266203200\x00\x00\x00\x00',], # a second ESP ECU
+ (Ecu.abs, 0x7D1, None): [b'\x01898A36203000\x00\x00\x00\x00',],
+ (Ecu.abs, 0x7B0, None): [ # a second ABS ECU
+ b'\x01F15266203200\x00\x00\x00\x00',
+ b'\x01F15266203500\x00\x00\x00\x00',
+ ],
(Ecu.eps, 0x7A1, None): [b'\x028965B6204100\x00\x00\x00\x008965B6203100\x00\x00\x00\x00',],
(Ecu.fwdRadar, 0x750, 0xf): [b'\x018821F6201200\x00\x00\x00\x00',],
(Ecu.fwdCamera, 0x750, 0x6d): [b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',],
@@ -1662,6 +1966,23 @@ FW_VERSIONS = {
b'\x028646F5803200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
+ CAR.ALPHARDH_TSS2: {
+ (Ecu.engine, 0x7e0, None): [
+ b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.eps, 0x7a1, None): [
+ b'8965B58040\x00\x00\x00\x00\x00\x00',
+ ],
+ (Ecu.abs, 0x7b0, None): [
+ b'F152658341\x00\x00\x00\x00\x00\x00'
+ ],
+ (Ecu.fwdRadar, 0x750, 0xf): [
+ b'\x018821F3301400\x00\x00\x00\x00',
+ ],
+ (Ecu.fwdCamera, 0x750, 0x6d): [
+ b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
+ ],
+ },
}
STEER_THRESHOLD = 100
@@ -1691,7 +2012,9 @@ DBC = {
CAR.AVALON_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
CAR.AVALONH_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
CAR.AVALON_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
+ CAR.AVALONH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.RAV4_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
+ CAR.RAV4_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None),
CAR.COROLLA_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.COROLLAH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.LEXUS_ES_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
@@ -1701,27 +2024,36 @@ DBC = {
CAR.LEXUS_IS: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'),
CAR.RAV4H_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
+ CAR.RAV4H_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None),
CAR.LEXUS_NXH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.LEXUS_NX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'),
CAR.LEXUS_NX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
+ CAR.LEXUS_NXH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.PRIUS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.MIRAI: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
CAR.ALPHARD_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
+ CAR.ALPHARDH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
}
# These cars have non-standard EPS torque scale factors. All others are 73
EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.PRIUS_V: 100})
# Toyota/Lexus Safety Sense 2.0 and 2.5
-TSS2_CAR = {CAR.RAV4_TSS2, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2,
+TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022,
CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2,
- CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2}
+ CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2}
NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH}
-EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS,
- CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH,
- CAR.LEXUS_RXH_TSS2, CAR.PRIUS_TSS2}
+# the DSU uses the AEB message for longitudinal on these cars
+UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC}
+
+# these cars have a radar which sends ACC messages instead of the camera
+RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022}
+
+EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS,
+ CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH,
+ CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2}
# no resume button press required
NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH}
diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py
index 648f416511..cf1c25e851 100755
--- a/selfdrive/car/vin.py
+++ b/selfdrive/car/vin.py
@@ -1,33 +1,63 @@
#!/usr/bin/env python3
-import traceback
+import re
import cereal.messaging as messaging
-from panda.python.uds import FUNCTIONAL_ADDRS
+from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
-from selfdrive.swaglog import cloudlog
+from selfdrive.car.fw_query_definitions import StdQueries
+from system.swaglog import cloudlog
-VIN_REQUEST = b'\x09\x02'
-VIN_RESPONSE = b'\x49\x02\x01'
VIN_UNKNOWN = "0" * 17
+VIN_RE = "[A-HJ-NPR-Z0-9]{17}"
+
+
+def is_valid_vin(vin: str):
+ return re.fullmatch(VIN_RE, vin) is not None
def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False):
+ addrs = list(range(0x7e0, 0x7e8)) + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) # addrs to process/wait for
+ valid_vin_addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI
for i in range(retry):
- try:
- query = IsoTpParallelQuery(sendcan, logcan, bus, FUNCTIONAL_ADDRS, [VIN_REQUEST], [VIN_RESPONSE], functional_addr=True, debug=debug)
- for addr, vin in query.get_data(timeout).items():
- return addr[0], vin.decode()
- print(f"vin query retry ({i+1}) ...")
- except Exception:
- cloudlog.warning(f"VIN query exception: {traceback.format_exc()}")
+ for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)):
+ try:
+ query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug)
+ results = query.get_data(timeout)
+
+ for addr in valid_vin_addrs:
+ vin = results.get((addr, None))
+ if vin is not None:
+ # Ford pads with null bytes
+ if len(vin) == 24:
+ vin = re.sub(b'\x00*$', b'', vin)
+
+ # Honda Bosch response starts with a length, trim to correct length
+ if vin.startswith(b'\x11'):
+ vin = vin[1:18]
+
+ return get_rx_addr_for_tx_addr(addr), vin.decode()
+
+ cloudlog.error(f"vin query retry ({i+1}) ...")
+ except Exception:
+ cloudlog.exception("VIN query exception")
return 0, VIN_UNKNOWN
if __name__ == "__main__":
+ import argparse
import time
+
+ parser = argparse.ArgumentParser(description='Get VIN of the car')
+ parser.add_argument('--debug', action='store_true')
+ parser.add_argument('--bus', type=int, default=1)
+ parser.add_argument('--timeout', type=float, default=0.1)
+ parser.add_argument('--retry', type=int, default=5)
+ args = parser.parse_args()
+
sendcan = messaging.pub_sock('sendcan')
logcan = messaging.sub_sock('can')
time.sleep(1)
- addr, vin = get_vin(logcan, sendcan, 1, debug=False)
- print(hex(addr), vin)
+
+ vin_rx_addr, vin = get_vin(logcan, sendcan, args.bus, args.timeout, args.retry, debug=args.debug)
+ print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}')
diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py
index f85a81a538..fff5548671 100644
--- a/selfdrive/car/volkswagen/carcontroller.py
+++ b/selfdrive/car/volkswagen/carcontroller.py
@@ -1,34 +1,36 @@
from cereal import car
-from selfdrive.car import apply_std_steer_torque_limits
-from selfdrive.car.volkswagen import volkswagencan
-from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, MQB_LDW_MESSAGES, BUTTON_STATES, CarControllerParams as P
from opendbc.can.packer import CANPacker
+from common.numpy_fast import clip
+from common.conversions import Conversions as CV
+from selfdrive.car import apply_std_steer_torque_limits
+from selfdrive.car.volkswagen import mqbcan, pqcan
+from selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams
VisualAlert = car.CarControl.HUDControl.VisualAlert
+LongCtrlState = car.CarControl.Actuators.LongControlState
-class CarController():
- def __init__(self, dbc_name, CP, VM):
- self.apply_steer_last = 0
- self.packer_pt = CANPacker(DBC_FILES.mqb)
+class CarController:
+ def __init__(self, dbc_name, CP, VM):
+ self.CP = CP
+ self.CCP = CarControllerParams(CP)
+ self.CCS = pqcan if CP.carFingerprint in PQ_CARS else mqbcan
+ self.packer_pt = CANPacker(dbc_name)
+ self.apply_steer_last = 0
+ self.gra_acc_counter_last = None
+ self.frame = 0
self.hcaSameTorqueCount = 0
self.hcaEnabledFrameCount = 0
- self.graButtonStatesToSend = None
- self.graMsgSentCount = 0
- self.graMsgStartFramePrev = 0
- self.graMsgBusCounterPrev = 0
-
- self.steer_rate_limited = False
-
- def update(self, c, enabled, CS, frame, ext_bus, actuators, visual_alert, left_lane_visible, right_lane_visible, left_lane_depart, right_lane_depart):
- """ Controls thread """
+ def update(self, CC, CS, ext_bus):
+ actuators = CC.actuators
+ hud_control = CC.hudControl
can_sends = []
# **** Steering Controls ************************************************ #
- if frame % P.HCA_STEP == 0:
+ if self.frame % self.CCP.HCA_STEP == 0:
# Logic to avoid HCA state 4 "refused":
# * Don't steer unless HCA is in state 3 "ready" or 5 "active"
# * Don't steer at standstill
@@ -39,23 +41,22 @@ class CarController():
# torque value. Do that anytime we happen to have 0 torque, or failing that,
# when exceeding ~1/3 the 360 second timer.
- if c.active and CS.out.vEgo > CS.CP.minSteerSpeed and not (CS.out.standstill or CS.out.steerError or CS.out.steerWarning):
- new_steer = int(round(actuators.steer * P.STEER_MAX))
- apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, P)
- self.steer_rate_limited = new_steer != apply_steer
+ if CC.latActive:
+ new_steer = int(round(actuators.steer * self.CCP.STEER_MAX))
+ apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP)
if apply_steer == 0:
hcaEnabled = False
self.hcaEnabledFrameCount = 0
else:
self.hcaEnabledFrameCount += 1
- if self.hcaEnabledFrameCount >= 118 * (100 / P.HCA_STEP): # 118s
+ if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.HCA_STEP): # 118s
hcaEnabled = False
self.hcaEnabledFrameCount = 0
else:
hcaEnabled = True
if self.apply_steer_last == apply_steer:
self.hcaSameTorqueCount += 1
- if self.hcaSameTorqueCount > 1.9 * (100 / P.HCA_STEP): # 1.9s
+ if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.HCA_STEP): # 1.9s
apply_steer -= (1, -1)[apply_steer < 0]
self.hcaSameTorqueCount = 0
else:
@@ -65,52 +66,44 @@ class CarController():
apply_steer = 0
self.apply_steer_last = apply_steer
- idx = (frame / P.HCA_STEP) % 16
- can_sends.append(volkswagencan.create_mqb_steering_control(self.packer_pt, CANBUS.pt, apply_steer,
- idx, hcaEnabled))
+ can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hcaEnabled))
+
+ # **** Acceleration Controls ******************************************** #
+
+ if self.frame % self.CCP.ACC_CONTROL_STEP == 0 and self.CP.openpilotLongitudinalControl:
+ acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
+ accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0
+ stopping = actuators.longControlState == LongCtrlState.stopping
+ starting = actuators.longControlState == LongCtrlState.starting
+ can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, CANBUS.pt, CS.acc_type, CC.longActive, accel,
+ acc_control, stopping, starting, CS.esp_hold_confirmation))
# **** HUD Controls ***************************************************** #
- if frame % P.LDW_STEP == 0:
- if visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw):
- hud_alert = MQB_LDW_MESSAGES["laneAssistTakeOverSilent"]
- else:
- hud_alert = MQB_LDW_MESSAGES["none"]
-
- can_sends.append(volkswagencan.create_mqb_hud_control(self.packer_pt, CANBUS.pt, enabled,
- CS.out.steeringPressed, hud_alert, left_lane_visible,
- right_lane_visible, CS.ldw_stock_values,
- left_lane_depart, right_lane_depart))
-
- # **** ACC Button Controls ********************************************** #
-
- # FIXME: this entire section is in desperate need of refactoring
-
- if CS.CP.pcmCruise:
- if frame > self.graMsgStartFramePrev + P.GRA_VBP_STEP:
- if not enabled and CS.out.cruiseState.enabled:
- # Cancel ACC if it's engaged with OP disengaged.
- self.graButtonStatesToSend = BUTTON_STATES.copy()
- self.graButtonStatesToSend["cancel"] = True
- elif enabled and CS.esp_hold_confirmation:
- # Blip the Resume button if we're engaged at standstill.
- # FIXME: This is a naive implementation, improve with visiond or radar input.
- self.graButtonStatesToSend = BUTTON_STATES.copy()
- self.graButtonStatesToSend["resumeCruise"] = True
-
- if CS.graMsgBusCounter != self.graMsgBusCounterPrev:
- self.graMsgBusCounterPrev = CS.graMsgBusCounter
- if self.graButtonStatesToSend is not None:
- if self.graMsgSentCount == 0:
- self.graMsgStartFramePrev = frame
- idx = (CS.graMsgBusCounter + 1) % 16
- can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, ext_bus, self.graButtonStatesToSend, CS, idx))
- self.graMsgSentCount += 1
- if self.graMsgSentCount >= P.GRA_VBP_COUNT:
- self.graButtonStatesToSend = None
- self.graMsgSentCount = 0
+ if self.frame % self.CCP.LDW_STEP == 0:
+ hud_alert = 0
+ if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw):
+ hud_alert = self.CCP.LDW_MESSAGES["laneAssistTakeOver"]
+ can_sends.append(self.CCS.create_lka_hud_control(self.packer_pt, CANBUS.pt, CS.ldw_stock_values, CC.enabled,
+ CS.out.steeringPressed, hud_alert, hud_control))
+
+ if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl:
+ acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive)
+ set_speed = hud_control.setSpeed * CV.MS_TO_KPH # FIXME: follow the recent displayed-speed updates, also use mph_kmh toggle to fix display rounding problem?
+ can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, CANBUS.pt, acc_hud_status, set_speed,
+ hud_control.leadVisible))
+
+ # **** Stock ACC Button Controls **************************************** #
+
+ gra_send_ready = self.CP.pcmCruise and CS.gra_stock_values["COUNTER"] != self.gra_acc_counter_last
+ if gra_send_ready and (CC.cruiseControl.cancel or CC.cruiseControl.resume):
+ counter = (CS.gra_stock_values["COUNTER"] + 1) % 16
+ can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, counter,
+ cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume))
new_actuators = actuators.copy()
- new_actuators.steer = self.apply_steer_last / P.STEER_MAX
+ new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX
+ self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"]
+ self.frame += 1
return new_actuators, can_sends
diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py
index 1fe8b56b90..263edbfc9d 100644
--- a/selfdrive/car/volkswagen/carstate.py
+++ b/selfdrive/car/volkswagen/carstate.py
@@ -1,23 +1,37 @@
import numpy as np
from cereal import car
-from selfdrive.config import Conversions as CV
+from common.conversions import Conversions as CV
from selfdrive.car.interfaces import CarStateBase
from opendbc.can.parser import CANParser
-from opendbc.can.can_define import CANDefine
-from selfdrive.car.volkswagen.values import DBC_FILES, CANBUS, NetworkLocation, TransmissionType, GearShifter, BUTTON_STATES, CarControllerParams
+from selfdrive.car.volkswagen.values import DBC, CANBUS, PQ_CARS, NetworkLocation, TransmissionType, GearShifter, \
+ CarControllerParams
+
class CarState(CarStateBase):
def __init__(self, CP):
super().__init__(CP)
- can_define = CANDefine(DBC_FILES.mqb)
- if CP.transmissionType == TransmissionType.automatic:
- self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"]
- elif CP.transmissionType == TransmissionType.direct:
- self.shifter_values = can_define.dv["EV_Gearshift"]["GearPosition"]
- self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"]
- self.buttonStates = BUTTON_STATES.copy()
+ self.CCP = CarControllerParams(CP)
+ self.button_states = {button.event_type: False for button in self.CCP.BUTTONS}
+ self.esp_hold_confirmation = False
+
+ def create_button_events(self, pt_cp, buttons):
+ button_events = []
+
+ for button in buttons:
+ state = pt_cp.vl[button.can_addr][button.can_msg] in button.values
+ if self.button_states[button.event_type] != state:
+ event = car.CarState.ButtonEvent.new_message()
+ event.type = button.event_type
+ event.pressed = state
+ button_events.append(event)
+ self.button_states[button.event_type] = state
+
+ return button_events
def update(self, pt_cp, cam_cp, ext_cp, trans_type):
+ if self.CP.carFingerprint in PQ_CARS:
+ return self.update_pq(pt_cp, cam_cp, ext_cp, trans_type)
+
ret = car.CarState.new_message()
# Update vehicle speed and acceleration from ABS wheel speeds.
ret.wheelSpeeds = self.get_wheel_speeds(
@@ -36,26 +50,28 @@ class CarState(CarStateBase):
ret.steeringAngleDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradwinkel"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradwinkel"])]
ret.steeringRateDeg = pt_cp.vl["LWI_01"]["LWI_Lenkradw_Geschw"] * (1, -1)[int(pt_cp.vl["LWI_01"]["LWI_VZ_Lenkradw_Geschw"])]
ret.steeringTorque = pt_cp.vl["LH_EPS_03"]["EPS_Lenkmoment"] * (1, -1)[int(pt_cp.vl["LH_EPS_03"]["EPS_VZ_Lenkmoment"])]
- ret.steeringPressed = abs(ret.steeringTorque) > CarControllerParams.STEER_DRIVER_ALLOWANCE
+ ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE
ret.yawRate = pt_cp.vl["ESP_02"]["ESP_Gierrate"] * (1, -1)[int(pt_cp.vl["ESP_02"]["ESP_VZ_Gierrate"])] * CV.DEG_TO_RAD
# Verify EPS readiness to accept steering commands
- hca_status = self.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"])
- ret.steerError = hca_status in ("DISABLED", "FAULT")
- ret.steerWarning = hca_status in ("INITIALIZING", "REJECTED")
+ hca_status = self.CCP.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"])
+ ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT")
+ ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED")
# Update gas, brakes, and gearshift.
ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0
ret.gasPressed = ret.gas > 0
ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects
- ret.brakePressed = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"])
- self.esp_hold_confirmation = pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"]
+ brake_pedal_pressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"])
+ brake_pressure_detected = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"])
+ ret.brakePressed = brake_pedal_pressed or brake_pressure_detected
+ ret.parkingBrake = bool(pt_cp.vl["Kombi_01"]["KBI_Handbremse"]) # FIXME: need to include an EPB check as well
# Update gear and/or clutch position data.
if trans_type == TransmissionType.automatic:
- ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["Getriebe_11"]["GE_Fahrstufe"], None))
+ ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Getriebe_11"]["GE_Fahrstufe"], None))
elif trans_type == TransmissionType.direct:
- ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["EV_Gearshift"]["GearPosition"], None))
+ ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["EV_Gearshift"]["GearPosition"], None))
elif trans_type == TransmissionType.manual:
ret.clutchPressed = not pt_cp.vl["Motor_14"]["MO_Kuppl_schalter"]
if bool(pt_cp.vl["Gateway_72"]["BCM1_Rueckfahrlicht_Schalter"]):
@@ -73,11 +89,6 @@ class CarState(CarStateBase):
# Update seatbelt fastened status.
ret.seatbeltUnlatched = pt_cp.vl["Airbag_02"]["AB_Gurtschloss_FA"] != 3
- # Update driver preference for metric. VW stores many different unit
- # preferences, including separate units for for distance vs. speed.
- # We use the speed preference for OP.
- self.displayMetricUnits = not pt_cp.vl["Einheiten_01"]["KBI_MFA_v_Einheit_02"]
-
# Consume blind-spot monitoring info/warning LED states, if available.
# Infostufe: BSM LED on, Warnung: BSM LED flashing
if self.CP.enableBsm:
@@ -97,56 +108,148 @@ class CarState(CarStateBase):
ret.stockAeb = bool(ext_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(ext_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"])
# Update ACC radar status.
- self.tsk_status = pt_cp.vl["TSK_06"]["TSK_Status"]
- if self.tsk_status == 2:
+ self.acc_type = ext_cp.vl["ACC_06"]["ACC_Typ"]
+ if pt_cp.vl["TSK_06"]["TSK_Status"] == 2:
# ACC okay and enabled, but not currently engaged
ret.cruiseState.available = True
ret.cruiseState.enabled = False
- elif self.tsk_status in (3, 4, 5):
- # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or overrun coast-down (5)
+ elif pt_cp.vl["TSK_06"]["TSK_Status"] in (3, 4, 5):
+ # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or brake only (5)
ret.cruiseState.available = True
ret.cruiseState.enabled = True
else:
# ACC okay but disabled (1), or a radar visibility or other fault/disruption (6 or 7)
ret.cruiseState.available = False
ret.cruiseState.enabled = False
+ self.esp_hold_confirmation = bool(pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"])
+ ret.cruiseState.standstill = self.CP.pcmCruise and self.esp_hold_confirmation
+ ret.accFaulted = pt_cp.vl["TSK_06"]["TSK_Status"] in (6, 7)
# Update ACC setpoint. When the setpoint is zero or there's an error, the
# radar sends a set-speed of ~90.69 m/s / 203mph.
if self.CP.pcmCruise:
- ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS
+ ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw_02"] * CV.KPH_TO_MS
if ret.cruiseState.speed > 90:
ret.cruiseState.speed = 0
- # Update control button states for turn signals and ACC controls.
- self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Hoch"])
- self.buttonStates["decelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Runter"])
- self.buttonStates["cancel"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Abbrechen"])
- self.buttonStates["setCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Setzen"])
- self.buttonStates["resumeCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Wiederaufnahme"])
- self.buttonStates["gapAdjustCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Verstellung_Zeitluecke"])
+ # Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough
ret.leftBlinker = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Left"])
ret.rightBlinker = bool(pt_cp.vl["Blinkmodi_02"]["Comfort_Signal_Right"])
-
- # Read ACC hardware button type configuration info that has to pass thru
- # to the radar. Ends up being different for steering wheel buttons vs
- # third stalk type controls.
- self.graHauptschalter = pt_cp.vl["GRA_ACC_01"]["GRA_Hauptschalter"]
- self.graTypHauptschalter = pt_cp.vl["GRA_ACC_01"]["GRA_Typ_Hauptschalter"]
- self.graButtonTypeInfo = pt_cp.vl["GRA_ACC_01"]["GRA_ButtonTypeInfo"]
- self.graTipStufe2 = pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Stufe_2"]
- # Pick up the GRA_ACC_01 CAN message counter so we can sync to it for
- # later cruise-control button spamming.
- self.graMsgBusCounter = pt_cp.vl["GRA_ACC_01"]["COUNTER"]
+ ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS)
+ self.gra_stock_values = pt_cp.vl["GRA_ACC_01"]
# Additional safety checks performed in CarInterface.
- self.parkingBrakeSet = bool(pt_cp.vl["Kombi_01"]["KBI_Handbremse"]) # FIXME: need to include an EPB check as well
ret.espDisabled = pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"] != 0
return ret
+ def update_pq(self, pt_cp, cam_cp, ext_cp, trans_type):
+ ret = car.CarState.new_message()
+ # Update vehicle speed and acceleration from ABS wheel speeds.
+ ret.wheelSpeeds = self.get_wheel_speeds(
+ pt_cp.vl["Bremse_3"]["Radgeschw__VL_4_1"],
+ pt_cp.vl["Bremse_3"]["Radgeschw__VR_4_1"],
+ pt_cp.vl["Bremse_3"]["Radgeschw__HL_4_1"],
+ pt_cp.vl["Bremse_3"]["Radgeschw__HR_4_1"],
+ )
+
+ # vEgo obtained from Bremse_1 vehicle speed rather than Bremse_3 wheel speeds because Bremse_3 isn't present on NSF
+ ret.vEgoRaw = pt_cp.vl["Bremse_1"]["Geschwindigkeit_neu__Bremse_1_"] * CV.KPH_TO_MS
+ ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
+ ret.standstill = ret.vEgo < 0.1
+
+ # Update steering angle, rate, yaw rate, and driver input torque. VW send
+ # the sign/direction in a separate signal so they must be recombined.
+ ret.steeringAngleDeg = pt_cp.vl["Lenkhilfe_3"]["LH3_BLW"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_BLWSign"])]
+ ret.steeringRateDeg = pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit"] * (1, -1)[int(pt_cp.vl["Lenkwinkel_1"]["Lenkradwinkel_Geschwindigkeit_S"])]
+ ret.steeringTorque = pt_cp.vl["Lenkhilfe_3"]["LH3_LM"] * (1, -1)[int(pt_cp.vl["Lenkhilfe_3"]["LH3_LMSign"])]
+ ret.steeringPressed = abs(ret.steeringTorque) > self.CCP.STEER_DRIVER_ALLOWANCE
+ ret.yawRate = pt_cp.vl["Bremse_5"]["Giergeschwindigkeit"] * (1, -1)[int(pt_cp.vl["Bremse_5"]["Vorzeichen_der_Giergeschwindigk"])] * CV.DEG_TO_RAD
+
+ # Verify EPS readiness to accept steering commands
+ hca_status = self.CCP.hca_status_values.get(pt_cp.vl["Lenkhilfe_2"]["LH2_Sta_HCA"])
+ ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT")
+ ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED")
+
+ # Update gas, brakes, and gearshift.
+ ret.gas = pt_cp.vl["Motor_3"]["Fahrpedal_Rohsignal"] / 100.0
+ ret.gasPressed = ret.gas > 0
+ ret.brake = pt_cp.vl["Bremse_5"]["Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects
+ ret.brakePressed = bool(pt_cp.vl["Motor_2"]["Bremslichtschalter"])
+ ret.parkingBrake = bool(pt_cp.vl["Kombi_1"]["Bremsinfo"])
+
+ # Update gear and/or clutch position data.
+ if trans_type == TransmissionType.automatic:
+ ret.gearShifter = self.parse_gear_shifter(self.CCP.shifter_values.get(pt_cp.vl["Getriebe_1"]["Waehlhebelposition__Getriebe_1_"], None))
+ elif trans_type == TransmissionType.manual:
+ ret.clutchPressed = not pt_cp.vl["Motor_1"]["Kupplungsschalter"]
+ reverse_light = bool(pt_cp.vl["Gate_Komf_1"]["GK1_Rueckfahr"])
+ if reverse_light:
+ ret.gearShifter = GearShifter.reverse
+ else:
+ ret.gearShifter = GearShifter.drive
+
+ # Update door and trunk/hatch lid open status.
+ ret.doorOpen = any([pt_cp.vl["Gate_Komf_1"]["GK1_Fa_Tuerkont"],
+ pt_cp.vl["Gate_Komf_1"]["BSK_BT_geoeffnet"],
+ pt_cp.vl["Gate_Komf_1"]["BSK_HL_geoeffnet"],
+ pt_cp.vl["Gate_Komf_1"]["BSK_HR_geoeffnet"],
+ pt_cp.vl["Gate_Komf_1"]["BSK_HD_Hauptraste"]])
+
+ # Update seatbelt fastened status.
+ ret.seatbeltUnlatched = not bool(pt_cp.vl["Airbag_1"]["Gurtschalter_Fahrer"])
+
+ # Consume blind-spot monitoring info/warning LED states, if available.
+ # Infostufe: BSM LED on, Warnung: BSM LED flashing
+ if self.CP.enableBsm:
+ ret.leftBlindspot = bool(ext_cp.vl["SWA_1"]["SWA_Infostufe_SWA_li"]) or bool(ext_cp.vl["SWA_1"]["SWA_Warnung_SWA_li"])
+ ret.rightBlindspot = bool(ext_cp.vl["SWA_1"]["SWA_Infostufe_SWA_re"]) or bool(ext_cp.vl["SWA_1"]["SWA_Warnung_SWA_re"])
+
+ # Consume factory LDW data relevant for factory SWA (Lane Change Assist)
+ # and capture it for forwarding to the blind spot radar controller
+ self.ldw_stock_values = cam_cp.vl["LDW_Status"] if self.CP.networkLocation == NetworkLocation.fwdCamera else {}
+
+ # Stock FCW is considered active if the release bit for brake-jerk warning
+ # is set. Stock AEB considered active if the partial braking or target
+ # braking release bits are set.
+ # Refer to VW Self Study Program 890253: Volkswagen Driver Assistance
+ # Systems, chapters on Front Assist with Braking and City Emergency
+ # Braking for the 2016 Passat NMS
+ # TODO: deferred until we can collect data on pre-MY2016 behavior, AWV message may be shorter with fewer signals
+ ret.stockFcw = False
+ ret.stockAeb = False
+
+ # Update ACC radar status.
+ self.acc_type = ext_cp.vl["ACC_System"]["ACS_Typ_ACC"]
+ ret.cruiseState.available = bool(pt_cp.vl["Motor_5"]["GRA_Hauptschalter"])
+ ret.cruiseState.enabled = pt_cp.vl["Motor_2"]["GRA_Status"] in (1, 2)
+ if self.CP.pcmCruise:
+ ret.accFaulted = ext_cp.vl["ACC_GRA_Anziege"]["ACA_StaACC"] in (6, 7)
+ else:
+ ret.accFaulted = pt_cp.vl["Motor_2"]["GRA_Status"] == 3
+
+ # Update ACC setpoint. When the setpoint reads as 255, the driver has not
+ # yet established an ACC setpoint, so treat it as zero.
+ ret.cruiseState.speed = ext_cp.vl["ACC_GRA_Anziege"]["ACA_V_Wunsch"] * CV.KPH_TO_MS
+ if ret.cruiseState.speed > 70: # 255 kph in m/s == no current setpoint
+ ret.cruiseState.speed = 0
+
+ # Update button states for turn signals and ACC controls, capture all ACC button state/config for passthrough
+ ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_stalk(300, pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_li"],
+ pt_cp.vl["Gate_Komf_1"]["GK1_Blinker_re"])
+ ret.buttonEvents = self.create_button_events(pt_cp, self.CCP.BUTTONS)
+ self.gra_stock_values = pt_cp.vl["GRA_Neu"]
+
+ # Additional safety checks performed in CarInterface.
+ ret.espDisabled = bool(pt_cp.vl["Bremse_1"]["ESP_Passiv_getastet"])
+
+ return ret
+
@staticmethod
def get_can_parser(CP):
+ if CP.carFingerprint in PQ_CARS:
+ return CarState.get_can_parser_pq(CP)
+
signals = [
# sig_name, sig_address
("LWI_Lenkradwinkel", "LWI_01"), # Absolute steering angle
@@ -168,15 +271,15 @@ class CarState(CarStateBase):
("Comfort_Signal_Right", "Blinkmodi_02"), # Right turn signal including comfort blink interval
("AB_Gurtschloss_FA", "Airbag_02"), # Seatbelt status, driver
("AB_Gurtschloss_BF", "Airbag_02"), # Seatbelt status, passenger
- ("ESP_Fahrer_bremst", "ESP_05"), # Brake pedal pressed
- ("ESP_Bremsdruck", "ESP_05"), # Brake pressure applied
+ ("ESP_Fahrer_bremst", "ESP_05"), # Driver applied brake pressure over threshold
+ ("MO_Fahrer_bremst", "Motor_14"), # Brake pedal switch
+ ("ESP_Bremsdruck", "ESP_05"), # Brake pressure
("MO_Fahrpedalrohwert_01", "Motor_20"), # Accelerator pedal value
("EPS_Lenkmoment", "LH_EPS_03"), # Absolute driver torque input
("EPS_VZ_Lenkmoment", "LH_EPS_03"), # Driver torque input sign
("EPS_HCA_Status", "LH_EPS_03"), # EPS HCA control status
("ESP_Tastung_passiv", "ESP_21"), # Stability control disabled
("ESP_Haltebestaetigung", "ESP_21"), # ESP hold confirmation
- ("KBI_MFA_v_Einheit_02", "Einheiten_01"), # MPH vs KMH speed display
("KBI_Handbremse", "Kombi_01"), # Manual handbrake applied
("TSK_Status", "TSK_06"), # ACC engagement status from drivetrain coordinator
("GRA_Hauptschalter", "GRA_ACC_01"), # ACC button, on/off
@@ -187,6 +290,7 @@ class CarState(CarStateBase):
("GRA_Tip_Wiederaufnahme", "GRA_ACC_01"), # ACC button, resume
("GRA_Verstellung_Zeitluecke", "GRA_ACC_01"), # ACC button, time gap adj
("GRA_Typ_Hauptschalter", "GRA_ACC_01"), # ACC main button type
+ ("GRA_Codierung", "GRA_ACC_01"), # ACC button configuration/coding
("GRA_Tip_Stufe_2", "GRA_ACC_01"), # unknown related to stalk type
("GRA_ButtonTypeInfo", "GRA_ACC_01"), # unknown related to stalk type
("COUNTER", "GRA_ACC_01"), # GRA_ACC_01 CAN message counter
@@ -204,10 +308,10 @@ class CarState(CarStateBase):
("ESP_02", 50), # From J104 ABS/ESP controller
("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls)
("Gateway_72", 10), # From J533 CAN gateway (aggregated data)
+ ("Motor_14", 10), # From J623 Engine control module
("Airbag_02", 5), # From J234 Airbag control module
("Kombi_01", 2), # From J285 Instrument cluster
("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active)
- ("Einheiten_01", 1), # From J??? not known if gateway, cluster, or BCM
]
if CP.transmissionType == TransmissionType.automatic:
@@ -219,7 +323,6 @@ class CarState(CarStateBase):
elif CP.transmissionType == TransmissionType.manual:
signals += [("MO_Kuppl_schalter", "Motor_14"), # Clutch switch
("BCM1_Rueckfahrlicht_Schalter", "Gateway_72")] # Reverse light from BCM
- checks.append(("Motor_14", 10)) # From J623 Engine control module
if CP.networkLocation == NetworkLocation.fwdCamera:
# Radars are here on CANBUS.pt
@@ -229,10 +332,13 @@ class CarState(CarStateBase):
signals += MqbExtraSignals.bsm_radar_signals
checks += MqbExtraSignals.bsm_radar_checks
- return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.pt)
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.pt)
@staticmethod
def get_cam_can_parser(CP):
+ if CP.carFingerprint in PQ_CARS:
+ return CarState.get_cam_can_parser_pq(CP)
+
signals = []
checks = []
@@ -257,19 +363,138 @@ class CarState(CarStateBase):
signals += MqbExtraSignals.bsm_radar_signals
checks += MqbExtraSignals.bsm_radar_checks
- return CANParser(DBC_FILES.mqb, signals, checks, CANBUS.cam)
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.cam)
+
+ @staticmethod
+ def get_can_parser_pq(CP):
+ signals = [
+ # sig_name, sig_address, default
+ ("LH3_BLW", "Lenkhilfe_3"), # Absolute steering angle
+ ("LH3_BLWSign", "Lenkhilfe_3"), # Steering angle sign
+ ("LH3_LM", "Lenkhilfe_3"), # Absolute driver torque input
+ ("LH3_LMSign", "Lenkhilfe_3"), # Driver torque input sign
+ ("LH2_Sta_HCA", "Lenkhilfe_2"), # Steering rack HCA status
+ ("Lenkradwinkel_Geschwindigkeit", "Lenkwinkel_1"), # Absolute steering rate
+ ("Lenkradwinkel_Geschwindigkeit_S", "Lenkwinkel_1"), # Steering rate sign
+ ("Geschwindigkeit_neu__Bremse_1_", "Bremse_1"), # Vehicle speed from ABS
+ ("Radgeschw__VL_4_1", "Bremse_3"), # ABS wheel speed, front left
+ ("Radgeschw__VR_4_1", "Bremse_3"), # ABS wheel speed, front right
+ ("Radgeschw__HL_4_1", "Bremse_3"), # ABS wheel speed, rear left
+ ("Radgeschw__HR_4_1", "Bremse_3"), # ABS wheel speed, rear right
+ ("Giergeschwindigkeit", "Bremse_5"), # Absolute yaw rate
+ ("Vorzeichen_der_Giergeschwindigk", "Bremse_5"), # Yaw rate sign
+ ("Gurtschalter_Fahrer", "Airbag_1"), # Seatbelt status, driver
+ ("Gurtschalter_Beifahrer", "Airbag_1"), # Seatbelt status, passenger
+ ("Bremstestschalter", "Motor_2"), # Brake pedal pressed (brake light test switch)
+ ("Bremslichtschalter", "Motor_2"), # Brakes applied (brake light switch)
+ ("Bremsdruck", "Bremse_5"), # Brake pressure applied
+ ("Vorzeichen_Bremsdruck", "Bremse_5"), # Brake pressure applied sign (???)
+ ("Fahrpedal_Rohsignal", "Motor_3"), # Accelerator pedal value
+ ("ESP_Passiv_getastet", "Bremse_1"), # Stability control disabled
+ ("GRA_Hauptschalter", "Motor_5"), # ACC main switch
+ ("GRA_Status", "Motor_2"), # ACC engagement status
+ ("GK1_Fa_Tuerkont", "Gate_Komf_1"), # Door open, driver
+ ("BSK_BT_geoeffnet", "Gate_Komf_1"), # Door open, passenger
+ ("BSK_HL_geoeffnet", "Gate_Komf_1"), # Door open, rear left
+ ("BSK_HR_geoeffnet", "Gate_Komf_1"), # Door open, rear right
+ ("BSK_HD_Hauptraste", "Gate_Komf_1"), # Trunk or hatch open
+ ("GK1_Blinker_li", "Gate_Komf_1"), # Left turn signal on
+ ("GK1_Blinker_re", "Gate_Komf_1"), # Right turn signal on
+ ("Bremsinfo", "Kombi_1"), # Manual handbrake applied
+ ("GRA_Hauptschalt", "GRA_Neu"), # ACC button, on/off
+ ("GRA_Typ_Hauptschalt", "GRA_Neu"), # ACC button, momentary vs latching
+ ("GRA_Kodierinfo", "GRA_Neu"), # ACC button, configuration
+ ("GRA_Abbrechen", "GRA_Neu"), # ACC button, cancel
+ ("GRA_Neu_Setzen", "GRA_Neu"), # ACC button, set
+ ("GRA_Up_lang", "GRA_Neu"), # ACC button, increase or accel, long press
+ ("GRA_Down_lang", "GRA_Neu"), # ACC button, decrease or decel, long press
+ ("GRA_Up_kurz", "GRA_Neu"), # ACC button, increase or accel, short press
+ ("GRA_Down_kurz", "GRA_Neu"), # ACC button, decrease or decel, short press
+ ("GRA_Recall", "GRA_Neu"), # ACC button, resume
+ ("GRA_Zeitluecke", "GRA_Neu"), # ACC button, time gap adj
+ ("COUNTER", "GRA_Neu"), # ACC button, message counter
+ ("GRA_Sender", "GRA_Neu"), # ACC button, CAN message originator
+ ]
+
+ checks = [
+ # sig_address, frequency
+ ("Bremse_1", 100), # From J104 ABS/ESP controller
+ ("Bremse_3", 100), # From J104 ABS/ESP controller
+ ("Lenkhilfe_3", 100), # From J500 Steering Assist with integrated sensors
+ ("Lenkwinkel_1", 100), # From J500 Steering Assist with integrated sensors
+ ("Motor_3", 100), # From J623 Engine control module
+ ("Airbag_1", 50), # From J234 Airbag control module
+ ("Bremse_5", 50), # From J104 ABS/ESP controller
+ ("GRA_Neu", 50), # From J??? steering wheel control buttons
+ ("Kombi_1", 50), # From J285 Instrument cluster
+ ("Motor_2", 50), # From J623 Engine control module
+ ("Motor_5", 50), # From J623 Engine control module
+ ("Lenkhilfe_2", 20), # From J500 Steering Assist with integrated sensors
+ ("Gate_Komf_1", 10), # From J533 CAN gateway
+ ]
+
+ if CP.transmissionType == TransmissionType.automatic:
+ signals += [("Waehlhebelposition__Getriebe_1_", "Getriebe_1", 0)] # Auto trans gear selector position
+ checks += [("Getriebe_1", 100)] # From J743 Auto transmission control module
+ elif CP.transmissionType == TransmissionType.manual:
+ signals += [("Kupplungsschalter", "Motor_1", 0), # Clutch switch
+ ("GK1_Rueckfahr", "Gate_Komf_1", 0)] # Reverse light from BCM
+ checks += [("Motor_1", 100)] # From J623 Engine control module
+
+ if CP.networkLocation == NetworkLocation.fwdCamera:
+ # Extended CAN devices other than the camera are here on CANBUS.pt
+ signals += PqExtraSignals.fwd_radar_signals
+ checks += PqExtraSignals.fwd_radar_checks
+ if CP.enableBsm:
+ signals += PqExtraSignals.bsm_radar_signals
+ checks += PqExtraSignals.bsm_radar_checks
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.pt)
+
+ @staticmethod
+ def get_cam_can_parser_pq(CP):
+
+ signals = []
+ checks = []
+
+ if CP.networkLocation == NetworkLocation.fwdCamera:
+ signals += [
+ # sig_name, sig_address
+ ("LDW_SW_Warnung_links", "LDW_Status"), # Blind spot in warning mode on left side due to lane departure
+ ("LDW_SW_Warnung_rechts", "LDW_Status"), # Blind spot in warning mode on right side due to lane departure
+ ("LDW_Seite_DLCTLC", "LDW_Status"), # Direction of most likely lane departure (left or right)
+ ("LDW_DLC", "LDW_Status"), # Lane departure, distance to line crossing
+ ("LDW_TLC", "LDW_Status"), # Lane departure, time to line crossing
+ ]
+ checks += [
+ # sig_address, frequency
+ ("LDW_Status", 10) # From R242 Driver assistance camera
+ ]
+
+ if CP.networkLocation == NetworkLocation.gateway:
+ # Radars are here on CANBUS.cam
+ signals += PqExtraSignals.fwd_radar_signals
+ checks += PqExtraSignals.fwd_radar_checks
+ if CP.enableBsm:
+ signals += PqExtraSignals.bsm_radar_signals
+ checks += PqExtraSignals.bsm_radar_checks
+
+ return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.cam)
+
class MqbExtraSignals:
# Additional signal and message lists for optional or bus-portable controllers
fwd_radar_signals = [
- ("ACC_Wunschgeschw", "ACC_02"), # ACC set speed
+ ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed
+ ("ACC_Typ", "ACC_06"), # Basic vs FtS vs SnG
("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release
("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release
("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release
]
fwd_radar_checks = [
- ("ACC_10", 50), # From J428 ACC radar control module
- ("ACC_02", 17), # From J428 ACC radar control module
+ ("ACC_06", 50), # From J428 ACC radar control module
+ ("ACC_10", 50), # From J428 ACC radar control module
+ ("ACC_02", 17), # From J428 ACC radar control module
]
bsm_radar_signals = [
("SWA_Infostufe_SWA_li", "SWA_01"), # Blind spot object info, left
@@ -278,5 +503,26 @@ class MqbExtraSignals:
("SWA_Warnung_SWA_re", "SWA_01"), # Blind spot object warning, right
]
bsm_radar_checks = [
- ("SWA_01", 20), # From J1086 Lane Change Assist
+ ("SWA_01", 20), # From J1086 Lane Change Assist
+ ]
+
+class PqExtraSignals:
+ # Additional signal and message lists for optional or bus-portable controllers
+ fwd_radar_signals = [
+ ("ACS_Typ_ACC", "ACC_System"), # Basic vs FtS (no SnG support on PQ)
+ ("ACA_StaACC", "ACC_GRA_Anziege"), # ACC drivetrain coordinator status
+ ("ACA_V_Wunsch", "ACC_GRA_Anziege"), # ACC set speed
+ ]
+ fwd_radar_checks = [
+ ("ACC_System", 50), # From J428 ACC radar control module
+ ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module
+ ]
+ bsm_radar_signals = [
+ ("SWA_Infostufe_SWA_li", "SWA_1"), # Blind spot object info, left
+ ("SWA_Warnung_SWA_li", "SWA_1"), # Blind spot object warning, left
+ ("SWA_Infostufe_SWA_re", "SWA_1"), # Blind spot object info, right
+ ("SWA_Warnung_SWA_re", "SWA_1"), # Blind spot object warning, right
+ ]
+ bsm_radar_checks = [
+ ("SWA_1", 20), # From J1086 Lane Change Assist
]
diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py
index 961cfed7fe..da0ce25afa 100644
--- a/selfdrive/car/volkswagen/interface.py
+++ b/selfdrive/car/volkswagen/interface.py
@@ -1,8 +1,11 @@
from cereal import car
-from selfdrive.car.volkswagen.values import CAR, BUTTON_STATES, CANBUS, NetworkLocation, TransmissionType, GearShifter
-from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
+from panda import Panda
+from common.conversions import Conversions as CV
+from selfdrive.car import STD_CARGO_KG, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
+from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter
+ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
@@ -10,9 +13,6 @@ class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState):
super().__init__(CP, CarController, CarState)
- self.displayMetricUnitsPrev = None
- self.buttonStatesPrev = BUTTON_STATES.copy()
-
if CP.networkLocation == NetworkLocation.fwdCamera:
self.ext_bus = CANBUS.pt
self.cp_ext = self.cp
@@ -21,17 +21,41 @@ class CarInterface(CarInterfaceBase):
self.cp_ext = self.cp_cam
@staticmethod
- def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None):
- ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
+ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.carName = "volkswagen"
ret.radarOffCan = True
- if True: # pylint: disable=using-constant-test
+ use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs
+
+ if candidate in PQ_CARS:
+ # Set global PQ35/PQ46/NMS parameters
+ ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagenPq)]
+ ret.enableBsm = 0x3BA in fingerprint[0] # SWA_1
+
+ if 0x440 in fingerprint[0] or use_off_car_defaults: # Getriebe_1
+ ret.transmissionType = TransmissionType.automatic
+ else:
+ ret.transmissionType = TransmissionType.manual
+
+ if any(msg in fingerprint[1] for msg in (0x1A0, 0xC2)): # Bremse_1, Lenkwinkel_1
+ ret.networkLocation = NetworkLocation.gateway
+ else:
+ ret.networkLocation = NetworkLocation.fwdCamera
+
+ # The PQ port is in dashcam-only mode due to a fixed six-minute maximum timer on HCA steering. An unsupported
+ # EPS flash update to work around this timer, and enable steering down to zero, is available from:
+ # https://github.com/pd0wm/pq-flasher
+ # It is documented in a four-part blog series:
+ # https://blog.willemmelching.nl/carhacking/2022/01/02/vw-part1/
+ # Panda ALLOW_DEBUG firmware required.
+ ret.dashcamOnly = True
+
+ else:
# Set global MQB parameters
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagen)]
ret.enableBsm = 0x30F in fingerprint[0] # SWA_01
- if 0xAD in fingerprint[0]: # Getriebe_11
+ if 0xAD in fingerprint[0] or use_off_car_defaults: # Getriebe_11
ret.transmissionType = TransmissionType.automatic
elif 0x187 in fingerprint[0]: # EV_Gearshift
ret.transmissionType = TransmissionType.direct
@@ -46,16 +70,33 @@ class CarInterface(CarInterfaceBase):
# Global lateral tuning defaults, can be overridden per-vehicle
ret.steerActuatorDelay = 0.1
- ret.steerRateCost = 1.0
ret.steerLimitTimer = 0.4
ret.steerRatio = 15.6 # Let the params learner figure this out
- tire_stiffness_factor = 1.0 # Let the params learner figure this out
ret.lateralTuning.pid.kpBP = [0.]
ret.lateralTuning.pid.kiBP = [0.]
ret.lateralTuning.pid.kf = 0.00006
ret.lateralTuning.pid.kpV = [0.6]
ret.lateralTuning.pid.kiV = [0.2]
+ # Global longitudinal tuning defaults, can be overridden per-vehicle
+
+ ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or use_off_car_defaults
+ if experimental_long:
+ # Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required.
+ ret.openpilotLongitudinalControl = True
+ ret.safetyConfigs[0].safetyParam |= Panda.FLAG_VOLKSWAGEN_LONG_CONTROL
+ if ret.transmissionType == TransmissionType.manual:
+ ret.minEnableSpeed = 4.5
+
+ ret.pcmCruise = not ret.openpilotLongitudinalControl
+ ret.stoppingControl = True
+ ret.startingState = True
+ ret.startAccel = 1.0
+ ret.vEgoStarting = 1.0
+ ret.vEgoStopping = 1.0
+ ret.longitudinalTuning.kpV = [0.1]
+ ret.longitudinalTuning.kiV = [0.0]
+
# Per-chassis tuning values, override tuning defaults here if desired
if candidate == CAR.ARTEON_MK1:
@@ -78,10 +119,24 @@ class CarInterface(CarInterfaceBase):
ret.mass = 1551 + STD_CARGO_KG
ret.wheelbase = 2.79
+ elif candidate == CAR.PASSAT_NMS:
+ ret.mass = 1503 + STD_CARGO_KG
+ ret.wheelbase = 2.80
+ ret.minEnableSpeed = 20 * CV.KPH_TO_MS # ACC "basic", no FtS
+ ret.minSteerSpeed = 50 * CV.KPH_TO_MS
+ ret.steerActuatorDelay = 0.2
+ CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+
elif candidate == CAR.POLO_MK6:
ret.mass = 1230 + STD_CARGO_KG
ret.wheelbase = 2.55
+ elif candidate == CAR.SHARAN_MK2:
+ ret.mass = 1639 + STD_CARGO_KG
+ ret.wheelbase = 2.92
+ ret.minSteerSpeed = 50 * CV.KPH_TO_MS
+ ret.steerActuatorDelay = 0.2
+
elif candidate == CAR.TAOS_MK1:
ret.mass = 1498 + STD_CARGO_KG
ret.wheelbase = 2.69
@@ -154,42 +209,17 @@ class CarInterface(CarInterfaceBase):
else:
raise ValueError(f"unsupported car {candidate}")
- ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
+ ret.autoResumeSng = ret.minEnableSpeed == -1
ret.centerToFront = ret.wheelbase * 0.45
- ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
- tire_stiffness_factor=tire_stiffness_factor)
return ret
# returns a car.CarState
- def update(self, c, can_strings):
- buttonEvents = []
-
- # Process the most recent CAN message traffic, and check for validity
- # The camera CAN has no signals we use at this time, but we process it
- # anyway so we can test connectivity with can_valid
- self.cp.update_strings(can_strings)
- self.cp_cam.update_strings(can_strings)
-
+ def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType)
- ret.canValid = self.cp.can_valid and self.cp_cam.can_valid
- ret.steeringRateLimited = self.CC.steer_rate_limited if self.CC is not None else False
-
- # Check for and process state-change events (button press or release) from
- # the turn stalk switch or ACC steering wheel/control stalk buttons.
- for button in self.CS.buttonStates:
- if self.CS.buttonStates[button] != self.buttonStatesPrev[button]:
- be = car.CarState.ButtonEvent.new_message()
- be.type = button
- be.pressed = self.CS.buttonStates[button]
- buttonEvents.append(be)
-
- events = self.create_common_events(ret, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic])
-
- # Vehicle health and operation safety checks
- if self.CS.parkingBrakeSet:
- events.add(EventName.parkBrake)
- if self.CS.tsk_status in (6, 7):
- events.add(EventName.accFaulted)
+
+ events = self.create_common_events(ret, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
+ pcm_enable=not self.CS.CP.openpilotLongitudinalControl,
+ enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise))
# Low speed steer alert hysteresis logic
if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 1.):
@@ -199,23 +229,15 @@ class CarInterface(CarInterfaceBase):
if self.low_speed_alert:
events.add(EventName.belowSteerSpeed)
- ret.events = events.to_msg()
- ret.buttonEvents = buttonEvents
+ if self.CS.CP.openpilotLongitudinalControl:
+ if ret.vEgo < self.CP.minEnableSpeed + 0.5:
+ events.add(EventName.belowEngageSpeed)
+ if c.enabled and ret.vEgo < self.CP.minEnableSpeed:
+ events.add(EventName.speedTooLow)
- # update previous car states
- self.displayMetricUnitsPrev = self.CS.displayMetricUnits
- self.buttonStatesPrev = self.CS.buttonStates.copy()
+ ret.events = events.to_msg()
- self.CS.out = ret.as_reader()
- return self.CS.out
+ return ret
def apply(self, c):
- hud_control = c.hudControl
- ret = self.CC.update(c, c.enabled, self.CS, self.frame, self.ext_bus, c.actuators,
- hud_control.visualAlert,
- hud_control.leftLaneVisible,
- hud_control.rightLaneVisible,
- hud_control.leftLaneDepart,
- hud_control.rightLaneDepart)
- self.frame += 1
- return ret
+ return self.CC.update(c, self.CS, self.ext_bus)
diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py
new file mode 100644
index 0000000000..25a710dbb8
--- /dev/null
+++ b/selfdrive/car/volkswagen/mqbcan.py
@@ -0,0 +1,108 @@
+def create_steering_control(packer, bus, apply_steer, lkas_enabled):
+ values = {
+ "SET_ME_0X3": 0x3,
+ "Assist_Torque": abs(apply_steer),
+ "Assist_Requested": lkas_enabled,
+ "Assist_VZ": 1 if apply_steer < 0 else 0,
+ "HCA_Available": 1,
+ "HCA_Standby": not lkas_enabled,
+ "HCA_Active": lkas_enabled,
+ "SET_ME_0XFE": 0xFE,
+ "SET_ME_0X07": 0x07,
+ }
+ return packer.make_can_msg("HCA_01", bus, values)
+
+
+def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control):
+ values = ldw_stock_values.copy()
+
+ values.update({
+ "LDW_Status_LED_gelb": 1 if enabled and steering_pressed else 0,
+ "LDW_Status_LED_gruen": 1 if enabled and not steering_pressed else 0,
+ "LDW_Lernmodus_links": 3 if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible,
+ "LDW_Lernmodus_rechts": 3 if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible,
+ "LDW_Texte": hud_alert,
+ })
+ return packer.make_can_msg("LDW_02", bus, values)
+
+
+def create_acc_buttons_control(packer, bus, gra_stock_values, counter, cancel=False, resume=False):
+ values = gra_stock_values.copy()
+
+ values.update({
+ "COUNTER": counter,
+ "GRA_Abbrechen": cancel,
+ "GRA_Tip_Wiederaufnahme": resume,
+ })
+
+ return packer.make_can_msg("GRA_ACC_01", bus, values)
+
+
+def acc_control_value(main_switch_on, acc_faulted, long_active):
+ if acc_faulted:
+ acc_control = 6
+ elif long_active:
+ acc_control = 3
+ elif main_switch_on:
+ acc_control = 2
+ else:
+ acc_control = 0
+
+ return acc_control
+
+
+def acc_hud_status_value(main_switch_on, acc_faulted, long_active):
+ # TODO: happens to resemble the ACC control value for now, but extend this for init/gas override later
+ return acc_control_value(main_switch_on, acc_faulted, long_active)
+
+
+def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, esp_hold):
+ commands = []
+
+ acc_06_values = {
+ "ACC_Typ": acc_type,
+ "ACC_Status_ACC": acc_control,
+ "ACC_StartStopp_Info": enabled,
+ "ACC_Sollbeschleunigung_02": accel if enabled else 3.01,
+ "ACC_zul_Regelabw_unten": 0.2, # TODO: dynamic adjustment of comfort-band
+ "ACC_zul_Regelabw_oben": 0.2, # TODO: dynamic adjustment of comfort-band
+ "ACC_neg_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits
+ "ACC_pos_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits
+ "ACC_Anfahren": starting,
+ "ACC_Anhalten": stopping,
+ }
+ commands.append(packer.make_can_msg("ACC_06", bus, acc_06_values))
+
+ if starting:
+ acc_hold_type = 4 # hold release / startup
+ elif esp_hold:
+ acc_hold_type = 3 # hold standby
+ elif stopping:
+ acc_hold_type = 1 # hold request
+ else:
+ acc_hold_type = 0
+
+ acc_07_values = {
+ "ACC_Anhalteweg": 0.75 if stopping else 20.46, # Distance to stop (stopping coordinator handles terminal roll-out)
+ "ACC_Freilauf_Info": 2 if enabled else 0,
+ "ACC_Folgebeschl": 3.02, # Not using secondary controller accel unless and until we understand its impact
+ "ACC_Sollbeschleunigung_02": accel if enabled else 3.01,
+ "ACC_Anforderung_HMS": acc_hold_type,
+ "ACC_Anfahren": starting,
+ "ACC_Anhalten": stopping,
+ }
+ commands.append(packer.make_can_msg("ACC_07", bus, acc_07_values))
+
+ return commands
+
+
+def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible):
+ values = {
+ "ACC_Status_Anzeige": acc_hud_status,
+ "ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36,
+ "ACC_Gesetzte_Zeitluecke": 3,
+ "ACC_Display_Prio": 3,
+ # TODO: ACC_Abstandsindex for lead car distance, must determine analog vs digital cluster for scaling
+ }
+
+ return packer.make_can_msg("ACC_02", bus, values)
diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py
new file mode 100644
index 0000000000..6beb90c092
--- /dev/null
+++ b/selfdrive/car/volkswagen/pqcan.py
@@ -0,0 +1,91 @@
+def create_steering_control(packer, bus, apply_steer, lkas_enabled):
+ values = {
+ "LM_Offset": abs(apply_steer),
+ "LM_OffSign": 1 if apply_steer < 0 else 0,
+ "HCA_Status": 5 if (lkas_enabled and apply_steer != 0) else 3,
+ "Vib_Freq": 16,
+ }
+
+ return packer.make_can_msg("HCA_1", bus, values)
+
+
+def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control):
+ values = ldw_stock_values.copy()
+
+ values.update({
+ "LDW_Lampe_gelb": 1 if enabled and steering_pressed else 0,
+ "LDW_Lampe_gruen": 1 if enabled and not steering_pressed else 0,
+ "LDW_Lernmodus_links": 3 if hud_control.leftLaneDepart else 1 + hud_control.leftLaneVisible,
+ "LDW_Lernmodus_rechts": 3 if hud_control.rightLaneDepart else 1 + hud_control.rightLaneVisible,
+ "LDW_Textbits": hud_alert,
+ })
+
+ return packer.make_can_msg("LDW_Status", bus, values)
+
+
+def create_acc_buttons_control(packer, bus, gra_stock_values, counter, cancel=False, resume=False):
+ values = gra_stock_values.copy()
+
+ values.update({
+ "COUNTER": counter,
+ "GRA_Abbrechen": cancel,
+ "GRA_Recall": resume,
+ })
+
+ return packer.make_can_msg("GRA_Neu", bus, values)
+
+
+def acc_control_value(main_switch_on, acc_faulted, long_active):
+ if long_active:
+ acc_control = 1
+ elif main_switch_on:
+ acc_control = 2
+ else:
+ acc_control = 0
+
+ return acc_control
+
+
+def acc_hud_status_value(main_switch_on, acc_faulted, long_active):
+ if acc_faulted:
+ hud_status = 6
+ elif long_active:
+ hud_status = 3
+ elif main_switch_on:
+ hud_status = 2
+ else:
+ hud_status = 0
+
+ return hud_status
+
+
+def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, esp_hold):
+ commands = []
+
+ values = {
+ "ACS_Sta_ADR": acc_control,
+ "ACS_StSt_Info": acc_control != 1,
+ "ACS_Typ_ACC": acc_type,
+ "ACS_Anhaltewunsch": acc_type == 1 and stopping,
+ "ACS_Sollbeschl": accel if acc_control == 1 else 3.01,
+ "ACS_zul_Regelabw": 0.2 if acc_control == 1 else 1.27,
+ "ACS_max_AendGrad": 3.0 if acc_control == 1 else 5.08,
+ }
+
+ commands.append(packer.make_can_msg("ACC_System", bus, values))
+
+ return commands
+
+
+def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible):
+ values = {
+ "ACA_StaACC": acc_hud_status,
+ "ACA_Zeitluecke": 2,
+ "ACA_V_Wunsch": set_speed,
+ "ACA_gemZeitl": 8 if lead_visible else 0,
+ # TODO: ACA_ID_StaACC, ACA_AnzDisplay, ACA_kmh_mph, ACA_PrioDisp, ACA_Aend_Zeitluecke
+ # display/display-prio handling probably needed to stop confusing the instrument cluster
+ # kmh_mph handling probably needed to resolve rounding errors in displayed setpoint
+ }
+
+ return packer.make_can_msg("ACC_GRA_Anziege", bus, values)
diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py
index 241338c2c5..18a444bf7c 100755
--- a/selfdrive/car/volkswagen/values.py
+++ b/selfdrive/car/volkswagen/values.py
@@ -1,61 +1,107 @@
-from collections import defaultdict
-from typing import Dict
+from collections import defaultdict, namedtuple
+from dataclasses import dataclass
+from enum import Enum
+from typing import Dict, List, Union
from cereal import car
+from panda.python import uds
+from opendbc.can.can_define import CANDefine
from selfdrive.car import dbc_dict
+from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
+from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16
Ecu = car.CarParams.Ecu
NetworkLocation = car.CarParams.NetworkLocation
TransmissionType = car.CarParams.TransmissionType
GearShifter = car.CarState.GearShifter
+Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
+
class CarControllerParams:
- HCA_STEP = 2 # HCA_01 message frequency 50Hz
- LDW_STEP = 10 # LDW_02 message frequency 10Hz
- GRA_ACC_STEP = 3 # GRA_ACC_01 message frequency 33Hz
-
- GRA_VBP_STEP = 100 # Send ACC virtual button presses once a second
- GRA_VBP_COUNT = 16 # Send VBP messages for ~0.5s (GRA_ACC_STEP * 16)
-
- # Observed documented MQB limits: 3.00 Nm max, rate of change 5.00 Nm/sec.
- # Limiting rate-of-change based on real-world testing and Comma's safety
- # requirements for minimum time to lane departure.
- STEER_MAX = 300 # Max heading control assist torque 3.00 Nm
- STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50))
- STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60))
- STEER_DRIVER_ALLOWANCE = 80
- STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily
- STEER_DRIVER_FACTOR = 1 # from dbc
+ HCA_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz
+ ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz
-class CANBUS:
- pt = 0
- cam = 2
+ ACCEL_MAX = 2.0 # 2.0 m/s max acceleration
+ ACCEL_MIN = -3.5 # 3.5 m/s max deceleration
-class DBC_FILES:
- mqb = "vw_mqb_2010" # Used for all cars with MQB-style CAN messaging
+ def __init__(self, CP):
+ # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec.
+ # MQB vs PQ maximums are shared, but rate-of-change limited differently
+ # based on safety requirements driven by lateral accel testing.
+ self.STEER_MAX = 300 # Max heading control assist torque 3.00 Nm
+ self.STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily
+ self.STEER_DRIVER_FACTOR = 1 # from dbc
-DBC = defaultdict(lambda: dbc_dict(DBC_FILES.mqb, None)) # type: Dict[str, Dict[str, str]]
+ can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
-BUTTON_STATES = {
- "accelCruise": False,
- "decelCruise": False,
- "cancel": False,
- "setCruise": False,
- "resumeCruise": False,
- "gapAdjustCruise": False
-}
+ if CP.carFingerprint in PQ_CARS:
+ self.LDW_STEP = 5 # LDW_1 message frequency 20Hz
+ self.ACC_HUD_STEP = 4 # ACC_GRA_Anziege frequency 25Hz
+ self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm
+ self.STEER_DELTA_UP = 6 # Max HCA reached in 1.00s (STEER_MAX / (50Hz * 1.00))
+ self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60))
+
+ if CP.transmissionType == TransmissionType.automatic:
+ self.shifter_values = can_define.dv["Getriebe_1"]["Waehlhebelposition__Getriebe_1_"]
+ self.hca_status_values = can_define.dv["Lenkhilfe_2"]["LH2_Sta_HCA"]
+
+ self.BUTTONS = [
+ Button(car.CarState.ButtonEvent.Type.setCruise, "GRA_Neu", "GRA_Neu_Setzen", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "GRA_Neu", "GRA_Recall", [1]),
+ Button(car.CarState.ButtonEvent.Type.accelCruise, "GRA_Neu", "GRA_Up_kurz", [1]),
+ Button(car.CarState.ButtonEvent.Type.decelCruise, "GRA_Neu", "GRA_Down_kurz", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "GRA_Neu", "GRA_Abbrechen", [1]),
+ Button(car.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_Neu", "GRA_Zeitluecke", [1]),
+ ]
+
+ self.LDW_MESSAGES = {
+ "none": 0, # Nothing to display
+ "laneAssistUnavail": 1, # "Lane Assist currently not available."
+ "laneAssistUnavailSysError": 2, # "Lane Assist system error"
+ "laneAssistUnavailNoSensorView": 3, # "Lane Assist not available. No sensor view."
+ "laneAssistTakeOver": 4, # "Lane Assist: Please Take Over Steering"
+ "laneAssistDeactivTrailer": 5, # "Lane Assist: no function with trailer"
+ }
+
+ else:
+ self.LDW_STEP = 10 # LDW_02 message frequency 10Hz
+ self.ACC_HUD_STEP = 6 # ACC_02 message frequency 16Hz
+ self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm
+ self.STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50))
+ self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60))
+
+ if CP.transmissionType == TransmissionType.automatic:
+ self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"]
+ elif CP.transmissionType == TransmissionType.direct:
+ self.shifter_values = can_define.dv["EV_Gearshift"]["GearPosition"]
+ self.hca_status_values = can_define.dv["LH_EPS_03"]["EPS_HCA_Status"]
+
+ self.BUTTONS = [
+ Button(car.CarState.ButtonEvent.Type.setCruise, "GRA_ACC_01", "GRA_Tip_Setzen", [1]),
+ Button(car.CarState.ButtonEvent.Type.resumeCruise, "GRA_ACC_01", "GRA_Tip_Wiederaufnahme", [1]),
+ Button(car.CarState.ButtonEvent.Type.accelCruise, "GRA_ACC_01", "GRA_Tip_Hoch", [1]),
+ Button(car.CarState.ButtonEvent.Type.decelCruise, "GRA_ACC_01", "GRA_Tip_Runter", [1]),
+ Button(car.CarState.ButtonEvent.Type.cancel, "GRA_ACC_01", "GRA_Abbrechen", [1]),
+ Button(car.CarState.ButtonEvent.Type.gapAdjustCruise, "GRA_ACC_01", "GRA_Verstellung_Zeitluecke", [1]),
+ ]
+
+ self.LDW_MESSAGES = {
+ "none": 0, # Nothing to display
+ "laneAssistUnavailChime": 1, # "Lane Assist currently not available." with chime
+ "laneAssistUnavailNoSensorChime": 3, # "Lane Assist not available. No sensor view." with chime
+ "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" with urgent beep
+ "emergencyAssistUrgent": 6, # "Emergency Assist: Please Take Over Steering" with urgent beep
+ "laneAssistTakeOverChime": 7, # "Lane Assist: Please Take Over Steering" with chime
+ "laneAssistTakeOver": 8, # "Lane Assist: Please Take Over Steering" silent
+ "emergencyAssistChangingLanes": 9, # "Emergency Assist: Changing lanes..." with urgent beep
+ "laneAssistDeactivated": 10, # "Lane Assist deactivated." silent with persistent icon afterward
+ }
+
+
+class CANBUS:
+ pt = 0
+ cam = 2
-MQB_LDW_MESSAGES = {
- "none": 0, # Nothing to display
- "laneAssistUnavailChime": 1, # "Lane Assist currently not available." with chime
- "laneAssistUnavailNoSensorChime": 3, # "Lane Assist not available. No sensor view." with chime
- "laneAssistTakeOverUrgent": 4, # "Lane Assist: Please Take Over Steering" with urgent beep
- "emergencyAssistUrgent": 6, # "Emergency Assist: Please Take Over Steering" with urgent beep
- "laneAssistTakeOverChime": 7, # "Lane Assist: Please Take Over Steering" with chime
- "laneAssistTakeOverSilent": 8, # "Lane Assist: Please Take Over Steering" silent
- "emergencyAssistChangingLanes": 9, # "Emergency Assist: Changing lanes..." with urgent beep
- "laneAssistDeactivated": 10, # "Lane Assist deactivated." silent with persistent icon afterward
-}
# Check the 7th and 8th characters of the VIN before adding a new CAR. If the
# chassis code is already listed below, don't add a new CAR, just add to the
@@ -68,7 +114,9 @@ class CAR:
GOLF_MK7 = "VOLKSWAGEN GOLF 7TH GEN" # Chassis 5G/AU/BA/BE, Mk7 VW Golf and variants
JETTA_MK7 = "VOLKSWAGEN JETTA 7TH GEN" # Chassis BU, Mk7 VW Jetta
PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 VW Passat and variants
+ PASSAT_NMS = "VOLKSWAGEN PASSAT NMS" # Chassis A3, North America/China/Mideast NMS Passat, incl. facelift
POLO_MK6 = "VOLKSWAGEN POLO 6TH GEN" # Chassis AW, Mk6 VW Polo
+ SHARAN_MK2 = "VOLKSWAGEN SHARAN 2ND GEN" # Chassis 7N, Mk2 Volkswagen Sharan and SEAT Alhambra
TAOS_MK1 = "VOLKSWAGEN TAOS 1ST GEN" # Chassis B2, Mk1 VW Taos and Tharu
TCROSS_MK1 = "VOLKSWAGEN T-CROSS 1ST GEN" # Chassis C1, Mk1 VW T-Cross SWB and LWB variants
TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants
@@ -87,6 +135,114 @@ class CAR:
SKODA_SUPERB_MK3 = "SKODA SUPERB 3RD GEN" # Chassis 3V/NP, Mk3 Skoda Superb and variants
SKODA_OCTAVIA_MK3 = "SKODA OCTAVIA 3RD GEN" # Chassis NE, Mk3 Skoda Octavia and variants
+
+PQ_CARS = {CAR.PASSAT_NMS, CAR.SHARAN_MK2}
+
+
+DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("vw_mqb_2010", None))
+for car_type in PQ_CARS:
+ DBC[car_type] = dbc_dict("vw_golf_mk4", None)
+
+
+class Footnote(Enum):
+ KAMIQ = CarFootnote(
+ "Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.",
+ Column.MODEL)
+ PASSAT = CarFootnote(
+ "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.",
+ Column.MODEL)
+ VW_EXP_LONG = CarFootnote (
+ "Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness " +
+ "are limited to using stock ACC.",
+ Column.LONGITUDINAL)
+ VW_MQB_A0 = CarFootnote(
+ "Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot " +
+ "in software, but doesn't yet have a harness available from the comma store.",
+ Column.HARNESS)
+
+
+@dataclass
+class VWCarInfo(CarInfo):
+ package: str = "Adaptive Cruise Control (ACC) & Lane Assist"
+ harness: Enum = Harness.j533
+
+ def init_make(self, CP: car.CarParams):
+ self.footnotes.insert(0, Footnote.VW_EXP_LONG)
+
+
+CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
+ CAR.ARTEON_MK1: [
+ VWCarInfo("Volkswagen Arteon 2018-22", video_link="https://youtu.be/FAomFKPFlDA"),
+ VWCarInfo("Volkswagen Arteon R 2020-22", video_link="https://youtu.be/FAomFKPFlDA"),
+ VWCarInfo("Volkswagen Arteon eHybrid 2020-22", video_link="https://youtu.be/FAomFKPFlDA"),
+ VWCarInfo("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"),
+ ],
+ CAR.ATLAS_MK1: [
+ VWCarInfo("Volkswagen Atlas 2018-23"),
+ VWCarInfo("Volkswagen Atlas Cross Sport 2021-22"),
+ VWCarInfo("Volkswagen Teramont 2018-22"),
+ VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"),
+ VWCarInfo("Volkswagen Teramont X 2021-22"),
+ ],
+ CAR.GOLF_MK7: [
+ VWCarInfo("Volkswagen e-Golf 2014-20"),
+ VWCarInfo("Volkswagen Golf 2015-20"),
+ VWCarInfo("Volkswagen Golf Alltrack 2015-19"),
+ VWCarInfo("Volkswagen Golf GTD 2015-20"),
+ VWCarInfo("Volkswagen Golf GTE 2015-20"),
+ VWCarInfo("Volkswagen Golf GTI 2015-21"),
+ VWCarInfo("Volkswagen Golf R 2015-19"),
+ VWCarInfo("Volkswagen Golf SportsVan 2015-20"),
+ ],
+ CAR.JETTA_MK7: [
+ VWCarInfo("Volkswagen Jetta 2018-22"),
+ VWCarInfo("Volkswagen Jetta GLI 2021-22"),
+ ],
+ CAR.PASSAT_MK8: [
+ VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]),
+ VWCarInfo("Volkswagen Passat Alltrack 2015-22"),
+ VWCarInfo("Volkswagen Passat GTE 2015-22"),
+ ],
+ CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22"),
+ CAR.POLO_MK6: [
+ VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_MQB_A0]),
+ VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_MQB_A0]),
+ ],
+ CAR.SHARAN_MK2: [
+ VWCarInfo("Volkswagen Sharan 2018-22"),
+ VWCarInfo("SEAT Alhambra 2018-20"),
+ ],
+ CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"),
+ CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]),
+ CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22"),
+ CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"),
+ CAR.TRANSPORTER_T61: [
+ VWCarInfo("Volkswagen Caravelle 2020"),
+ VWCarInfo("Volkswagen California 2021"),
+ ],
+ CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_MQB_A0]),
+ CAR.AUDI_A3_MK3: [
+ VWCarInfo("Audi A3 2014-19"),
+ VWCarInfo("Audi A3 Sportback e-tron 2017-18"),
+ VWCarInfo("Audi RS3 2018"),
+ VWCarInfo("Audi S3 2015-17"),
+ ],
+ CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018"),
+ CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2019-23"),
+ CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"),
+ CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"),
+ CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]),
+ CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"),
+ CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"),
+ CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]),
+ CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"),
+ CAR.SKODA_OCTAVIA_MK3: [
+ VWCarInfo("Škoda Octavia 2015, 2018-19"),
+ VWCarInfo("Škoda Octavia RS 2016"),
+ ],
+}
+
+
# All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars
# with a manual trans won't return transmission firmware, but all other cars will.
#
@@ -96,25 +252,57 @@ class CAR:
# ECU SW part numbers are invalid for vehicle ID and compatibility checks. Try to have
# them repaired by the tuner before including them in openpilot.
+VOLKSWAGEN_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER) + \
+ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION)
+VOLKSWAGEN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
+
+VOLKSWAGEN_RX_OFFSET = 0x6a
+
+FW_QUERY_CONFIG = FwQueryConfig(
+ requests=[
+ Request(
+ [VOLKSWAGEN_VERSION_REQUEST_MULTI],
+ [VOLKSWAGEN_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.srs, Ecu.eps, Ecu.fwdRadar],
+ rx_offset=VOLKSWAGEN_RX_OFFSET,
+ ),
+ Request(
+ [VOLKSWAGEN_VERSION_REQUEST_MULTI],
+ [VOLKSWAGEN_VERSION_RESPONSE],
+ whitelist_ecus=[Ecu.engine, Ecu.transmission],
+ ),
+ ],
+)
+
FW_VERSIONS = {
CAR.ARTEON_MK1: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x873G0906259F \xf1\x890004',
+ b'\xf1\x873G0906259N \xf1\x890004',
b'\xf1\x873G0906259P \xf1\x890001',
+ b'\xf1\x875NA907115H \xf1\x890002',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158L \xf1\x893611',
b'\xf1\x870GC300011L \xf1\x891401',
+ b'\xf1\x870GC300014M \xf1\x892802',
+ b'\xf1\x870GC300040P \xf1\x891401',
],
(Ecu.srs, 0x715, None): [
+ b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121157161111572900',
b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900',
b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\0161812141812171105141123052J00',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1',
+ b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800',
+ b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N',
],
(Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x872Q0907572AA\xf1\x890396',
b'\xf1\x872Q0907572T \xf1\x890383',
b'\xf1\x875Q0907572J \xf1\x890654',
],
@@ -122,31 +310,42 @@ FW_VERSIONS = {
CAR.ATLAS_MK1: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8703H906026AA\xf1\x899970',
+ b'\xf1\x8703H906026AJ\xf1\x890638',
+ b'\xf1\x8703H906026AJ\xf1\x891017',
b'\xf1\x8703H906026AT\xf1\x891922',
+ b'\xf1\x8703H906026BC\xf1\x892664',
b'\xf1\x8703H906026F \xf1\x896696',
b'\xf1\x8703H906026F \xf1\x899970',
b'\xf1\x8703H906026J \xf1\x896026',
b'\xf1\x8703H906026J \xf1\x899971',
b'\xf1\x8703H906026S \xf1\x896693',
b'\xf1\x8703H906026S \xf1\x899970',
+ b'\xf1\x873CN906259 \xf1\x890005',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158A \xf1\x893387',
b'\xf1\x8709G927158DR\xf1\x893536',
+ b'\xf1\x8709G927158DR\xf1\x893742',
+ b'\xf1\x8709G927158F \xf1\x893489',
b'\xf1\x8709G927158FT\xf1\x893835',
+ b'\xf1\x8709G927158GL\xf1\x893939',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x873Q0959655BC\xf1\x890503\xf1\x82\0161914151912001103111122031200',
b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\0162214152212001105141122052900',
b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\0162214152212001105141122052900',
b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105161122052J00',
+ b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1115151112001105171122052J00',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1',
b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1',
+ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105',
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105',
],
(Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x872Q0907572AA\xf1\x890396',
+ b'\xf1\x872Q0907572R \xf1\x890372',
b'\xf1\x872Q0907572T \xf1\x890383',
b'\xf1\x875Q0907572H \xf1\x890620',
b'\xf1\x875Q0907572J \xf1\x890654',
@@ -162,6 +361,7 @@ FW_VERSIONS = {
b'\xf1\x8704E906023BN\xf1\x894518',
b'\xf1\x8704E906024K \xf1\x896811',
b'\xf1\x8704E906027GR\xf1\x892394',
+ b'\xf1\x8704E906027HD\xf1\x892603',
b'\xf1\x8704E906027HD\xf1\x893742',
b'\xf1\x8704E906027MA\xf1\x894958',
b'\xf1\x8704L906021DT\xf1\x895520',
@@ -172,16 +372,21 @@ FW_VERSIONS = {
b'\xf1\x8704L906056CL\xf1\x893823',
b'\xf1\x8704L906056CR\xf1\x895813',
b'\xf1\x8704L906056HE\xf1\x893758',
+ b'\xf1\x8704L906056HN\xf1\x896590',
b'\xf1\x870EA906016A \xf1\x898343',
b'\xf1\x870EA906016E \xf1\x894219',
+ b'\xf1\x870EA906016F \xf1\x894238',
b'\xf1\x870EA906016F \xf1\x895002',
+ b'\xf1\x870EA906016Q \xf1\x895993',
b'\xf1\x870EA906016S \xf1\x897207',
b'\xf1\x875G0906259 \xf1\x890007',
+ b'\xf1\x875G0906259D \xf1\x890002',
b'\xf1\x875G0906259J \xf1\x890002',
b'\xf1\x875G0906259L \xf1\x890002',
b'\xf1\x875G0906259N \xf1\x890003',
b'\xf1\x875G0906259Q \xf1\x890002',
b'\xf1\x875G0906259Q \xf1\x892313',
+ b'\xf1\x875G0906259T \xf1\x890003',
b'\xf1\x878V0906259H \xf1\x890002',
b'\xf1\x878V0906259J \xf1\x890003',
b'\xf1\x878V0906259K \xf1\x890001',
@@ -191,10 +396,12 @@ FW_VERSIONS = {
b'\xf1\x878V0906264L \xf1\x890002',
b'\xf1\x878V0906264M \xf1\x890001',
b'\xf1\x878V09C0BB01 \xf1\x890001',
+ b'\xf1\x8704E906024K \xf1\x899970',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927749AP\xf1\x892943',
b'\xf1\x8709S927158A \xf1\x893585',
+ b'\xf1\x870CW300040H \xf1\x890606',
b'\xf1\x870CW300041H \xf1\x891010',
b'\xf1\x870CW300042F \xf1\x891604',
b'\xf1\x870CW300043B \xf1\x891601',
@@ -208,6 +415,9 @@ FW_VERSIONS = {
b'\xf1\x870D9300012 \xf1\x894937',
b'\xf1\x870D9300012 \xf1\x895045',
b'\xf1\x870D9300014M \xf1\x895004',
+ b'\xf1\x870D9300014Q \xf1\x895006',
+ b'\xf1\x870D9300020J \xf1\x894902',
+ b'\xf1\x870D9300020Q \xf1\x895201',
b'\xf1\x870D9300020S \xf1\x895201',
b'\xf1\x870D9300040A \xf1\x893613',
b'\xf1\x870D9300040S \xf1\x894311',
@@ -221,86 +431,96 @@ FW_VERSIONS = {
b'\xf1\x870GC300020G \xf1\x892401',
b'\xf1\x870GC300020G \xf1\x892403',
b'\xf1\x870GC300020G \xf1\x892404',
+ b'\xf1\x870GC300020N \xf1\x892804',
b'\xf1\x870GC300043T \xf1\x899999',
],
(Ecu.srs, 0x715, None): [
- b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\0211413001113120043114317121C111C9113',
- b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\0211413001113120053114317121C111C9113',
- b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120043114317121C111C9113',
- b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120043114417121411149113',
- b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\0211413001113120053114317121C111C9113',
- b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02314160011123300314211012230229333463100',
- b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\023141600111233003142404A2252229333463100',
- b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\023141600111233003142405A2252229333463100',
- b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\0211413001112120004110415121610169112',
- b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\0211413001113120006110417121A101A9113',
- b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023271112111312--071104171825102591131211',
- b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023271212111312--071104171838103891131211',
- b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023341512112212--071104172328102891131211',
+ b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120043114317121C111C9113',
+ b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120053114317121C111C9113',
+ b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114317121C111C9113',
+ b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114417121411149113',
+ b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113',
+ b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100',
+ b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100',
+ b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100',
+ b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100',
+ b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112',
+ b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113',
+ b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211',
+ b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211',
+ b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211',
- b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\0211413001112120041114115121611169112',
- b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011211200621143171717111791132111',
- b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200061104171724102491132111',
- b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02324230011211200621143171724112491132111',
+ b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\x111413001112120041114115121611169112',
+ b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200621143171717111791132111',
+ b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200061104171724102491132111',
+ b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200621143171724112491132111',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200631143171724122491132111',
- b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\023271200111312--071104171837103791132111',
+ b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\x13271200111312--071104171837103791132111',
b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211',
b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111413001113120006110417121D101D9112',
],
(Ecu.eps, 0x712, None): [
- b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\00561A01612A0',
- b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566A0J612A1',
- b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A00514A1',
- b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A0J712A1',
- b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\00571A0J714A1',
+ b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0',
+ b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1',
+ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1',
+ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1',
+ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1',
+ b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1',
b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1',
- b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\00571A01A18A1',
- b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\00571A0JA16A1',
+ b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1',
+ b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A0JA16A1',
+ b'\xf1\x873QM909144 \xf1\x895072\xf1\x82\x0571A01714A1',
b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820519A9040203',
- b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521A00441A1',
+ b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00441A1',
+ b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00608A1',
b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00641A1',
- b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A00442A1',
- b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A00642A1',
- b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521A07B05A1',
- b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\00521A00602A0',
- b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\00522A00402A0',
+ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00442A1',
+ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00642A1',
+ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A07B05A1',
+ b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00602A0',
+ b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0522A00402A0',
b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0',
- b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511A00403A0',
- b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516A00604A1',
- b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516A00604A1',
- b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516A07A02A1',
- b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521A00507A1',
+ b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0',
+ b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1',
+ b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1',
+ b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1',
+ b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1',
+ b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1',
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1',
- b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521A20B03A1',
+ b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1',
b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1',
b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1',
- b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\00521A00442A1',
- b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\00571A01A16A1',
- b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\00571A01A18A1',
+ b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1',
+ b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1',
+ b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1',
b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1',
- b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\00571A01A18A1',
+ b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A01A18A1',
+ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400',
],
(Ecu.fwdRadar, 0x757, None): [
- b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\00101',
+ b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\x0101',
b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101',
- b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\00101',
- b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101',
- b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\00101',
- b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\00101',
- b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\00101',
- b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101',
+ b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\x0101',
+ b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101',
+ b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\x0101',
+ b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101',
+ b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\x0101',
+ b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101',
b'\xf1\x875Q0907572G \xf1\x890571',
b'\xf1\x875Q0907572H \xf1\x890620',
b'\xf1\x875Q0907572J \xf1\x890654',
b'\xf1\x875Q0907572P \xf1\x890682',
b'\xf1\x875Q0907572R \xf1\x890771',
+ b'\xf1\x875Q0907572S \xf1\x890780',
],
},
CAR.JETTA_MK7: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8704E906024AK\xf1\x899937',
b'\xf1\x8704E906024AS\xf1\x899912',
+ b'\xf1\x8704E906024BC\xf1\x899971',
+ b'\xf1\x8704E906024BG\xf1\x891057',
b'\xf1\x8704E906024B \xf1\x895594',
b'\xf1\x8704E906024C \xf1\x899970',
b'\xf1\x8704E906024L \xf1\x895595',
@@ -311,6 +531,8 @@ FW_VERSIONS = {
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158BQ\xf1\x893545',
b'\xf1\x8709S927158BS\xf1\x893642',
+ b'\xf1\x8709S927158BS\xf1\x893694',
+ b'\xf1\x8709S927158CK\xf1\x893770',
b'\xf1\x8709S927158R \xf1\x893552',
b'\xf1\x8709S927158R \xf1\x893587',
b'\xf1\x870GC300020N \xf1\x892803',
@@ -323,6 +545,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100',
b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02319170031313300314240011550159333463100',
b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100',
+ b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1',
@@ -340,67 +563,119 @@ FW_VERSIONS = {
},
CAR.PASSAT_MK8: {
(Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8703N906026E \xf1\x892114',
b'\xf1\x8704E906023AH\xf1\x893379',
+ b'\xf1\x8704L906026ET\xf1\x891990',
+ b'\xf1\x8704L906026FP\xf1\x892012',
b'\xf1\x8704L906026GA\xf1\x892013',
b'\xf1\x8704L906026KD\xf1\x894798',
b'\xf1\x873G0906264 \xf1\x890004',
],
(Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x870CW300043H \xf1\x891601',
b'\xf1\x870CW300048R \xf1\x890610',
+ b'\xf1\x870D9300013A \xf1\x894905',
b'\xf1\x870D9300014L \xf1\x895002',
+ b'\xf1\x870D9300041A \xf1\x894801',
b'\xf1\x870DD300045T \xf1\x891601',
+ b'\xf1\x870DL300011H \xf1\x895201',
b'\xf1\x870GC300042H \xf1\x891404',
],
(Ecu.srs, 0x715, None): [
+ b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111',
b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311',
+ b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111',
b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211',
b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900',
+ b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111',
],
(Ecu.eps, 0x712, None): [
+ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1',
+ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1',
+ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803',
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1',
b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1',
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1',
+ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600',
],
(Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x873Q0907572A \xf1\x890130',
b'\xf1\x873Q0907572B \xf1\x890192',
b'\xf1\x873Q0907572C \xf1\x890195',
+ b'\xf1\x873Q0907572C \xf1\x890196',
b'\xf1\x875Q0907572R \xf1\x890771',
],
},
+ CAR.PASSAT_NMS: {
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8706K906016C \xf1\x899609',
+ b'\xf1\x8706K906016G \xf1\x891124',
+ b'\xf1\x8706K906071BJ\xf1\x894891',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x8709G927158AB\xf1\x893318',
+ b'\xf1\x8709G927158BD\xf1\x893121',
+ b'\xf1\x8709G927158FQ\xf1\x893745',
+ ],
+ (Ecu.srs, 0x715, None): [
+ b'\xf1\x87561959655 \xf1\x890210\xf1\x82\02212121111113000102011--121012--101312',
+ b'\xf1\x87561959655C \xf1\x890508\xf1\x82\02215141111121100314919--153015--304831',
+ ],
+ (Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x87561907567A \xf1\x890132',
+ b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\00152',
+ ],
+ },
CAR.POLO_MK6: {
(Ecu.engine, 0x7e0, None): [
- b'\xf1\x8704C906025H \xf1\x895177',
+ b'\xf1\x8704C906025H \xf1\x895177',
],
(Ecu.transmission, 0x7e1, None): [
- b'\xf1\x870CW300050D \xf1\x891908',
+ b'\xf1\x870CW300050D \xf1\x891908',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1248130411110416--04040404784811152H14',
],
(Ecu.eps, 0x712, None): [
- b'\xf1\x872Q1909144M \xf1\x896041',
+ b'\xf1\x872Q1909144M \xf1\x896041',
],
(Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572R \xf1\x890372',
],
},
- CAR.TAOS_MK1: {
+ CAR.SHARAN_MK2: {
+ # TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting
(Ecu.engine, 0x7e0, None): [
- b'\xf1\x8705E906013E \xf1\x891624',
+ b'\xf1\x8704L906016HE\xf1\x894635',
],
- (Ecu.transmission, 0x7e1, None): [
- b'\xf1\x8709S927158BL\xf1\x893791',
+ (Ecu.srs, 0x715, None): [
+ b'\xf1\x877N0959655D \xf1\x890016\xf1\x82\x0801100705----10--',
],
(Ecu.fwdRadar, 0x757, None): [
- b'\xf1\x872Q0907572T \xf1\x890383',
+ b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0153',
],
- (Ecu.eps, 0x712, None): [
- b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060605A1',
+ },
+ CAR.TAOS_MK1: {
+ (Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8704E906027NJ\xf1\x891445',
+ b'\xf1\x8705E906013E \xf1\x891624',
+ ],
+ (Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x8709S927158BL\xf1\x893791',
+ b'\xf1\x8709S927158FF\xf1\x893876',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1311111111333500314646021450149333613100',
+ b'\xf1\x875Q0959655CE\xf1\x890421\xf1\x82\x1311110011333300314240021350139333613100',
+ ],
+ (Ecu.eps, 0x712, None): [
+ b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060405A1',
+ b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060605A1',
+ ],
+ (Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x872Q0907572T \xf1\x890383',
],
},
CAR.TCROSS_MK1: {
@@ -422,16 +697,23 @@ FW_VERSIONS = {
},
CAR.TIGUAN_MK2: {
(Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8704E906027NB\xf1\x899504',
b'\xf1\x8704L906026EJ\xf1\x893661',
b'\xf1\x8704L906027G \xf1\x899893',
b'\xf1\x875N0906259 \xf1\x890002',
+ b'\xf1\x875NA907115E \xf1\x890005',
b'\xf1\x8783A907115B \xf1\x890005',
+ b'\xf1\x8783A907115G \xf1\x890001',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158DT\xf1\x893698',
+ b'\xf1\x8709G927158GC\xf1\x893821',
+ b'\xf1\x8709G927158GD\xf1\x893820',
+ b'\xf1\x870D9300043 \xf1\x895202',
b'\xf1\x870DL300011N \xf1\x892001',
b'\xf1\x870DL300011N \xf1\x892012',
b'\xf1\x870DL300013A \xf1\x893005',
+ b'\xf1\x870DL300013G \xf1\x892119',
b'\xf1\x870DL300013G \xf1\x892120',
],
(Ecu.srs, 0x715, None): [
@@ -439,17 +721,25 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100',
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100',
+ b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100',
+ b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100',
+ b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603',
+ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1',
+ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600',
b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1',
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1',
+ b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1',
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1',
],
(Ecu.fwdRadar, 0x757, None): [
+ b'\xf1\x872Q0907572AA\xf1\x890396',
b'\xf1\x872Q0907572J \xf1\x890156',
b'\xf1\x872Q0907572Q \xf1\x890342',
b'\xf1\x872Q0907572R \xf1\x890372',
+ b'\xf1\x872Q0907572T \xf1\x890383',
],
},
CAR.TOURAN_MK2: {
@@ -513,27 +803,40 @@ FW_VERSIONS = {
b'\xf1\x8704E906023BL\xf1\x895190',
b'\xf1\x8704E906027CJ\xf1\x897798',
b'\xf1\x8704L997022N \xf1\x899459',
+ b'\xf1\x875G0906259A \xf1\x890004',
b'\xf1\x875G0906259L \xf1\x890002',
b'\xf1\x875G0906259Q \xf1\x890002',
b'\xf1\x878V0906259F \xf1\x890002',
+ b'\xf1\x878V0906259J \xf1\x890002',
+ b'\xf1\x878V0906259K \xf1\x890001',
b'\xf1\x878V0906264B \xf1\x890003',
b'\xf1\x878V0907115B \xf1\x890007',
+ b'\xf1\x878V0907404A \xf1\x890005',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300044T \xf1\x895245',
b'\xf1\x870CW300048 \xf1\x895201',
b'\xf1\x870D9300012 \xf1\x894912',
+ b'\xf1\x870D9300012K \xf1\x894513',
b'\xf1\x870D9300013B \xf1\x894931',
b'\xf1\x870D9300041N \xf1\x894512',
+ b'\xf1\x870D9300043T \xf1\x899699',
+ b'\xf1\x870DD300046 \xf1\x891604',
b'\xf1\x870DD300046A \xf1\x891602',
b'\xf1\x870DD300046F \xf1\x891602',
b'\xf1\x870DD300046G \xf1\x891601',
+ b'\xf1\x870DL300012E \xf1\x892012',
+ b'\xf1\x870GC300011 \xf1\x890403',
b'\xf1\x870GC300013M \xf1\x892402',
b'\xf1\x870GC300042J \xf1\x891402',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\0211111001111111206110412111321139114',
b'\xf1\x875Q0959655AM\xf1\x890315\xf1\x82\x1311111111111111311411011231129321212100',
+ b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100',
+ b'\xf1\x875Q0959655AR\xf1\x890315\xf1\x82\x1311110011131115311211012331239321212100',
+ b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100',
+ b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221',
@@ -542,9 +845,13 @@ FW_VERSIONS = {
],
(Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1',
+ b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1',
+ b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1',
+ b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1',
b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0',
b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00803A0',
+ b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G0G803A0',
b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516G00804A1',
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521G00807A1',
],
@@ -576,20 +883,24 @@ FW_VERSIONS = {
CAR.AUDI_Q3_MK2: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8705E906018N \xf1\x899970',
+ b'\xf1\x8705L906022M \xf1\x890901',
b'\xf1\x8783A906259 \xf1\x890001',
b'\xf1\x8783A906259 \xf1\x890005',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158CN\xf1\x893608',
+ b'\xf1\x870GC300045D \xf1\x892802',
b'\xf1\x870GC300046F \xf1\x892701',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111',
+ b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111',
b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800',
+ b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60533A1',
],
(Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572R \xf1\x890372',
@@ -660,18 +971,23 @@ FW_VERSIONS = {
CAR.SKODA_KAROQ_MK1: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8705E906018P \xf1\x896020',
+ b'\xf1\x8705L906022BS\xf1\x890913',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300041S \xf1\x891615',
+ b'\xf1\x870GC300014L \xf1\x892802',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\0161213001211001101131122012100',
+ b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567T6100500',
+ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100700',
],
(Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572M \xf1\x890233',
+ b'\xf1\x872Q0907572T \xf1\x890383',
],
},
CAR.SKODA_KODIAQ_MK1: {
@@ -705,11 +1021,13 @@ FW_VERSIONS = {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8704E906016ER\xf1\x895823',
b'\xf1\x8704E906027HD\xf1\x893742',
+ b'\xf1\x8704E906027MH\xf1\x894786',
b'\xf1\x8704L906021DT\xf1\x898127',
b'\xf1\x8704L906026BS\xf1\x891541',
b'\xf1\x875G0906259C \xf1\x890002',
],
(Ecu.transmission, 0x7e1, None): [
+ b'\xf1\x870CW300041L \xf1\x891601',
b'\xf1\x870CW300041N \xf1\x891605',
b'\xf1\x870CW300043B \xf1\x891601',
b'\xf1\x870D9300041C \xf1\x894936',
@@ -722,11 +1040,13 @@ FW_VERSIONS = {
b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200',
b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100',
b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100',
+ b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A01513A1',
b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521T00403A1',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1',
+ b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1',
b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1',
],
(Ecu.fwdRadar, 0x757, None): [
@@ -734,6 +1054,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101',
b'\xf1\x875Q0907572J \xf1\x890654',
b'\xf1\x875Q0907572P \xf1\x890682',
+ b'\xf1\x875Q0907572R \xf1\x890771',
],
},
CAR.SKODA_SCALA_MK1: {
@@ -755,6 +1076,7 @@ FW_VERSIONS = {
},
CAR.SKODA_SUPERB_MK3: {
(Ecu.engine, 0x7e0, None): [
+ b'\xf1\x8704L906026ET\xf1\x891343',
b'\xf1\x8704L906026FP\xf1\x891196',
b'\xf1\x8704L906026KB\xf1\x894071',
b'\xf1\x8704L906026KD\xf1\x894798',
@@ -765,9 +1087,11 @@ FW_VERSIONS = {
b'\xf1\x870CW300042H \xf1\x891601',
b'\xf1\x870D9300011T \xf1\x894801',
b'\xf1\x870D9300012 \xf1\x894940',
+ b'\xf1\x870D9300041H \xf1\x894905',
b'\xf1\x870GC300043 \xf1\x892301',
],
(Ecu.srs, 0x715, None): [
+ b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111',
b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111',
b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111',
b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100',
diff --git a/selfdrive/car/volkswagen/volkswagencan.py b/selfdrive/car/volkswagen/volkswagencan.py
deleted file mode 100644
index 10e0054c79..0000000000
--- a/selfdrive/car/volkswagen/volkswagencan.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# CAN controls for MQB platform Volkswagen, Audi, Skoda and SEAT.
-# PQ35/PQ46/NMS, and any future MLB, to come later.
-
-def create_mqb_steering_control(packer, bus, apply_steer, idx, lkas_enabled):
- values = {
- "SET_ME_0X3": 0x3,
- "Assist_Torque": abs(apply_steer),
- "Assist_Requested": lkas_enabled,
- "Assist_VZ": 1 if apply_steer < 0 else 0,
- "HCA_Available": 1,
- "HCA_Standby": not lkas_enabled,
- "HCA_Active": lkas_enabled,
- "SET_ME_0XFE": 0xFE,
- "SET_ME_0X07": 0x07,
- }
- return packer.make_can_msg("HCA_01", bus, values, idx)
-
-def create_mqb_hud_control(packer, bus, enabled, steering_pressed, hud_alert, left_lane_visible, right_lane_visible,
- ldw_stock_values, left_lane_depart, right_lane_depart):
- # Lane color reference:
- # 0 (LKAS disabled) - off
- # 1 (LKAS enabled, no lane detected) - dark gray
- # 2 (LKAS enabled, lane detected) - light gray on VW, green or white on Audi depending on year or virtual cockpit. On a color MFD on a 2015 A3 TDI it is white, virtual cockpit on a 2018 A3 e-Tron its green.
- # 3 (LKAS enabled, lane departure detected) - white on VW, red on Audi
- values = ldw_stock_values.copy()
- values.update({
- "LDW_Status_LED_gelb": 1 if enabled and steering_pressed else 0,
- "LDW_Status_LED_gruen": 1 if enabled and not steering_pressed else 0,
- "LDW_Lernmodus_links": 3 if left_lane_depart else 1 + left_lane_visible,
- "LDW_Lernmodus_rechts": 3 if right_lane_depart else 1 + right_lane_visible,
- "LDW_Texte": hud_alert,
- })
- return packer.make_can_msg("LDW_02", bus, values)
-
-def create_mqb_acc_buttons_control(packer, bus, buttonStatesToSend, CS, idx):
- values = {
- "GRA_Hauptschalter": CS.graHauptschalter,
- "GRA_Abbrechen": buttonStatesToSend["cancel"],
- "GRA_Tip_Setzen": buttonStatesToSend["setCruise"],
- "GRA_Tip_Hoch": buttonStatesToSend["accelCruise"],
- "GRA_Tip_Runter": buttonStatesToSend["decelCruise"],
- "GRA_Tip_Wiederaufnahme": buttonStatesToSend["resumeCruise"],
- "GRA_Verstellung_Zeitluecke": 3 if buttonStatesToSend["gapAdjustCruise"] else 0,
- "GRA_Typ_Hauptschalter": CS.graTypHauptschalter,
- "GRA_Codierung": 2,
- "GRA_Tip_Stufe_2": CS.graTipStufe2,
- "GRA_ButtonTypeInfo": CS.graButtonTypeInfo
- }
- return packer.make_can_msg("GRA_ACC_01", bus, values, idx)
diff --git a/selfdrive/common/SConscript b/selfdrive/common/SConscript
deleted file mode 100644
index 8dfeecbdd7..0000000000
--- a/selfdrive/common/SConscript
+++ /dev/null
@@ -1,37 +0,0 @@
-Import('env', 'arch', 'SHARED')
-
-if SHARED:
- fxn = env.SharedLibrary
-else:
- fxn = env.Library
-
-common_libs = [
- 'params.cc',
- 'statlog.cc',
- 'swaglog.cc',
- 'util.cc',
- 'gpio.cc',
- 'i2c.cc',
- 'watchdog.cc',
-]
-
-_common = fxn('common', common_libs, LIBS="json11")
-
-files = [
- 'clutil.cc',
- 'visionimg.cc',
-]
-
-if arch == "aarch64":
- _gpu_libs = ['gui', 'adreno_utils']
-elif arch == "larch64":
- _gpu_libs = ["GLESv2"]
-else:
- _gpu_libs = ["GL"]
-
-_gpucommon = fxn('gpucommon', files, LIBS=_gpu_libs)
-Export('_common', '_gpucommon', '_gpu_libs')
-
-if GetOption('test'):
- env.Program('tests/test_util', ['tests/test_util.cc'], LIBS=[_common])
- env.Program('tests/test_swaglog', ['tests/test_swaglog.cc'], LIBS=[_common, 'json11', 'zmq', 'pthread'])
diff --git a/selfdrive/common/gpio.cc b/selfdrive/common/gpio.cc
deleted file mode 100644
index 033d6da4f7..0000000000
--- a/selfdrive/common/gpio.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "selfdrive/common/gpio.h"
-
-#include
-#include
-
-#include
-
-#include "selfdrive/common/util.h"
-
-// We assume that all pins have already been exported on boot,
-// and that we have permission to write to them.
-
-int gpio_init(int pin_nr, bool output) {
- char pin_dir_path[50];
- int pin_dir_path_len = snprintf(pin_dir_path, sizeof(pin_dir_path),
- "/sys/class/gpio/gpio%d/direction", pin_nr);
- if(pin_dir_path_len <= 0) {
- return -1;
- }
- const char *value = output ? "out" : "in";
- return util::write_file(pin_dir_path, (void*)value, strlen(value));
-}
-
-int gpio_set(int pin_nr, bool high) {
- char pin_val_path[50];
- int pin_val_path_len = snprintf(pin_val_path, sizeof(pin_val_path),
- "/sys/class/gpio/gpio%d/value", pin_nr);
- if(pin_val_path_len <= 0) {
- return -1;
- }
- return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1);
-}
diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h
deleted file mode 100644
index 1fcbdac271..0000000000
--- a/selfdrive/common/version.h
+++ /dev/null
@@ -1 +0,0 @@
-#define COMMA_VERSION "0.8.14"
diff --git a/selfdrive/common/visionimg.cc b/selfdrive/common/visionimg.cc
deleted file mode 100644
index a98aabc36c..0000000000
--- a/selfdrive/common/visionimg.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "selfdrive/common/visionimg.h"
-
-#include
-
-#ifdef QCOM
-#include
-#include
-#include
-#include
-#define GL_GLEXT_PROTOTYPES
-#include
-using namespace android;
-
-EGLImageTexture::EGLImageTexture(const VisionBuf *buf) {
- const int bpp = 3;
- assert((buf->len % buf->stride) == 0);
- assert((buf->stride % bpp) == 0);
-
- const int format = HAL_PIXEL_FORMAT_RGB_888;
- private_handle = new private_handle_t(buf->fd, buf->len,
- private_handle_t::PRIV_FLAGS_USES_ION|private_handle_t::PRIV_FLAGS_FRAMEBUFFER,
- 0, format,
- buf->stride/bpp, buf->len/buf->stride,
- buf->width, buf->height);
-
- // GraphicBuffer is ref counted by EGLClientBuffer(ANativeWindowBuffer), no need and not possible to release.
- GraphicBuffer* gb = new GraphicBuffer(buf->width, buf->height, (PixelFormat)format,
- GraphicBuffer::USAGE_HW_TEXTURE, buf->stride/bpp, (private_handle_t*)private_handle, false);
-
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- assert(display != EGL_NO_DISPLAY);
-
- EGLint img_attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
- img_khr = eglCreateImageKHR(display, EGL_NO_CONTEXT,
- EGL_NATIVE_BUFFER_ANDROID, gb->getNativeBuffer(), img_attrs);
- assert(img_khr != EGL_NO_IMAGE_KHR);
-
- glGenTextures(1, &frame_tex);
- glBindTexture(GL_TEXTURE_2D, frame_tex);
- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img_khr);
-}
-
-EGLImageTexture::~EGLImageTexture() {
- glDeleteTextures(1, &frame_tex);
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- assert(display != EGL_NO_DISPLAY);
- eglDestroyImageKHR(display, img_khr);
- delete (private_handle_t*)private_handle;
-}
-
-#else // ifdef QCOM
-
-EGLImageTexture::EGLImageTexture(const VisionBuf *buf) {
- glGenTextures(1, &frame_tex);
- glBindTexture(GL_TEXTURE_2D, frame_tex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, buf->width, buf->height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
- glGenerateMipmap(GL_TEXTURE_2D);
-}
-
-EGLImageTexture::~EGLImageTexture() {
- glDeleteTextures(1, &frame_tex);
-}
-#endif // ifdef QCOM
diff --git a/selfdrive/common/visionimg.h b/selfdrive/common/visionimg.h
deleted file mode 100644
index e8917f3bd6..0000000000
--- a/selfdrive/common/visionimg.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include "cereal/visionipc/visionbuf.h"
-
-#ifdef __APPLE__
-#include
-#else
-#include
-#endif
-
-#ifdef QCOM
-#include
-#define EGL_EGLEXT_PROTOTYPES
-#include
-#undef Status
-#endif
-
-class EGLImageTexture {
- public:
- EGLImageTexture(const VisionBuf *buf);
- ~EGLImageTexture();
- GLuint frame_tex = 0;
-#ifdef QCOM
- void *private_handle = nullptr;
- EGLImageKHR img_khr = 0;
-#endif
-};
diff --git a/selfdrive/common/watchdog.h b/selfdrive/common/watchdog.h
deleted file mode 100644
index 7ed23aa0d9..0000000000
--- a/selfdrive/common/watchdog.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-bool watchdog_kick();
diff --git a/selfdrive/config.py b/selfdrive/config.py
deleted file mode 100644
index 511f6126c4..0000000000
--- a/selfdrive/config.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import numpy as np
-
-class Conversions:
- #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
-
-
-RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car
-RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
-
-class UIParams:
- lidar_x, lidar_y, lidar_zoom = 384, 960, 6
- lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1
- car_hwidth = 1.7272 / 2 * lidar_zoom
- car_front = 2.6924 * lidar_zoom
- car_back = 1.8796 * lidar_zoom
- car_color = 110
diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py
index 1a34d31311..342ecc52fe 100755
--- a/selfdrive/controls/controlsd.py
+++ b/selfdrive/controls/controlsd.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os
import math
-from numbers import Number
+from typing import SupportsFloat
from cereal import car, log
from common.numpy_fast import clip
@@ -9,23 +9,25 @@ from common.realtime import sec_since_boot, config_realtime_process, Priority, R
from common.profiler import Profiler
from common.params import Params, put_nonblocking
import cereal.messaging as messaging
-from selfdrive.config import Conversions as CV
-from selfdrive.swaglog import cloudlog
+from common.conversions import Conversions as CV
+from panda import ALTERNATIVE_EXPERIENCE
+from system.swaglog import cloudlog
+from system.version import is_release_branch, get_short_branch
from selfdrive.boardd.boardd import can_list_to_can_capnp
from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can
-from selfdrive.controls.lib.lane_planner import CAMERA_OFFSET
-from selfdrive.controls.lib.drive_helpers import update_v_cruise, initialize_v_cruise
-from selfdrive.controls.lib.drive_helpers import get_lag_adjusted_curvature
+from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET
+from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature
+from selfdrive.controls.lib.latcontrol import LatControl
from selfdrive.controls.lib.longcontrol import LongControl
from selfdrive.controls.lib.latcontrol_pid import LatControlPID
from selfdrive.controls.lib.latcontrol_indi import LatControlINDI
-from selfdrive.controls.lib.latcontrol_lqr import LatControlLQR
from selfdrive.controls.lib.latcontrol_angle import LatControlAngle
+from selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from selfdrive.controls.lib.events import Events, ET
from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert
from selfdrive.controls.lib.vehicle_model import VehicleModel
from selfdrive.locationd.calibrationd import Calibration
-from selfdrive.hardware import HARDWARE, TICI, EON
+from system.hardware import HARDWARE
from selfdrive.manager.process_config import managed_processes
SOFT_DISABLE_TIME = 3 # seconds
@@ -35,12 +37,9 @@ LANE_DEPARTURE_THRESHOLD = 0.1
REPLAY = "REPLAY" in os.environ
SIMULATION = "SIMULATION" in os.environ
NOSENSOR = "NOSENSOR" in os.environ
-IGNORE_PROCESSES = {"rtshield", "uploader", "deleter", "loggerd", "logmessaged", "tombstoned",
- "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad",
- "statsd", "shutdownd"} | \
- {k for k, v in managed_processes.items() if not v.enabled}
-
-ACTUATOR_FIELDS = set(car.CarControl.Actuators.schema.fields.keys())
+IGNORE_PROCESSES = {"uploader", "deleter", "loggerd", "logmessaged", "tombstoned", "statsd",
+ "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad", "laikad"} | \
+ {k for k, v in managed_processes.items() if not v.enabled}
ThermalStatus = log.DeviceState.ThermalStatus
State = log.ControlsState.OpenpilotState
@@ -49,15 +48,22 @@ Desire = log.LateralPlan.Desire
LaneChangeState = log.LateralPlan.LaneChangeState
LaneChangeDirection = log.LateralPlan.LaneChangeDirection
EventName = car.CarEvent.EventName
-ButtonEvent = car.CarState.ButtonEvent
+ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
-IGNORED_SAFETY_MODES = [SafetyModel.silent, SafetyModel.noOutput]
-CSID_MAP = {"0": EventName.roadCameraError, "1": EventName.wideRoadCameraError, "2": EventName.driverCameraError}
+IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
+CSID_MAP = {"1": EventName.roadCameraError, "2": EventName.wideRoadCameraError, "0": EventName.driverCameraError}
+ACTUATOR_FIELDS = tuple(car.CarControl.Actuators.schema.fields.keys())
+ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding)
+ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES)
+
class Controls:
- def __init__(self, sm=None, pm=None, can_sock=None):
- config_realtime_process(4 if TICI else 3, Priority.CTRL_HIGH)
+ def __init__(self, sm=None, pm=None, can_sock=None, CI=None):
+ config_realtime_process(4, Priority.CTRL_HIGH)
+
+ # Ensure the current branch is cached, otherwise the first iteration of controlsd lags
+ self.branch = get_short_branch("")
# Setup sockets
self.pm = pm
@@ -65,41 +71,54 @@ class Controls:
self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState',
'carControl', 'carEvents', 'carParams'])
- self.camera_packets = ["roadCameraState", "driverCameraState"]
- if TICI:
- self.camera_packets.append("wideRoadCameraState")
+ self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
+
+ self.can_sock = can_sock
+ if can_sock is None:
+ can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 20
+ self.can_sock = messaging.sub_sock('can', timeout=can_timeout)
- params = Params()
- self.joystick_mode = params.get_bool("JoystickDebugMode")
- joystick_packet = ['testJoystick'] if self.joystick_mode else []
+ self.log_sock = messaging.sub_sock('androidLog')
+ self.params = Params()
self.sm = sm
if self.sm is None:
- ignore = ['driverCameraState', 'managerState'] if SIMULATION else None
+ ignore = ['testJoystick']
+ if SIMULATION:
+ ignore += ['driverCameraState', 'managerState']
+ if self.params.get_bool('WideCameraOnly'):
+ ignore += ['roadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
- 'managerState', 'liveParameters', 'radarState'] + self.camera_packets + joystick_packet,
- ignore_alive=ignore, ignore_avg_freq=['radarState', 'longitudinalPlan'])
+ 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'testJoystick'] + self.camera_packets,
+ ignore_alive=ignore, ignore_avg_freq=['radarState', 'longitudinalPlan', 'testJoystick'])
- self.can_sock = can_sock
- if can_sock is None:
- can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 100
- self.can_sock = messaging.sub_sock('can', timeout=can_timeout)
+ if CI is None:
+ # wait for one pandaState and one CAN packet
+ print("Waiting for CAN messages...")
+ get_one_can(self.can_sock)
+
+ num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
+ self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], num_pandas)
+ else:
+ self.CI, self.CP = CI, CI.CP
- if TICI:
- self.log_sock = messaging.sub_sock('androidLog')
+ self.joystick_mode = self.params.get_bool("JoystickDebugMode") or (self.CP.notCar and sm is None)
- # wait for one pandaState and one CAN packet
- print("Waiting for CAN messages...")
- get_one_can(self.can_sock)
+ # set alternative experiences from parameters
+ self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
+ self.CP.alternativeExperience = 0
+ if not self.disengage_on_accelerator:
+ self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
- self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'])
+ if self.CP.dashcamOnly and self.params.get_bool("DashcamOverride"):
+ self.CP.dashcamOnly = False
# read params
- self.is_metric = params.get_bool("IsMetric")
- self.is_ldw_enabled = params.get_bool("IsLdwEnabled")
- openpilot_enabled_toggle = params.get_bool("OpenpilotEnabledToggle")
- passive = params.get_bool("Passive") or not openpilot_enabled_toggle
+ self.is_metric = self.params.get_bool("IsMetric")
+ self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
+ openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle")
+ passive = self.params.get_bool("Passive") or not openpilot_enabled_toggle
# detect sound card presence and ensure successful init
sounds_available = HARDWARE.get_sound_card_online()
@@ -113,46 +132,59 @@ class Controls:
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
self.CP.safetyConfigs = [safety_config]
+ if is_release_branch():
+ self.CP.experimentalLongitudinalAvailable = False
+
# Write CarParams for radard
cp_bytes = self.CP.to_bytes()
- params.put("CarParams", cp_bytes)
+ self.params.put("CarParams", cp_bytes)
put_nonblocking("CarParamsCache", cp_bytes)
+ put_nonblocking("CarParamsPersistent", cp_bytes)
+
+ # cleanup old params
+ if not self.CP.experimentalLongitudinalAvailable:
+ self.params.remove("ExperimentalLongitudinalEnabled")
+ if not self.CP.openpilotLongitudinalControl:
+ self.params.remove("ExperimentalMode")
self.CC = car.CarControl.new_message()
+ self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
self.events = Events()
self.LoC = LongControl(self.CP)
self.VM = VehicleModel(self.CP)
+ self.LaC: LatControl
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
self.LaC = LatControlAngle(self.CP, self.CI)
elif self.CP.lateralTuning.which() == 'pid':
self.LaC = LatControlPID(self.CP, self.CI)
elif self.CP.lateralTuning.which() == 'indi':
self.LaC = LatControlINDI(self.CP, self.CI)
- elif self.CP.lateralTuning.which() == 'lqr':
- self.LaC = LatControlLQR(self.CP, self.CI)
+ elif self.CP.lateralTuning.which() == 'torque':
+ self.LaC = LatControlTorque(self.CP, self.CI)
self.initialized = False
self.state = State.disabled
self.enabled = False
self.active = False
- self.can_rcv_error = False
+ self.can_rcv_timeout = False
self.soft_disable_timer = 0
- self.v_cruise_kph = 255
- self.v_cruise_kph_last = 0
self.mismatch_counter = 0
self.cruise_mismatch_counter = 0
- self.can_rcv_error_counter = 0
+ self.can_rcv_timeout_counter = 0
self.last_blinker_frame = 0
self.distance_traveled = 0
self.last_functional_fan_frame = 0
self.events_prev = []
self.current_alert_types = [ET.PERMANENT]
- self.logged_comm_issue = False
- self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0}
+ self.logged_comm_issue = None
self.last_actuators = car.CarControl.Actuators.new_message()
+ self.steer_limited = False
+ self.desired_curvature = 0.0
+ self.desired_curvature_rate = 0.0
+ self.v_cruise_helper = VCruiseHelper(self.CP)
# TODO: no longer necessary, aside from process replay
self.sm['liveParameters'].valid = True
@@ -177,6 +209,16 @@ class Controls:
self.rk = Ratekeeper(100, print_delay_threshold=None)
self.prof = Profiler(False) # off by default
+ def set_initial_state(self):
+ if REPLAY:
+ controls_state = Params().get("ReplayControlsState")
+ if controls_state is not None:
+ controls_state = log.ControlsState.from_bytes(controls_state)
+ self.v_cruise_helper.v_cruise_kph = controls_state.vCruise
+
+ if any(ps.controlsAllowed for ps in self.sm['pandaStates']):
+ self.state = State.enabled
+
def update_events(self, CS):
"""Compute carEvents from carState"""
@@ -192,30 +234,51 @@ class Controls:
self.events.add(EventName.controlsInitializing)
return
- self.events.add_from_msg(CS.events)
- self.events.add_from_msg(self.sm['driverMonitoringState'].events)
+ # no more events while in dashcam mode
+ if self.read_only:
+ return
+
+ # Block resume if cruise never previously enabled
+ resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
+ if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed:
+ self.events.add(EventName.resumeBlocked)
+
+ # Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
+ if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
+ (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
+ (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
+ self.events.add(EventName.pedalPressed)
+
+ if CS.brakePressed and CS.standstill:
+ self.events.add(EventName.preEnableStandstill)
+
+ if CS.gasPressed:
+ self.events.add(EventName.gasPressedOverride)
+
+ if not self.CP.notCar:
+ self.events.add_from_msg(self.sm['driverMonitoringState'].events)
+
+ # Add car events, ignore if CAN isn't valid
+ if CS.canValid:
+ self.events.add_from_msg(CS.events)
- # Create events for battery, temperature, disk space, and memory
- if EON and (self.sm['peripheralState'].pandaType != PandaType.uno) and \
- self.sm['deviceState'].batteryPercent < 1 and self.sm['deviceState'].chargingError:
- # at zero percent battery, while discharging, OP should not allowed
- self.events.add(EventName.lowBattery)
+ # Create events for temperature, disk space, and memory
if self.sm['deviceState'].thermalStatus >= ThermalStatus.red:
self.events.add(EventName.overheat)
if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION:
# under 7% of space free no enable allowed
self.events.add(EventName.outOfSpace)
# TODO: make tici threshold the same
- if self.sm['deviceState'].memoryUsagePercent > (90 if TICI else 65) and not SIMULATION:
+ if self.sm['deviceState'].memoryUsagePercent > 90 and not SIMULATION:
self.events.add(EventName.lowMemory)
# TODO: enable this once loggerd CPU usage is more reasonable
- #cpus = list(self.sm['deviceState'].cpuUsagePercent)[:(-1 if EON else None)]
+ #cpus = list(self.sm['deviceState'].cpuUsagePercent)
#if max(cpus, default=0) > 95 and not SIMULATION:
# self.events.add(EventName.highCpuUsage)
# Alert if fan isn't spinning for 5 seconds
- if self.sm['peripheralState'].pandaType in (PandaType.uno, PandaType.dos):
+ if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown:
if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50:
if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0:
self.events.add(EventName.fanMalfunction)
@@ -245,44 +308,74 @@ class Controls:
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
- if not CS.canValid:
- self.events.add(EventName.canError)
-
for i, pandaState in enumerate(self.sm['pandaStates']):
# All pandas must match the list of safetyConfigs, and if outside this list, must be silent or noOutput
if i < len(self.CP.safetyConfigs):
safety_mismatch = pandaState.safetyModel != self.CP.safetyConfigs[i].safetyModel or \
pandaState.safetyParam != self.CP.safetyConfigs[i].safetyParam or \
- pandaState.unsafeMode != self.CP.unsafeMode
+ pandaState.alternativeExperience != self.CP.alternativeExperience
else:
safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES
- if safety_mismatch or self.mismatch_counter >= 200:
+ if safety_mismatch or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200:
self.events.add(EventName.controlsMismatch)
if log.PandaState.FaultType.relayMalfunction in pandaState.faults:
self.events.add(EventName.relayMalfunction)
- # Check for HW or system issues
- if len(self.sm['radarState'].radarErrors):
+ # Handle HW and system malfunctions
+ # Order is very intentional here. Be careful when modifying this.
+ # All events here should at least have NO_ENTRY and SOFT_DISABLE.
+ num_events = len(self.events)
+
+ not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
+ if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
+ self.events.add(EventName.processNotRunning)
+ else:
+ if not SIMULATION and not self.rk.lagging:
+ if not self.sm.all_alive(self.camera_packets):
+ self.events.add(EventName.cameraMalfunction)
+ elif not self.sm.all_freq_ok(self.camera_packets):
+ self.events.add(EventName.cameraFrameRate)
+ if self.rk.lagging:
+ self.events.add(EventName.controlsdLagging)
+ if len(self.sm['radarState'].radarErrors) or not self.sm.all_checks(['radarState']):
self.events.add(EventName.radarFault)
- elif not self.sm.valid["pandaStates"]:
+ if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
- elif not self.sm.all_alive_and_valid() or self.can_rcv_error:
- self.events.add(EventName.commIssue)
- if not self.logged_comm_issue:
- invalid = [s for s, valid in self.sm.valid.items() if not valid]
- not_alive = [s for s, alive in self.sm.alive.items() if not alive]
- cloudlog.event("commIssue", invalid=invalid, not_alive=not_alive, can_error=self.can_rcv_error, error=True)
- self.logged_comm_issue = True
+ if CS.canTimeout:
+ self.events.add(EventName.canBusMissing)
+ elif not CS.canValid:
+ self.events.add(EventName.canError)
+
+ # generic catch-all. ideally, a more specific event should be added above instead
+ has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE))
+ no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
+ if (not self.sm.all_checks() or self.can_rcv_timeout) and no_system_errors:
+ if not self.sm.all_alive():
+ self.events.add(EventName.commIssue)
+ elif not self.sm.all_freq_ok():
+ self.events.add(EventName.commIssueAvgFreq)
+ else: # invalid or can_rcv_timeout.
+ self.events.add(EventName.commIssue)
+
+ logs = {
+ 'invalid': [s for s, valid in self.sm.valid.items() if not valid],
+ 'not_alive': [s for s, alive in self.sm.alive.items() if not alive],
+ 'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok],
+ 'can_rcv_timeout': self.can_rcv_timeout,
+ }
+ if logs != self.logged_comm_issue:
+ cloudlog.event("commIssue", error=True, **logs)
+ self.logged_comm_issue = logs
else:
- self.logged_comm_issue = False
+ self.logged_comm_issue = None
if not self.sm['liveParameters'].valid:
self.events.add(EventName.vehicleModelInvalid)
if not self.sm['lateralPlan'].mpcSolutionValid:
self.events.add(EventName.plannerError)
- if not self.sm['liveLocationKalman'].sensorsOK and not NOSENSOR:
+ if not (self.sm['liveParameters'].sensorValid or self.sm['liveLocationKalman'].sensorsOK) and not NOSENSOR:
if self.sm.frame > 5 / DT_CTRL: # Give locationd some time to receive all the inputs
self.events.add(EventName.sensorDataInvalid)
if not self.sm['liveLocationKalman'].posenetOK:
@@ -294,27 +387,26 @@ class Controls:
# Check for mismatch between openpilot and car's PCM
cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise)
self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0
- if self.cruise_mismatch_counter > int(3. / DT_CTRL):
+ if self.cruise_mismatch_counter > int(6. / DT_CTRL):
self.events.add(EventName.cruiseMismatch)
# Check for FCW
- stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.5
+ stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
if planner_fcw or model_fcw:
self.events.add(EventName.fcw)
- if TICI:
- for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
- try:
- msg = m.androidLog.message
- if any(err in msg for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED")):
- csid = msg.split("CSID:")[-1].split(" ")[0]
- evt = CSID_MAP.get(csid, None)
- if evt is not None:
- self.events.add(evt)
- except UnicodeDecodeError:
- pass
+ for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
+ try:
+ msg = m.androidLog.message
+ if any(err in msg for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED")):
+ csid = msg.split("CSID:")[-1].split(" ")[0]
+ evt = CSID_MAP.get(csid, None)
+ if evt is not None:
+ self.events.add(evt)
+ except UnicodeDecodeError:
+ pass
# TODO: fix simulator
if not SIMULATION:
@@ -322,28 +414,12 @@ class Controls:
if not self.sm['liveLocationKalman'].gpsOK and (self.distance_traveled > 1000):
# Not show in first 1 km to allow for driving out of garage. This event shows after 5 minutes
self.events.add(EventName.noGps)
- if not self.sm.all_alive(self.camera_packets):
- self.events.add(EventName.cameraMalfunction)
+
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
if self.sm['liveLocationKalman'].excessiveResets:
self.events.add(EventName.localizerMalfunction)
- # Check if all manager processes are running
- not_running = {p.name for p in self.sm['managerState'].processes if not p.running}
- if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
- self.events.add(EventName.processNotRunning)
-
- # Only allow engagement with brake pressed when stopped behind another stopped car
- speeds = self.sm['longitudinalPlan'].speeds
- if len(speeds) > 1:
- v_future = speeds[-1]
- else:
- v_future = 100.0
- if CS.brakePressed and v_future >= self.CP.vEgoStarting \
- and self.CP.openpilotLongitudinalControl and CS.vEgo < 0.3:
- self.events.add(EventName.noTarget)
-
def data_sample(self):
"""Receive data from sockets and update carState"""
@@ -354,23 +430,22 @@ class Controls:
self.sm.update(0)
if not self.initialized:
- all_valid = CS.canValid and self.sm.all_alive_and_valid()
- if all_valid or self.sm.frame * DT_CTRL > 3.5 or SIMULATION:
+ all_valid = CS.canValid and self.sm.all_checks()
+ timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5)
+ if all_valid or timed_out or SIMULATION:
if not self.read_only:
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
- self.initialized = True
-
- if REPLAY and self.sm['pandaStates'][0].controlsAllowed:
- self.state = State.enabled
+ self.initialized = True
+ self.set_initial_state()
Params().put_bool("ControlsReady", True)
# Check for CAN timeout
if not can_strs:
- self.can_rcv_error_counter += 1
- self.can_rcv_error = True
+ self.can_rcv_timeout_counter += 1
+ self.can_rcv_timeout = True
else:
- self.can_rcv_error = False
+ self.can_rcv_timeout = False
# When the panda and controlsd do not agree on controls_allowed
# we want to disengage openpilot. However the status from the panda goes through
@@ -391,13 +466,7 @@ class Controls:
def state_transition(self, CS):
"""Compute conditional state transitions and execute actions on state transitions"""
- self.v_cruise_kph_last = self.v_cruise_kph
-
- # if stock cruise is completely disabled, then we can use our own set speed logic
- if not self.CP.pcmCruise:
- self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.buttonEvents, self.button_timers, self.enabled, self.is_metric)
- elif CS.cruiseState.enabled:
- self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
+ self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric)
# decrement the soft disable timer at every step, as it's reset on
# entrance in SOFT_DISABLING state
@@ -405,7 +474,7 @@ class Controls:
self.current_alert_types = [ET.PERMANENT]
- # ENABLED, PRE ENABLING, SOFT DISABLING
+ # ENABLED, SOFT DISABLING, PRE ENABLING, OVERRIDING
if self.state != State.disabled:
# user and immediate disable always have priority in a non-disabled state
if self.events.any(ET.USER_DISABLE):
@@ -424,6 +493,10 @@ class Controls:
self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
self.current_alert_types.append(ET.SOFT_DISABLE)
+ elif self.events.any(ET.OVERRIDE_LATERAL) or self.events.any(ET.OVERRIDE_LONGITUDINAL):
+ self.state = State.overriding
+ self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
+
# SOFT DISABLING
elif self.state == State.softDisabling:
if not self.events.any(ET.SOFT_DISABLE):
@@ -443,6 +516,17 @@ class Controls:
else:
self.current_alert_types.append(ET.PRE_ENABLE)
+ # OVERRIDING
+ elif self.state == State.overriding:
+ if self.events.any(ET.SOFT_DISABLE):
+ self.state = State.softDisabling
+ self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
+ self.current_alert_types.append(ET.SOFT_DISABLE)
+ elif not (self.events.any(ET.OVERRIDE_LATERAL) or self.events.any(ET.OVERRIDE_LONGITUDINAL)):
+ self.state = State.enabled
+ else:
+ self.current_alert_types += [ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL]
+
# DISABLED
elif self.state == State.disabled:
if self.events.any(ET.ENABLE):
@@ -452,32 +536,45 @@ class Controls:
else:
if self.events.any(ET.PRE_ENABLE):
self.state = State.preEnabled
+ elif self.events.any(ET.OVERRIDE_LATERAL) or self.events.any(ET.OVERRIDE_LONGITUDINAL):
+ self.state = State.overriding
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
- self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last)
+ self.v_cruise_helper.initialize_v_cruise(CS)
- # Check if actuators are enabled
- self.active = self.state == State.enabled or self.state == State.softDisabling
+ # Check if openpilot is engaged and actuators are enabled
+ self.enabled = self.state in ENABLED_STATES
+ self.active = self.state in ACTIVE_STATES
if self.active:
self.current_alert_types.append(ET.WARNING)
- # Check if openpilot is engaged
- self.enabled = self.active or self.state == State.preEnabled
-
def state_control(self, CS):
- """Given the state, this function returns an actuators packet"""
+ """Given the state, this function returns a CarControl packet"""
# Update VehicleModel
- params = self.sm['liveParameters']
- x = max(params.stiffnessFactor, 0.1)
- sr = max(params.steerRatio, 0.1)
+ lp = self.sm['liveParameters']
+ x = max(lp.stiffnessFactor, 0.1)
+ sr = max(lp.steerRatio, 0.1)
self.VM.update_params(x, sr)
+ # Update Torque Params
+ if self.CP.lateralTuning.which() == 'torque':
+ torque_params = self.sm['liveTorqueParameters']
+ if self.sm.all_checks(['liveTorqueParameters']) and torque_params.useParams:
+ self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered, torque_params.frictionCoefficientFiltered)
+
lat_plan = self.sm['lateralPlan']
long_plan = self.sm['longitudinalPlan']
- actuators = car.CarControl.Actuators.new_message()
+ CC = car.CarControl.new_message()
+ CC.enabled = self.enabled
+ # Check which actuators can be enabled
+ CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
+ CS.vEgo > self.CP.minSteerSpeed and not CS.standstill
+ CC.longActive = self.enabled and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl
+
+ actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state
if CS.leftBlinker or CS.rightBlinker:
@@ -485,46 +582,61 @@ class Controls:
# State specific actions
- if not self.active:
+ if not CC.latActive:
self.LaC.reset()
+ if not CC.longActive:
self.LoC.reset(v_pid=CS.vEgo)
if not self.joystick_mode:
# accel PID loop
- pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_kph * CV.KPH_TO_MS)
+ pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS)
t_since_plan = (self.sm.frame - self.sm.rcv_frame['longitudinalPlan']) * DT_CTRL
- actuators.accel = self.LoC.update(self.active, CS, self.CP, long_plan, pid_accel_limits, t_since_plan)
+ actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan)
# Steering PID loop and lateral MPC
- lat_active = self.active and not CS.steerWarning and not CS.steerError and CS.vEgo > self.CP.minSteerSpeed
- desired_curvature, desired_curvature_rate = get_lag_adjusted_curvature(self.CP, CS.vEgo,
- lat_plan.psis,
- lat_plan.curvatures,
- lat_plan.curvatureRates)
- actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(lat_active, CS, self.CP, self.VM, params, self.last_actuators,
- desired_curvature, desired_curvature_rate)
+ self.desired_curvature, self.desired_curvature_rate = get_lag_adjusted_curvature(self.CP, CS.vEgo,
+ lat_plan.psis,
+ lat_plan.curvatures,
+ lat_plan.curvatureRates)
+ actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
+ self.last_actuators, self.steer_limited, self.desired_curvature,
+ self.desired_curvature_rate, self.sm['liveLocationKalman'])
else:
lac_log = log.ControlsState.LateralDebugState.new_message()
- if self.sm.rcv_frame['testJoystick'] > 0 and self.active:
- actuators.accel = 4.0*clip(self.sm['testJoystick'].axes[0], -1, 1)
+ if self.sm.rcv_frame['testJoystick'] > 0:
+ if CC.longActive:
+ actuators.accel = 4.0*clip(self.sm['testJoystick'].axes[0], -1, 1)
- steer = clip(self.sm['testJoystick'].axes[1], -1, 1)
- # max angle is 45 for angle-based cars
- actuators.steer, actuators.steeringAngleDeg = steer, steer * 45.
+ if CC.latActive:
+ steer = clip(self.sm['testJoystick'].axes[1], -1, 1)
+ # max angle is 45 for angle-based cars
+ actuators.steer, actuators.steeringAngleDeg = steer, steer * 45.
- lac_log.active = True
+ lac_log.active = self.active
lac_log.steeringAngleDeg = CS.steeringAngleDeg
- lac_log.output = steer
- lac_log.saturated = abs(steer) >= 0.9
+ lac_log.output = actuators.steer
+ lac_log.saturated = abs(actuators.steer) >= 0.9
# Send a "steering required alert" if saturation count has reached the limit
- if lac_log.active and lac_log.saturated and not CS.steeringPressed:
+ if lac_log.active and not CS.steeringPressed and self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
+ undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
+ turning = abs(lac_log.desiredLateralAccel) > 1.0
+ good_speed = CS.vEgo > 5
+ max_torque = abs(self.last_actuators.steer) > 0.99
+ if undershooting and turning and good_speed and max_torque:
+ self.events.add(EventName.steerSaturated)
+ elif lac_log.active and not CS.steeringPressed and lac_log.saturated:
dpath_points = lat_plan.dPathPoints
if len(dpath_points):
# Check if we deviated from the path
# TODO use desired vs actual curvature
- left_deviation = actuators.steer > 0 and dpath_points[0] < -0.20
- right_deviation = actuators.steer < 0 and dpath_points[0] > 0.20
+ if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
+ steering_value = actuators.steeringAngleDeg
+ else:
+ steering_value = actuators.steer
+
+ left_deviation = steering_value > 0 and dpath_points[0] < -0.20
+ right_deviation = steering_value < 0 and dpath_points[0] > 0.20
if left_deviation or right_deviation:
self.events.add(EventName.steerSaturated)
@@ -532,44 +644,38 @@ class Controls:
# Ensure no NaNs/Infs
for p in ACTUATOR_FIELDS:
attr = getattr(actuators, p)
- if not isinstance(attr, Number):
+ if not isinstance(attr, SupportsFloat):
continue
if not math.isfinite(attr):
cloudlog.error(f"actuators.{p} not finite {actuators.to_dict()}")
setattr(actuators, p, 0.0)
- return actuators, lac_log
-
- def update_button_timers(self, buttonEvents):
- # increment timer for buttons still pressed
- for k in self.button_timers:
- if self.button_timers[k] > 0:
- self.button_timers[k] += 1
-
- for b in buttonEvents:
- if b.type.raw in self.button_timers:
- self.button_timers[b.type.raw] = 1 if b.pressed else 0
+ return CC, lac_log
- def publish_logs(self, CS, start_time, actuators, lac_log):
+ def publish_logs(self, CS, start_time, CC, lac_log):
"""Send actuators and hud commands to the car, send controlsstate and MPC logging"""
- CC = car.CarControl.new_message()
- CC.enabled = self.enabled
- CC.active = self.active
- CC.actuators = actuators
-
- orientation_value = self.sm['liveLocationKalman'].orientationNED.value
+ # Orientation and angle rates can be useful for carcontroller
+ # Only calibrated (car) frame is relevant for the carcontroller
+ orientation_value = list(self.sm['liveLocationKalman'].calibratedOrientationNED.value)
if len(orientation_value) > 2:
- CC.roll = orientation_value[0]
- CC.pitch = orientation_value[1]
+ CC.orientationNED = orientation_value
+ angular_rate_value = list(self.sm['liveLocationKalman'].angularVelocityCalibrated.value)
+ if len(angular_rate_value) > 2:
+ CC.angularVelocity = angular_rate_value
+ CC.cruiseControl.override = self.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl
CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise)
if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]:
CC.cruiseControl.cancel = True
+ speeds = self.sm['longitudinalPlan'].speeds
+ if len(speeds):
+ CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1
+
hudControl = CC.hudControl
- hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS)
+ hudControl.setSpeed = float(self.v_cruise_helper.v_cruise_cluster_kph * CV.KPH_TO_MS)
hudControl.speedVisible = self.enabled
hudControl.lanesVisible = self.enabled
hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead
@@ -579,13 +685,13 @@ class Controls:
recent_blinker = (self.sm.frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown
ldw_allowed = self.is_ldw_enabled and CS.vEgo > LDW_MIN_SPEED and not recent_blinker \
- and not self.active and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED
+ and not CC.latActive and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED
model_v2 = self.sm['modelV2']
desire_prediction = model_v2.meta.desirePrediction
if len(desire_prediction) and ldw_allowed:
- right_lane_visible = self.sm['lateralPlan'].rProb > 0.5
- left_lane_visible = self.sm['lateralPlan'].lProb > 0.5
+ right_lane_visible = model_v2.laneLineProbs[2] > 0.5
+ left_lane_visible = model_v2.laneLineProbs[1] > 0.5
l_lane_change_prob = desire_prediction[Desire.laneChangeLeft - 1]
r_lane_change_prob = desire_prediction[Desire.laneChangeRight - 1]
@@ -605,7 +711,7 @@ class Controls:
if self.enabled:
clear_event_types.add(ET.NO_ENTRY)
- alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric, self.soft_disable_timer])
+ alerts = self.events.create_alerts(self.current_alert_types, [self.CP, CS, self.sm, self.is_metric, self.soft_disable_timer])
self.AM.add_many(self.sm.frame, alerts)
current_alert = self.AM.process_alerts(self.sm.frame, clear_event_types)
if current_alert:
@@ -616,15 +722,16 @@ class Controls:
self.last_actuators, can_sends = self.CI.apply(CC)
self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid))
CC.actuatorsOutput = self.last_actuators
+ self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2
force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \
(self.state == State.softDisabling)
# Curvature & Steering angle
- params = self.sm['liveParameters']
+ lp = self.sm['liveParameters']
- steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetDeg)
- curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, params.roll)
+ steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg)
+ curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll)
# controlsState
dat = messaging.new_message('controlsState')
@@ -645,18 +752,22 @@ class Controls:
controlsState.enabled = self.enabled
controlsState.active = self.active
controlsState.curvature = curvature
+ controlsState.desiredCurvature = self.desired_curvature
+ controlsState.desiredCurvatureRate = self.desired_curvature_rate
controlsState.state = self.state
controlsState.engageable = not self.events.any(ET.NO_ENTRY)
controlsState.longControlState = self.LoC.long_control_state
controlsState.vPid = float(self.LoC.v_pid)
- controlsState.vCruise = float(self.v_cruise_kph)
+ controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph)
+ controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)
controlsState.upAccelCmd = float(self.LoC.pid.p)
controlsState.uiAccelCmd = float(self.LoC.pid.i)
controlsState.ufAccelCmd = float(self.LoC.pid.f)
controlsState.cumLagMs = -self.rk.remaining * 1000.
controlsState.startMonoTime = int(start_time * 1e9)
controlsState.forceDecel = bool(force_decel)
- controlsState.canErrorCounter = self.can_rcv_error_counter
+ controlsState.canErrorCounter = self.can_rcv_timeout_counter
+ controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
lat_tuning = self.CP.lateralTuning.which()
if self.joystick_mode:
@@ -665,8 +776,8 @@ class Controls:
controlsState.lateralControlState.angleState = lac_log
elif lat_tuning == 'pid':
controlsState.lateralControlState.pidState = lac_log
- elif lat_tuning == 'lqr':
- controlsState.lateralControlState.lqrState = lac_log
+ elif lat_tuning == 'torque':
+ controlsState.lateralControlState.torqueState = lac_log
elif lat_tuning == 'indi':
controlsState.lateralControlState.indiState = lac_log
@@ -706,11 +817,15 @@ class Controls:
start_time = sec_since_boot()
self.prof.checkpoint("Ratekeeper", ignore=True)
+ self.is_metric = self.params.get_bool("IsMetric")
+
# Sample data from sockets and get a carState
CS = self.data_sample()
+ cloudlog.timestamp("Data sampled")
self.prof.checkpoint("Sample")
self.update_events(CS)
+ cloudlog.timestamp("Events updated")
if not self.read_only and self.initialized:
# Update control state
@@ -718,15 +833,15 @@ class Controls:
self.prof.checkpoint("State transition")
# Compute actuators (runs PID loops and lateral MPC)
- actuators, lac_log = self.state_control(CS)
+ CC, lac_log = self.state_control(CS)
self.prof.checkpoint("State Control")
# Publish data
- self.publish_logs(CS, start_time, actuators, lac_log)
+ self.publish_logs(CS, start_time, CC, lac_log)
self.prof.checkpoint("Sent")
- self.update_button_timers(CS.buttonEvents)
+ self.CS_prev = CS
def controlsd_thread(self):
while True:
@@ -734,6 +849,7 @@ class Controls:
self.rk.monitor_time()
self.prof.display()
+
def main(sm=None, pm=None, logcan=None):
controls = Controls(sm, pm, logcan)
controls.controlsd_thread()
diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py
index 2dad05e214..cb878483de 100644
--- a/selfdrive/controls/lib/alertmanager.py
+++ b/selfdrive/controls/lib/alertmanager.py
@@ -22,7 +22,7 @@ def set_offroad_alert(alert: str, show_alert: bool, extra_text: Optional[str] =
a['text'] += extra_text
Params().put(alert, json.dumps(a))
else:
- Params().delete(alert)
+ Params().remove(alert)
@dataclass
diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json
index aa54f582f6..2f85ea917a 100644
--- a/selfdrive/controls/lib/alerts_offroad.json
+++ b/selfdrive/controls/lib/alerts_offroad.json
@@ -1,8 +1,4 @@
{
- "Offroad_ChargeDisabled": {
- "text": "EON charging disabled after car being off for more than 30 hours. Turn ignition on to start charging again.",
- "severity": 0
- },
"Offroad_TemperatureTooHigh": {
"text": "Device temperature too high. System won't start.",
"severity": 1
@@ -41,6 +37,10 @@
"text": "NVMe drive not mounted.",
"severity": 1
},
+ "Offroad_BadNvme": {
+ "text": "Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe.",
+ "severity": 1
+ },
"Offroad_CarUnrecognized": {
"text": "openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.",
"severity": 0
diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py
index c34d143a5a..4790b8f0eb 100644
--- a/selfdrive/controls/lib/desire_helper.py
+++ b/selfdrive/controls/lib/desire_helper.py
@@ -1,11 +1,11 @@
from cereal import log
+from common.conversions import Conversions as CV
from common.realtime import DT_MDL
-from selfdrive.config import Conversions as CV
LaneChangeState = log.LateralPlan.LaneChangeState
LaneChangeDirection = log.LateralPlan.LaneChangeDirection
-LANE_CHANGE_SPEED_MIN = 30 * CV.MPH_TO_MS
+LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.
DESIRES = {
@@ -40,12 +40,12 @@ class DesireHelper:
self.prev_one_blinker = False
self.desire = log.LateralPlan.Desire.none
- def update(self, carstate, active, lane_change_prob):
+ def update(self, carstate, lateral_active, lane_change_prob):
v_ego = carstate.vEgo
one_blinker = carstate.leftBlinker != carstate.rightBlinker
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN
- if not active or self.lane_change_timer > LANE_CHANGE_TIME_MAX:
+ if not lateral_active or self.lane_change_timer > LANE_CHANGE_TIME_MAX:
self.lane_change_state = LaneChangeState.off
self.lane_change_direction = LaneChangeDirection.none
else:
diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py
index 14be3d5ed8..3d5ec8ac1d 100644
--- a/selfdrive/controls/lib/drive_helpers.py
+++ b/selfdrive/controls/lib/drive_helpers.py
@@ -1,8 +1,9 @@
import math
+
from cereal import car
+from common.conversions import Conversions as CV
from common.numpy_fast import clip, interp
from common.realtime import DT_MDL
-from selfdrive.config import Conversions as CV
from selfdrive.modeld.constants import T_IDXS
# WARNING: this value was determined based on the model's training distribution,
@@ -10,115 +11,180 @@ from selfdrive.modeld.constants import T_IDXS
V_CRUISE_MAX = 145 # kph
V_CRUISE_MIN = 8 # kph
V_CRUISE_ENABLE_MIN = 40 # kph
+V_CRUISE_INITIAL = 255 # kph
+IMPERIAL_INCREMENT = 1.6 # should be CV.MPH_TO_KPH, but this causes rounding errors
+MIN_SPEED = 1.0
LAT_MPC_N = 16
LON_MPC_N = 32
CONTROL_N = 17
CAR_ROTATION_RADIUS = 0.0
-# this corresponds to 80deg/s and 20deg/s steering angle in a toyota corolla
-MAX_CURVATURE_RATES = [0.03762194918267951, 0.003441203371932992]
-MAX_CURVATURE_RATE_SPEEDS = [0, 35]
+# EU guidelines
+MAX_LATERAL_JERK = 5.0
+ButtonEvent = car.CarState.ButtonEvent
+ButtonType = car.CarState.ButtonEvent.Type
CRUISE_LONG_PRESS = 50
CRUISE_NEAREST_FUNC = {
- car.CarState.ButtonEvent.Type.accelCruise: math.ceil,
- car.CarState.ButtonEvent.Type.decelCruise: math.floor,
+ ButtonType.accelCruise: math.ceil,
+ ButtonType.decelCruise: math.floor,
}
CRUISE_INTERVAL_SIGN = {
- car.CarState.ButtonEvent.Type.accelCruise: +1,
- car.CarState.ButtonEvent.Type.decelCruise: -1,
+ ButtonType.accelCruise: +1,
+ ButtonType.decelCruise: -1,
}
-class MPC_COST_LAT:
- PATH = 1.0
- HEADING = 1.0
- STEER_RATE = 1.0
+class VCruiseHelper:
+ def __init__(self, CP):
+ self.CP = CP
+ self.v_cruise_kph = V_CRUISE_INITIAL
+ self.v_cruise_cluster_kph = V_CRUISE_INITIAL
+ self.v_cruise_kph_last = 0
+ self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0}
+ self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}
+
+ @property
+ def v_cruise_initialized(self):
+ return self.v_cruise_kph != V_CRUISE_INITIAL
+
+ def update_v_cruise(self, CS, enabled, is_metric):
+ self.v_cruise_kph_last = self.v_cruise_kph
+
+ if CS.cruiseState.available:
+ if not self.CP.pcmCruise:
+ # if stock cruise is completely disabled, then we can use our own set speed logic
+ self._update_v_cruise_non_pcm(CS, enabled, is_metric)
+ self.v_cruise_cluster_kph = self.v_cruise_kph
+ self.update_button_timers(CS, enabled)
+ else:
+ self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
+ self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
+ else:
+ self.v_cruise_kph = V_CRUISE_INITIAL
+ self.v_cruise_cluster_kph = V_CRUISE_INITIAL
+ def _update_v_cruise_non_pcm(self, CS, enabled, is_metric):
+ # handle button presses. TODO: this should be in state_control, but a decelCruise press
+ # would have the effect of both enabling and changing speed is checked after the state transition
+ if not enabled:
+ return
-class MPC_COST_LONG:
- TTC = 5.0
- DISTANCE = 0.1
- ACCELERATION = 10.0
- JERK = 20.0
+ long_press = False
+ button_type = None
+ v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT
-def rate_limit(new_value, last_value, dw_step, up_step):
- return clip(new_value, last_value + dw_step, last_value + up_step)
+ for b in CS.buttonEvents:
+ if b.type.raw in self.button_timers and not b.pressed:
+ if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS:
+ return # end long press
+ button_type = b.type.raw
+ break
+ else:
+ for k in self.button_timers.keys():
+ if self.button_timers[k] and self.button_timers[k] % CRUISE_LONG_PRESS == 0:
+ button_type = k
+ long_press = True
+ break
+ if button_type is None:
+ return
-def get_steer_max(CP, v_ego):
- return interp(v_ego, CP.steerMaxBP, CP.steerMaxV)
+ # Don't adjust speed when pressing resume to exit standstill
+ cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill
+ if button_type == ButtonType.accelCruise and cruise_standstill:
+ return
+ # Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge)
+ if not self.button_change_states[button_type]["enabled"]:
+ return
-def update_v_cruise(v_cruise_kph, buttonEvents, button_timers, enabled, metric):
- # handle button presses. TODO: this should be in state_control, but a decelCruise press
- # would have the effect of both enabling and changing speed is checked after the state transition
- if not enabled:
- return v_cruise_kph
+ v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
+ if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
+ self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
+ else:
+ self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
- long_press = False
- button_type = None
+ # If set is pressed while overriding, clip cruise speed to minimum of vEgo
+ if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
+ self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
- v_cruise_delta = 1 if metric else 1.6
+ self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
- for b in buttonEvents:
- if b.type.raw in button_timers and not b.pressed:
- if button_timers[b.type.raw] > CRUISE_LONG_PRESS:
- return v_cruise_kph # end long press
- button_type = b.type.raw
- break
- else:
- for k in button_timers.keys():
- if button_timers[k] and button_timers[k] % CRUISE_LONG_PRESS == 0:
- button_type = k
- long_press = True
- break
+ def update_button_timers(self, CS, enabled):
+ # increment timer for buttons still pressed
+ for k in self.button_timers:
+ if self.button_timers[k] > 0:
+ self.button_timers[k] += 1
- if button_type:
- v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
- if long_press and v_cruise_kph % v_cruise_delta != 0: # partial interval
- v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](v_cruise_kph / v_cruise_delta) * v_cruise_delta
+ for b in CS.buttonEvents:
+ if b.type.raw in self.button_timers:
+ # Start/end timer and store current state on change of button pressed
+ self.button_timers[b.type.raw] = 1 if b.pressed else 0
+ self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
+
+ def initialize_v_cruise(self, CS):
+ # initializing is handled by the PCM
+ if self.CP.pcmCruise:
+ return
+
+ # 250kph or above probably means we never had a set speed
+ if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_kph_last < 250:
+ self.v_cruise_kph = self.v_cruise_kph_last
else:
- v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
- v_cruise_kph = clip(round(v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
+ self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX)))
- return v_cruise_kph
+ self.v_cruise_cluster_kph = self.v_cruise_kph
-def initialize_v_cruise(v_ego, buttonEvents, v_cruise_last):
- for b in buttonEvents:
- # 250kph or above probably means we never had a set speed
- if b.type == car.CarState.ButtonEvent.Type.accelCruise and v_cruise_last < 250:
- return v_cruise_last
+def apply_deadzone(error, deadzone):
+ if error > deadzone:
+ error -= deadzone
+ elif error < - deadzone:
+ error += deadzone
+ else:
+ error = 0.
+ return error
+
- return int(round(clip(v_ego * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX)))
+def apply_center_deadzone(error, deadzone):
+ if (error > - deadzone) and (error < deadzone):
+ error = 0.
+ return error
+
+
+def rate_limit(new_value, last_value, dw_step, up_step):
+ return clip(new_value, last_value + dw_step, last_value + up_step)
def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates):
if len(psis) != CONTROL_N:
- psis = [0.0 for i in range(CONTROL_N)]
- curvatures = [0.0 for i in range(CONTROL_N)]
- curvature_rates = [0.0 for i in range(CONTROL_N)]
+ psis = [0.0]*CONTROL_N
+ curvatures = [0.0]*CONTROL_N
+ curvature_rates = [0.0]*CONTROL_N
+ v_ego = max(MIN_SPEED, v_ego)
# TODO this needs more thought, use .2s extra for now to estimate other delays
delay = CP.steerActuatorDelay + .2
- current_curvature = curvatures[0]
- psi = interp(delay, T_IDXS[:CONTROL_N], psis)
- desired_curvature_rate = curvature_rates[0]
# MPC can plan to turn the wheel and turn back before t_delay. This means
# in high delay cases some corrections never even get commanded. So just use
# psi to calculate a simple linearization of desired curvature
- curvature_diff_from_psi = psi / (max(v_ego, 1e-1) * delay) - current_curvature
- desired_curvature = current_curvature + 2 * curvature_diff_from_psi
+ current_curvature_desired = curvatures[0]
+ psi = interp(delay, T_IDXS[:CONTROL_N], psis)
+ average_curvature_desired = psi / (v_ego * delay)
+ desired_curvature = 2 * average_curvature_desired - current_curvature_desired
- max_curvature_rate = interp(v_ego, MAX_CURVATURE_RATE_SPEEDS, MAX_CURVATURE_RATES)
+ # This is the "desired rate of the setpoint" not an actual desired rate
+ desired_curvature_rate = curvature_rates[0]
+ max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755
safe_desired_curvature_rate = clip(desired_curvature_rate,
- -max_curvature_rate,
- max_curvature_rate)
+ -max_curvature_rate,
+ max_curvature_rate)
safe_desired_curvature = clip(desired_curvature,
- current_curvature - max_curvature_rate * DT_MDL,
- current_curvature + max_curvature_rate * DT_MDL)
+ current_curvature_desired - max_curvature_rate * DT_MDL,
+ current_curvature_desired + max_curvature_rate * DT_MDL)
+
return safe_desired_curvature, safe_desired_curvature_rate
diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py
index 931932cc0d..5578a83a23 100644
--- a/selfdrive/controls/lib/events.py
+++ b/selfdrive/controls/lib/events.py
@@ -1,13 +1,14 @@
+import math
import os
from enum import IntEnum
from typing import Dict, Union, Callable, List, Optional
from cereal import log, car
import cereal.messaging as messaging
+from common.conversions import Conversions as CV
from common.realtime import DT_CTRL
-from selfdrive.config import Conversions as CV
from selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
-from selfdrive.version import get_short_branch
+from system.version import get_short_branch
AlertSize = log.ControlsState.AlertSize
AlertStatus = log.ControlsState.AlertStatus
@@ -30,6 +31,8 @@ class Priority(IntEnum):
class ET:
ENABLE = 'enable'
PRE_ENABLE = 'preEnable'
+ OVERRIDE_LATERAL = 'overrideLateral'
+ OVERRIDE_LONGITUDINAL = 'overrideLongitudinal'
NO_ENTRY = 'noEntry'
WARNING = 'warning'
USER_DISABLE = 'userDisable'
@@ -134,12 +137,16 @@ class Alert:
return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}"
def __gt__(self, alert2) -> bool:
+ if not isinstance(alert2, Alert):
+ return False
return self.priority > alert2.priority
class NoEntryAlert(Alert):
- def __init__(self, alert_text_2: str, visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none):
- super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal,
+ def __init__(self, alert_text_2: str,
+ alert_text_1: str = "openpilot Unavailable",
+ visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none):
+ super().__init__(alert_text_1, alert_text_2, AlertStatus.normal,
AlertSize.mid, Priority.LOW, visual_alert,
AudibleAlert.refuse, 3.)
@@ -198,35 +205,35 @@ def get_display_speed(speed_ms: float, metric: bool) -> str:
# ********** alert callback functions **********
-AlertCallbackType = Callable[[car.CarParams, messaging.SubMaster, bool, int], Alert]
+AlertCallbackType = Callable[[car.CarParams, car.CarState, messaging.SubMaster, bool, int], Alert]
def soft_disable_alert(alert_text_2: str) -> AlertCallbackType:
- def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ def func(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
if soft_disable_time < int(0.5 / DT_CTRL):
return ImmediateDisableAlert(alert_text_2)
return SoftDisableAlert(alert_text_2)
return func
def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType:
- def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ def func(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
if soft_disable_time < int(0.5 / DT_CTRL):
return ImmediateDisableAlert(alert_text_2)
return UserSoftDisableAlert(alert_text_2)
return func
-def startup_master_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
- branch = get_short_branch("")
+def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ branch = get_short_branch("") # Ensure get_short_branch is cached to avoid lags on startup
if "REPLAY" in os.environ:
branch = "replay"
return StartupAlert("WARNING: This branch is not tested", branch, alert_status=AlertStatus.userPrompt)
-def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}")
-def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
return Alert(
f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
"",
@@ -234,7 +241,7 @@ def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric:
Priority.MID, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4)
-def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
return Alert(
"Calibration in Progress: %d%%" % sm['liveCalibration'].calPerc,
f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}",
@@ -242,23 +249,81 @@ def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, met
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
-def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
- gps_integrated = sm['peripheralState'].pandaType in (log.PandaState.PandaType.uno, log.PandaState.PandaType.dos)
+def no_gps_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
return Alert(
"Poor GPS reception",
- "If sky is visible, contact support" if gps_integrated else "Check GPS antenna placement",
+ "Hardware malfunctioning if sky is visible",
AlertStatus.normal, AlertSize.mid,
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.)
+# *** debug alerts ***
-def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ full_perc = round(100. - sm['deviceState'].freeSpacePercent)
+ return NormalPermanentAlert("Out of Storage", f"{full_perc}% full")
+
+
+def posenet_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ mdl = sm['modelV2'].velocity.x[0] if len(sm['modelV2'].velocity.x) else math.nan
+ err = CS.vEgo - mdl
+ msg = f"Speed Error: {err:.1f} m/s"
+ return NoEntryAlert(msg, alert_text_1="Posenet Speed Invalid")
+
+
+def process_not_running_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning]
+ msg = ', '.join(not_running)
+ return NoEntryAlert(msg, alert_text_1="Process Not Running")
+
+
+def comm_issue_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ bs = [s for s in sm.data.keys() if not sm.all_checks([s, ])]
+ msg = ', '.join(bs[:4]) # can't fit too many on one line
+ return NoEntryAlert(msg, alert_text_1="Communication Issue Between Processes")
+
+
+def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
+ bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])]
+ return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams))
+
+
+def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ rpy = sm['liveCalibration'].rpyCalib
+ yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan)
+ pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan)
+ angles = f"Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°"
+ return NormalPermanentAlert("Calibration Invalid", angles)
+
+
+def overheat_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ cpu = max(sm['deviceState'].cpuTempC, default=0.)
+ gpu = max(sm['deviceState'].gpuTempC, default=0.)
+ temp = max((cpu, gpu, sm['deviceState'].memoryTempC))
+ return NormalPermanentAlert("System Overheated", f"{temp:.0f} °C")
+
+
+def low_memory_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ return NormalPermanentAlert("Low Memory", f"{sm['deviceState'].memoryUsagePercent}% used")
+
+
+def high_cpu_usage_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ x = max(sm['deviceState'].cpuUsagePercent, default=0.)
+ return NormalPermanentAlert("High CPU Usage", f"{x}% used")
+
+
+def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+ return NormalPermanentAlert("Driving Model Lagging", f"{sm['modelV2'].frameDropPerc:.1f}% frames dropped")
+
+
+def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
text = "Cruise Mode Disabled"
if CP.carName == "honda":
text = "Main Switch Off"
return NoEntryAlert(text)
-def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
+def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
axes = sm['testJoystick'].axes
gb, steer = list(axes)[:2] if len(axes) else (0., 0.)
vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%"
@@ -356,14 +421,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# ********** events only containing alerts that display while engaged **********
- EventName.gasPressed: {
- ET.PRE_ENABLE: Alert(
- "Release Gas Pedal to Engage",
- "",
- AlertStatus.normal, AlertSize.small,
- Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.),
- },
-
# openpilot tries to learn certain parameters about your car by observing
# how the car behaves to steering inputs from both human and openpilot driving.
# This includes:
@@ -382,7 +439,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
"Steering Temporarily Unavailable",
"",
AlertStatus.userPrompt, AlertSize.small,
- Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.),
+ Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8),
},
EventName.preDriverDistracted: {
@@ -444,7 +501,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
EventName.resumeRequired: {
ET.WARNING: Alert(
"STOPPED",
- "Press Resume to Go",
+ "Press Resume to Exit Standstill",
AlertStatus.userPrompt, AlertSize.mid,
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
},
@@ -495,24 +552,32 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# Thrown when the fan is driven at >50% but is not rotating
EventName.fanMalfunction: {
- ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Contact Support"),
+ ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Likely Hardware Issue"),
},
- # Camera is not outputting frames at a constant framerate
+ # Camera is not outputting frames
EventName.cameraMalfunction: {
- ET.PERMANENT: NormalPermanentAlert("Camera Malfunction", "Contact Support"),
+ ET.PERMANENT: camera_malfunction_alert,
+ ET.SOFT_DISABLE: soft_disable_alert("Camera Malfunction"),
+ ET.NO_ENTRY: NoEntryAlert("Camera Malfunction: Reboot Your Device"),
+ },
+ # Camera framerate too low
+ EventName.cameraFrameRate: {
+ ET.PERMANENT: NormalPermanentAlert("Camera Frame Rate Low", "Reboot your Device"),
+ ET.SOFT_DISABLE: soft_disable_alert("Camera Frame Rate Low"),
+ ET.NO_ENTRY: NoEntryAlert("Camera Frame Rate Low: Reboot Your Device"),
},
# Unused
EventName.gpsMalfunction: {
- ET.PERMANENT: NormalPermanentAlert("GPS Malfunction", "Contact Support"),
+ ET.PERMANENT: NormalPermanentAlert("GPS Malfunction", "Likely Hardware Issue"),
},
# When the GPS position and localizer diverge the localizer is reset to the
# current GPS position. This alert is thrown when the localizer is reset
# more often than expected.
EventName.localizerMalfunction: {
- # ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Contact Support"),
+ # ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Hardware Malfunction"),
},
# ********** events that affect controls state transitions **********
@@ -531,6 +596,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
EventName.buttonCancel: {
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
+ ET.NO_ENTRY: NoEntryAlert("Cancel Pressed"),
},
EventName.brakeHold: {
@@ -549,11 +615,39 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
visual_alert=VisualAlert.brakePressed),
},
+ EventName.preEnableStandstill: {
+ ET.PRE_ENABLE: Alert(
+ "Release Brake to Engage",
+ "",
+ AlertStatus.normal, AlertSize.small,
+ Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.),
+ },
+
+ EventName.gasPressedOverride: {
+ ET.OVERRIDE_LONGITUDINAL: Alert(
+ "",
+ "",
+ AlertStatus.normal, AlertSize.none,
+ Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1),
+ },
+
+ EventName.steerOverride: {
+ ET.OVERRIDE_LATERAL: Alert(
+ "",
+ "",
+ AlertStatus.normal, AlertSize.none,
+ Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1),
+ },
+
EventName.wrongCarMode: {
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
ET.NO_ENTRY: wrong_car_mode_alert,
},
+ EventName.resumeBlocked: {
+ ET.NO_ENTRY: NoEntryAlert("Press Set to Engage"),
+ },
+
EventName.wrongCruiseMode: {
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
ET.NO_ENTRY: NoEntryAlert("Adaptive Cruise Disabled"),
@@ -565,7 +659,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
},
EventName.outOfSpace: {
- ET.PERMANENT: NormalPermanentAlert("Out of Storage"),
+ ET.PERMANENT: out_of_space_alert,
ET.NO_ENTRY: NoEntryAlert("Out of Storage"),
},
@@ -575,11 +669,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
EventName.sensorDataInvalid: {
ET.PERMANENT: Alert(
- "No Data from Device Sensors",
- "Reboot your Device",
+ "Sensor Data Invalid",
+ "Ensure device is mounted securely",
AlertStatus.normal, AlertSize.mid,
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.),
- ET.NO_ENTRY: NoEntryAlert("No Data from Device Sensors"),
+ ET.NO_ENTRY: NoEntryAlert("Sensor Data Invalid"),
+ ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"),
},
EventName.noGps: {
@@ -596,7 +691,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
},
EventName.overheat: {
- ET.PERMANENT: NormalPermanentAlert("System Overheated"),
+ ET.PERMANENT: overheat_alert,
ET.SOFT_DISABLE: soft_disable_alert("System Overheated"),
ET.NO_ENTRY: NoEntryAlert("System Overheated"),
},
@@ -612,7 +707,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# and attaching while making sure the device is pointed straight forward and is level.
# See https://comma.ai/setup for more information
EventName.calibrationInvalid: {
- ET.PERMANENT: NormalPermanentAlert("Calibration Invalid", "Remount Device and Recalibrate"),
+ ET.PERMANENT: calibration_invalid_alert,
ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"),
ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"),
},
@@ -649,12 +744,22 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# ten times the regular interval, or the average interval is more than 10% too high.
EventName.commIssue: {
ET.SOFT_DISABLE: soft_disable_alert("Communication Issue between Processes"),
- ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes"),
+ ET.NO_ENTRY: comm_issue_alert,
+ },
+ EventName.commIssueAvgFreq: {
+ ET.SOFT_DISABLE: soft_disable_alert("Low Communication Rate between Processes"),
+ ET.NO_ENTRY: NoEntryAlert("Low Communication Rate between Processes"),
+ },
+
+ EventName.controlsdLagging: {
+ ET.SOFT_DISABLE: soft_disable_alert("Controls Lagging"),
+ ET.NO_ENTRY: NoEntryAlert("Controls Process Lagging: Reboot Your Device"),
},
# Thrown when manager detects a service exited unexpectedly while driving
EventName.processNotRunning: {
- ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"),
+ ET.NO_ENTRY: process_not_running_alert,
+ ET.SOFT_DISABLE: soft_disable_alert("Process Not Running"),
},
EventName.radarFault: {
@@ -666,8 +771,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# is not processing frames fast enough they have to be dropped. This alert is
# thrown when over 20% of frames are dropped.
EventName.modeldLagging: {
- ET.SOFT_DISABLE: soft_disable_alert("Driving model lagging"),
- ET.NO_ENTRY: NoEntryAlert("Driving model lagging"),
+ ET.SOFT_DISABLE: soft_disable_alert("Driving Model Lagging"),
+ ET.NO_ENTRY: NoEntryAlert("Driving Model Lagging"),
+ ET.PERMANENT: modeld_lagging_alert,
},
# Besides predicting the path, lane lines and lead car data the model also
@@ -676,8 +782,8 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# usually means the model has trouble understanding the scene. This is used
# as a heuristic to warn the driver.
EventName.posenetInvalid: {
- ET.SOFT_DISABLE: soft_disable_alert("Model Output Uncertain"),
- ET.NO_ENTRY: NoEntryAlert("Model Output Uncertain"),
+ ET.SOFT_DISABLE: soft_disable_alert("Posenet Speed Invalid"),
+ ET.NO_ENTRY: posenet_invalid_alert,
},
# When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we
@@ -689,14 +795,14 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
EventName.lowMemory: {
ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"),
- ET.PERMANENT: NormalPermanentAlert("Low Memory", "Reboot your Device"),
+ ET.PERMANENT: low_memory_alert,
ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"),
},
EventName.highCpuUsage: {
#ET.SOFT_DISABLE: soft_disable_alert("System Malfunction: Reboot Your Device"),
#ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"),
- ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"),
+ ET.NO_ENTRY: high_cpu_usage_alert,
},
EventName.accFaulted: {
@@ -705,24 +811,29 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"),
},
+ EventName.accFaultedTemp: {
+ ET.NO_ENTRY: NoEntryAlert("Cruise Temporarily Faulted"),
+ },
+
EventName.controlsMismatch: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"),
+ ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"),
},
EventName.roadCameraError: {
- ET.PERMANENT: NormalPermanentAlert("Camera Error",
+ ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Road",
duration=1.,
creation_delay=30.),
},
- EventName.driverCameraError: {
- ET.PERMANENT: NormalPermanentAlert("Camera Error",
+ EventName.wideRoadCameraError: {
+ ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Road Fisheye",
duration=1.,
creation_delay=30.),
},
- EventName.wideRoadCameraError: {
- ET.PERMANENT: NormalPermanentAlert("Camera Error",
+ EventName.driverCameraError: {
+ ET.PERMANENT: NormalPermanentAlert("Camera CRC Error - Driver",
duration=1.,
creation_delay=30.),
},
@@ -740,7 +851,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# - CAN data is received, but some message are not received at the right frequency
# If you're not writing a new car port, this is usually cause by faulty wiring
EventName.canError: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error: Check Connections"),
+ ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error"),
ET.PERMANENT: Alert(
"CAN Error: Check Connections",
"",
@@ -749,6 +860,16 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"),
},
+ EventName.canBusMissing: {
+ ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Bus Disconnected"),
+ ET.PERMANENT: Alert(
+ "CAN Bus Disconnected: Likely Faulty Cable",
+ "",
+ AlertStatus.normal, AlertSize.small,
+ Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
+ ET.NO_ENTRY: NoEntryAlert("CAN Bus Disconnected: Check Connections"),
+ },
+
EventName.steerUnavailable: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("LKAS Fault: Restart the Car"),
ET.PERMANENT: NormalPermanentAlert("LKAS Fault: Restart the car to engage"),
@@ -790,18 +911,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
# are received on the car side this usually means the relay hasn't opened correctly
# and this alert is thrown.
EventName.relayMalfunction: {
- ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Malfunction"),
- ET.PERMANENT: NormalPermanentAlert("Harness Malfunction", "Check Hardware"),
- ET.NO_ENTRY: NoEntryAlert("Harness Malfunction"),
- },
-
- EventName.noTarget: {
- ET.IMMEDIATE_DISABLE: Alert(
- "openpilot Canceled",
- "No close lead car",
- AlertStatus.normal, AlertSize.mid,
- Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.),
- ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"),
+ ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Relay Malfunction"),
+ ET.PERMANENT: NormalPermanentAlert("Harness Relay Malfunction", "Check Hardware"),
+ ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"),
},
EventName.speedTooLow: {
diff --git a/selfdrive/controls/lib/lane_planner.py b/selfdrive/controls/lib/lane_planner.py
deleted file mode 100644
index 0230255ef9..0000000000
--- a/selfdrive/controls/lib/lane_planner.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import numpy as np
-from cereal import log
-from common.filter_simple import FirstOrderFilter
-from common.numpy_fast import interp
-from common.realtime import DT_MDL
-from selfdrive.hardware import EON, TICI
-from selfdrive.swaglog import cloudlog
-
-
-TRAJECTORY_SIZE = 33
-# camera offset is meters from center car to camera
-# model path is in the frame of the camera. Empirically
-# the model knows the difference between TICI and EON
-# so a path offset is not needed
-PATH_OFFSET = 0.00
-if EON:
- CAMERA_OFFSET = -0.06
-elif TICI:
- CAMERA_OFFSET = 0.04
-else:
- CAMERA_OFFSET = 0.0
-
-
-class LanePlanner:
- def __init__(self, wide_camera=False):
- self.ll_t = np.zeros((TRAJECTORY_SIZE,))
- self.ll_x = np.zeros((TRAJECTORY_SIZE,))
- self.lll_y = np.zeros((TRAJECTORY_SIZE,))
- self.rll_y = np.zeros((TRAJECTORY_SIZE,))
- self.lane_width_estimate = FirstOrderFilter(3.7, 9.95, DT_MDL)
- self.lane_width_certainty = FirstOrderFilter(1.0, 0.95, DT_MDL)
- self.lane_width = 3.7
-
- self.lll_prob = 0.
- self.rll_prob = 0.
- self.d_prob = 0.
-
- self.lll_std = 0.
- self.rll_std = 0.
-
- self.l_lane_change_prob = 0.
- self.r_lane_change_prob = 0.
-
- self.camera_offset = -CAMERA_OFFSET if wide_camera else CAMERA_OFFSET
- self.path_offset = -PATH_OFFSET if wide_camera else PATH_OFFSET
-
- def parse_model(self, md):
- lane_lines = md.laneLines
- if len(lane_lines) == 4 and len(lane_lines[0].t) == TRAJECTORY_SIZE:
- self.ll_t = (np.array(lane_lines[1].t) + np.array(lane_lines[2].t))/2
- # left and right ll x is the same
- self.ll_x = lane_lines[1].x
- self.lll_y = np.array(lane_lines[1].y) + self.camera_offset
- self.rll_y = np.array(lane_lines[2].y) + self.camera_offset
- self.lll_prob = md.laneLineProbs[1]
- self.rll_prob = md.laneLineProbs[2]
- self.lll_std = md.laneLineStds[1]
- self.rll_std = md.laneLineStds[2]
-
- desire_state = md.meta.desireState
- if len(desire_state):
- self.l_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeLeft]
- self.r_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeRight]
-
- def get_d_path(self, v_ego, path_t, path_xyz):
- # Reduce reliance on lanelines that are too far apart or
- # will be in a few seconds
- path_xyz[:, 1] += self.path_offset
- l_prob, r_prob = self.lll_prob, self.rll_prob
- width_pts = self.rll_y - self.lll_y
- prob_mods = []
- for t_check in (0.0, 1.5, 3.0):
- width_at_t = interp(t_check * (v_ego + 7), self.ll_x, width_pts)
- prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0]))
- mod = min(prob_mods)
- l_prob *= mod
- r_prob *= mod
-
- # Reduce reliance on uncertain lanelines
- l_std_mod = interp(self.lll_std, [.15, .3], [1.0, 0.0])
- r_std_mod = interp(self.rll_std, [.15, .3], [1.0, 0.0])
- l_prob *= l_std_mod
- r_prob *= r_std_mod
-
- # Find current lanewidth
- self.lane_width_certainty.update(l_prob * r_prob)
- current_lane_width = abs(self.rll_y[0] - self.lll_y[0])
- self.lane_width_estimate.update(current_lane_width)
- speed_lane_width = interp(v_ego, [0., 31.], [2.8, 3.5])
- self.lane_width = self.lane_width_certainty.x * self.lane_width_estimate.x + \
- (1 - self.lane_width_certainty.x) * speed_lane_width
-
- clipped_lane_width = min(4.0, self.lane_width)
- path_from_left_lane = self.lll_y + clipped_lane_width / 2.0
- path_from_right_lane = self.rll_y - clipped_lane_width / 2.0
-
- self.d_prob = l_prob + r_prob - l_prob * r_prob
- lane_path_y = (l_prob * path_from_left_lane + r_prob * path_from_right_lane) / (l_prob + r_prob + 0.0001)
- safe_idxs = np.isfinite(self.ll_t)
- if safe_idxs[0]:
- lane_path_y_interp = np.interp(path_t, self.ll_t[safe_idxs], lane_path_y[safe_idxs])
- path_xyz[:,1] = self.d_prob * lane_path_y_interp + (1.0 - self.d_prob) * path_xyz[:,1]
- else:
- cloudlog.warning("Lateral mpc - NaNs in laneline times, ignoring")
- return path_xyz
diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py
index eb16aca2e8..78b59fda59 100644
--- a/selfdrive/controls/lib/latcontrol.py
+++ b/selfdrive/controls/lib/latcontrol.py
@@ -1,7 +1,7 @@
from abc import abstractmethod, ABC
-from common.realtime import DT_CTRL
from common.numpy_fast import clip
+from common.realtime import DT_CTRL
MIN_STEER_SPEED = 0.3
@@ -12,15 +12,18 @@ class LatControl(ABC):
self.sat_limit = CP.steerLimitTimer
self.sat_count = 0.
+ # we define the steer torque scale as [-1.0...1.0]
+ self.steer_max = 1.0
+
@abstractmethod
- def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate):
+ def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk):
pass
def reset(self):
self.sat_count = 0.
- def _check_saturation(self, saturated, CS):
- if saturated and CS.vEgo > 10. and not CS.steeringRateLimited and not CS.steeringPressed:
+ def _check_saturation(self, saturated, CS, steer_limited):
+ if saturated and CS.vEgo > 10. and not steer_limited and not CS.steeringPressed:
self.sat_count += self.sat_count_rate
else:
self.sat_count -= self.sat_count_rate
diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py
index 414df2b662..2507771a7d 100644
--- a/selfdrive/controls/lib/latcontrol_angle.py
+++ b/selfdrive/controls/lib/latcontrol_angle.py
@@ -7,7 +7,7 @@ STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees
class LatControlAngle(LatControl):
- def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate):
+ def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk):
angle_log = log.ControlsState.LateralAngleState.new_message()
if not active:
@@ -19,7 +19,7 @@ class LatControlAngle(LatControl):
angle_steers_des += params.angleOffsetDeg
angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD
- angle_log.saturated = self._check_saturation(angle_control_saturated, CS)
+ angle_log.saturated = self._check_saturation(angle_control_saturated, CS, steer_limited)
angle_log.steeringAngleDeg = float(CS.steeringAngleDeg)
angle_log.steeringAngleDesiredDeg = angle_steers_des
return 0, float(angle_steers_des), angle_log
diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py
index dc1b31bad9..2bc3cef76b 100644
--- a/selfdrive/controls/lib/latcontrol_indi.py
+++ b/selfdrive/controls/lib/latcontrol_indi.py
@@ -5,7 +5,6 @@ from cereal import log
from common.filter_simple import FirstOrderFilter
from common.numpy_fast import clip, interp
from common.realtime import DT_CTRL
-from selfdrive.controls.lib.drive_helpers import get_steer_max
from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED
@@ -41,7 +40,6 @@ class LatControlINDI(LatControl):
self._inner_loop_gain = (CP.lateralTuning.indi.innerLoopGainBP, CP.lateralTuning.indi.innerLoopGainV)
self.steer_filter = FirstOrderFilter(0., self.RC, DT_CTRL)
-
self.reset()
@property
@@ -65,7 +63,7 @@ class LatControlINDI(LatControl):
self.steer_filter.x = 0.
self.speed = 0.
- def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate):
+ def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk):
self.speed = CS.vEgo
# Update Kalman filter
y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]])
@@ -80,6 +78,7 @@ class LatControlINDI(LatControl):
steers_des += math.radians(params.angleOffsetDeg)
indi_log.steeringAngleDesiredDeg = math.degrees(steers_des)
+ # desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
rate_des = VM.get_steer_from_curvature(-desired_curvature_rate, CS.vEgo, 0)
indi_log.steeringRateDesiredDeg = math.degrees(rate_des)
@@ -107,8 +106,7 @@ class LatControlINDI(LatControl):
output_steer = self.steer_filter.x + delta_u
- steers_max = get_steer_max(CP, CS.vEgo)
- output_steer = clip(output_steer, -steers_max, steers_max)
+ output_steer = clip(output_steer, -self.steer_max, self.steer_max)
indi_log.active = True
indi_log.rateSetPoint = float(rate_sp)
@@ -117,6 +115,6 @@ class LatControlINDI(LatControl):
indi_log.delayedOutput = float(self.steer_filter.x)
indi_log.delta = float(delta_u)
indi_log.output = float(output_steer)
- indi_log.saturated = self._check_saturation(steers_max - abs(output_steer) < 1e-3, CS)
+ indi_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited)
return float(output_steer), float(steers_des), indi_log
diff --git a/selfdrive/controls/lib/latcontrol_lqr.py b/selfdrive/controls/lib/latcontrol_lqr.py
deleted file mode 100644
index 5c273a45be..0000000000
--- a/selfdrive/controls/lib/latcontrol_lqr.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import math
-import numpy as np
-
-from common.numpy_fast import clip
-from common.realtime import DT_CTRL
-from cereal import log
-from selfdrive.controls.lib.drive_helpers import get_steer_max
-from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED
-
-
-class LatControlLQR(LatControl):
- def __init__(self, CP, CI):
- super().__init__(CP, CI)
- self.scale = CP.lateralTuning.lqr.scale
- self.ki = CP.lateralTuning.lqr.ki
-
- self.A = np.array(CP.lateralTuning.lqr.a).reshape((2, 2))
- self.B = np.array(CP.lateralTuning.lqr.b).reshape((2, 1))
- self.C = np.array(CP.lateralTuning.lqr.c).reshape((1, 2))
- self.K = np.array(CP.lateralTuning.lqr.k).reshape((1, 2))
- self.L = np.array(CP.lateralTuning.lqr.l).reshape((2, 1))
- self.dc_gain = CP.lateralTuning.lqr.dcGain
-
- self.x_hat = np.array([[0], [0]])
- self.i_unwind_rate = 0.3 * DT_CTRL
- self.i_rate = 1.0 * DT_CTRL
-
- self.reset()
-
- def reset(self):
- super().reset()
- self.i_lqr = 0.0
-
- def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate):
- lqr_log = log.ControlsState.LateralLQRState.new_message()
-
- steers_max = get_steer_max(CP, CS.vEgo)
- torque_scale = (0.45 + CS.vEgo / 60.0)**2 # Scale actuator model with speed
-
- # Subtract offset. Zero angle should correspond to zero torque
- steering_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetAverageDeg
-
- desired_angle = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
-
- instant_offset = params.angleOffsetDeg - params.angleOffsetAverageDeg
- desired_angle += instant_offset # Only add offset that originates from vehicle model errors
- lqr_log.steeringAngleDesiredDeg = desired_angle
-
- # Update Kalman filter
- angle_steers_k = float(self.C.dot(self.x_hat))
- e = steering_angle_no_offset - angle_steers_k
- self.x_hat = self.A.dot(self.x_hat) + self.B.dot(CS.steeringTorqueEps / torque_scale) + self.L.dot(e)
-
- if CS.vEgo < MIN_STEER_SPEED or not active:
- lqr_log.active = False
- lqr_output = 0.
- output_steer = 0.
- self.reset()
- else:
- lqr_log.active = True
-
- # LQR
- u_lqr = float(desired_angle / self.dc_gain - self.K.dot(self.x_hat))
- lqr_output = torque_scale * u_lqr / self.scale
-
- # Integrator
- if CS.steeringPressed:
- self.i_lqr -= self.i_unwind_rate * float(np.sign(self.i_lqr))
- else:
- error = desired_angle - angle_steers_k
- i = self.i_lqr + self.ki * self.i_rate * error
- control = lqr_output + i
-
- if (error >= 0 and (control <= steers_max or i < 0.0)) or \
- (error <= 0 and (control >= -steers_max or i > 0.0)):
- self.i_lqr = i
-
- output_steer = lqr_output + self.i_lqr
- output_steer = clip(output_steer, -steers_max, steers_max)
-
- lqr_log.steeringAngleDeg = angle_steers_k
- lqr_log.i = self.i_lqr
- lqr_log.output = output_steer
- lqr_log.lqrOutput = lqr_output
- lqr_log.saturated = self._check_saturation(steers_max - abs(output_steer) < 1e-3, CS)
- return output_steer, desired_angle, lqr_log
diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py
index f5ff5a95e5..6bd678073e 100644
--- a/selfdrive/controls/lib/latcontrol_pid.py
+++ b/selfdrive/controls/lib/latcontrol_pid.py
@@ -1,54 +1,48 @@
import math
-from selfdrive.controls.lib.pid import PIController
-from selfdrive.controls.lib.drive_helpers import get_steer_max
-from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED
from cereal import log
+from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED
+from selfdrive.controls.lib.pid import PIDController
class LatControlPID(LatControl):
def __init__(self, CP, CI):
super().__init__(CP, CI)
- self.pid = PIController((CP.lateralTuning.pid.kpBP, CP.lateralTuning.pid.kpV),
- (CP.lateralTuning.pid.kiBP, CP.lateralTuning.pid.kiV),
- k_f=CP.lateralTuning.pid.kf, pos_limit=1.0, neg_limit=-1.0)
+ self.pid = PIDController((CP.lateralTuning.pid.kpBP, CP.lateralTuning.pid.kpV),
+ (CP.lateralTuning.pid.kiBP, CP.lateralTuning.pid.kiV),
+ k_f=CP.lateralTuning.pid.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.get_steer_feedforward = CI.get_steer_feedforward_function()
def reset(self):
super().reset()
self.pid.reset()
- def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate):
+ def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk):
pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg)
angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
angle_steers_des = angle_steers_des_no_offset + params.angleOffsetDeg
+ error = angle_steers_des - CS.steeringAngleDeg
pid_log.steeringAngleDesiredDeg = angle_steers_des
- pid_log.angleError = angle_steers_des - CS.steeringAngleDeg
+ pid_log.angleError = error
if CS.vEgo < MIN_STEER_SPEED or not active:
output_steer = 0.0
pid_log.active = False
self.pid.reset()
else:
- steers_max = get_steer_max(CP, CS.vEgo)
- self.pid.pos_limit = steers_max
- self.pid.neg_limit = -steers_max
-
# offset does not contribute to resistive torque
steer_feedforward = self.get_steer_feedforward(angle_steers_des_no_offset, CS.vEgo)
- deadzone = 0.0
-
- output_steer = self.pid.update(angle_steers_des, CS.steeringAngleDeg, override=CS.steeringPressed,
- feedforward=steer_feedforward, speed=CS.vEgo, deadzone=deadzone)
+ output_steer = self.pid.update(error, override=CS.steeringPressed,
+ feedforward=steer_feedforward, speed=CS.vEgo)
pid_log.active = True
pid_log.p = self.pid.p
pid_log.i = self.pid.i
pid_log.f = self.pid.f
pid_log.output = output_steer
- pid_log.saturated = self._check_saturation(steers_max - abs(output_steer) < 1e-3, CS)
+ pid_log.saturated = self._check_saturation(self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited)
return output_steer, angle_steers_des, pid_log
diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py
new file mode 100644
index 0000000000..d10d39d945
--- /dev/null
+++ b/selfdrive/controls/lib/latcontrol_torque.py
@@ -0,0 +1,89 @@
+import math
+
+from cereal import log
+from common.numpy_fast import interp
+from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED
+from selfdrive.controls.lib.pid import PIDController
+from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
+
+# At higher speeds (25+mph) we can assume:
+# Lateral acceleration achieved by a specific car correlates to
+# torque applied to the steering rack. It does not correlate to
+# wheel slip, or to speed.
+
+# This controller applies torque to achieve desired lateral
+# accelerations. To compensate for the low speed effects we
+# use a LOW_SPEED_FACTOR in the error. Additionally, there is
+# friction in the steering wheel that needs to be overcome to
+# move it at all, this is compensated for too.
+
+LOW_SPEED_X = [0, 10, 20, 30]
+LOW_SPEED_Y = [15, 13, 10, 5]
+
+
+class LatControlTorque(LatControl):
+ def __init__(self, CP, CI):
+ super().__init__(CP, CI)
+ self.torque_params = CP.lateralTuning.torque
+ self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
+ k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
+ self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
+ self.use_steering_angle = self.torque_params.useSteeringAngle
+ self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
+
+ def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
+ self.torque_params.latAccelFactor = latAccelFactor
+ self.torque_params.latAccelOffset = latAccelOffset
+ self.torque_params.friction = friction
+
+ def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk):
+ pid_log = log.ControlsState.LateralTorqueState.new_message()
+
+ if CS.vEgo < MIN_STEER_SPEED or not active:
+ output_torque = 0.0
+ pid_log.active = False
+ else:
+ if self.use_steering_angle:
+ actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
+ curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
+ else:
+ actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
+ actual_curvature_llk = llk.angularVelocityCalibrated.value[2] / CS.vEgo
+ actual_curvature = interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_llk])
+ curvature_deadzone = 0.0
+ desired_lateral_accel = desired_curvature * CS.vEgo ** 2
+
+ # desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
+ # desired_lateral_jerk = desired_curvature_rate * CS.vEgo ** 2
+ actual_lateral_accel = actual_curvature * CS.vEgo ** 2
+ lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
+
+ low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2
+ setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
+ measurement = actual_lateral_accel + low_speed_factor * actual_curvature
+ error = setpoint - measurement
+ gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY
+ pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error,
+ lateral_accel_deadzone, friction_compensation=False)
+ ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params,
+ desired_lateral_accel - actual_lateral_accel,
+ lateral_accel_deadzone, friction_compensation=True)
+
+ freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5
+ output_torque = self.pid.update(pid_log.error,
+ feedforward=ff,
+ speed=CS.vEgo,
+ freeze_integrator=freeze_integrator)
+
+ pid_log.active = True
+ pid_log.p = self.pid.p
+ pid_log.i = self.pid.i
+ pid_log.d = self.pid.d
+ pid_log.f = self.pid.f
+ pid_log.output = -output_torque
+ pid_log.actualLateralAccel = actual_lateral_accel
+ pid_log.desiredLateralAccel = desired_lateral_accel
+ pid_log.saturated = self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited)
+
+ # TODO left is positive in this convention
+ return -output_torque, 0.0, pid_log
diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript
index f402e6e15e..df1e2a2a1a 100644
--- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript
+++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript
@@ -43,11 +43,21 @@ generated_files = [
f'{gen}/lat_cost/lat_cost_y_0_fun.h',
] + build_files
+acados_dir = '#third_party/acados'
+acados_templates_dir = '#pyextra/acados_template/c_templates_tera'
+
+source_list = ['lat_mpc.py',
+ f'{acados_dir}/include/acados_c/ocp_nlp_interface.h',
+ f'{acados_dir}/x86_64/lib/libacados.so',
+ f'{acados_dir}/larch64/lib/libacados.so',
+ f'{acados_templates_dir}/acados_solver.in.c',
+]
+
lenv = env.Clone()
lenv.Clean(generated_files, Dir(gen))
lenv.Command(generated_files,
- ["lat_mpc.py"],
+ source_list,
f"cd {Dir('.').abspath} && python3 lat_mpc.py")
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py
index e4a73bf97d..9607532ace 100755
--- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py
+++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py
@@ -5,31 +5,35 @@ import numpy as np
from casadi import SX, vertcat, sin, cos
from common.realtime import sec_since_boot
-from selfdrive.controls.lib.drive_helpers import LAT_MPC_N as N
from selfdrive.modeld.constants import T_IDXS
if __name__ == '__main__': # generating code
from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
else:
- # from pyextra.acados_template import AcadosOcpSolverFast
- from selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverFast # pylint: disable=no-name-in-module, import-error
+ from selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error
LAT_MPC_DIR = os.path.dirname(os.path.abspath(__file__))
EXPORT_DIR = os.path.join(LAT_MPC_DIR, "c_generated_code")
-JSON_FILE = "acados_ocp_lat.json"
+JSON_FILE = os.path.join(LAT_MPC_DIR, "acados_ocp_lat.json")
X_DIM = 4
P_DIM = 2
+N = 16
+COST_E_DIM = 3
+COST_DIM = COST_E_DIM + 2
+SPEED_OFFSET = 10.0
+MODEL_NAME = 'lat'
+ACADOS_SOLVER_TYPE = 'SQP_RTI'
def gen_lat_model():
model = AcadosModel()
- model.name = 'lat'
+ model.name = MODEL_NAME
# set up states & controls
x_ego = SX.sym('x_ego')
y_ego = SX.sym('y_ego')
psi_ego = SX.sym('psi_ego')
- curv_ego = SX.sym('curv_ego')
- model.x = vertcat(x_ego, y_ego, psi_ego, curv_ego)
+ psi_rate_ego = SX.sym('psi_rate_ego')
+ model.x = vertcat(x_ego, y_ego, psi_ego, psi_rate_ego)
# parameters
v_ego = SX.sym('v_ego')
@@ -37,28 +41,28 @@ def gen_lat_model():
model.p = vertcat(v_ego, rotation_radius)
# controls
- curv_rate = SX.sym('curv_rate')
- model.u = vertcat(curv_rate)
+ psi_accel_ego = SX.sym('psi_accel_ego')
+ model.u = vertcat(psi_accel_ego)
# xdot
x_ego_dot = SX.sym('x_ego_dot')
y_ego_dot = SX.sym('y_ego_dot')
psi_ego_dot = SX.sym('psi_ego_dot')
- curv_ego_dot = SX.sym('curv_ego_dot')
+ psi_rate_ego_dot = SX.sym('psi_rate_ego_dot')
- model.xdot = vertcat(x_ego_dot, y_ego_dot, psi_ego_dot, curv_ego_dot)
+ model.xdot = vertcat(x_ego_dot, y_ego_dot, psi_ego_dot, psi_rate_ego_dot)
# dynamics model
- f_expl = vertcat(v_ego * cos(psi_ego) - rotation_radius * sin(psi_ego) * (v_ego * curv_ego),
- v_ego * sin(psi_ego) + rotation_radius * cos(psi_ego) * (v_ego * curv_ego),
- v_ego * curv_ego,
- curv_rate)
+ f_expl = vertcat(v_ego * cos(psi_ego) - rotation_radius * sin(psi_ego) * psi_rate_ego,
+ v_ego * sin(psi_ego) + rotation_radius * cos(psi_ego) * psi_rate_ego,
+ psi_rate_ego,
+ psi_accel_ego)
model.f_impl_expr = model.xdot - f_expl
model.f_expl_expr = f_expl
return model
-def gen_lat_mpc_solver():
+def gen_lat_ocp():
ocp = AcadosOcp()
ocp.model = gen_lat_model()
@@ -71,26 +75,35 @@ def gen_lat_mpc_solver():
ocp.cost.cost_type = 'NONLINEAR_LS'
ocp.cost.cost_type_e = 'NONLINEAR_LS'
- Q = np.diag([0.0, 0.0])
- QR = np.diag([0.0, 0.0, 0.0])
+ Q = np.diag(np.zeros(COST_E_DIM))
+ QR = np.diag(np.zeros(COST_DIM))
ocp.cost.W = QR
ocp.cost.W_e = Q
- y_ego, psi_ego = ocp.model.x[1], ocp.model.x[2]
- curv_rate = ocp.model.u[0]
+ y_ego, psi_ego, psi_rate_ego = ocp.model.x[1], ocp.model.x[2], ocp.model.x[3]
+ psi_rate_ego_dot = ocp.model.u[0]
v_ego = ocp.model.p[0]
ocp.parameter_values = np.zeros((P_DIM, ))
- ocp.cost.yref = np.zeros((3, ))
- ocp.cost.yref_e = np.zeros((2, ))
- # TODO hacky weights to keep behavior the same
+ ocp.cost.yref = np.zeros((COST_DIM, ))
+ ocp.cost.yref_e = np.zeros((COST_E_DIM, ))
+ # Add offset to smooth out low speed control
+ # TODO unclear if this right solution long term
+ v_ego_offset = v_ego + SPEED_OFFSET
+ # TODO there are two costs on psi_rate_ego_dot, one
+ # is correlated to jerk the other to steering wheel movement
+ # the steering wheel movement cost is added to prevent excessive
+ # wheel movements
ocp.model.cost_y_expr = vertcat(y_ego,
- ((v_ego +5.0) * psi_ego),
- ((v_ego +5.0) * 4 * curv_rate))
+ v_ego_offset * psi_ego,
+ v_ego_offset * psi_rate_ego,
+ v_ego_offset * psi_rate_ego_dot,
+ psi_rate_ego_dot / (v_ego + 0.1))
ocp.model.cost_y_expr_e = vertcat(y_ego,
- ((v_ego +5.0) * psi_ego))
+ v_ego_offset * psi_ego,
+ v_ego_offset * psi_rate_ego)
# set constraints
ocp.constraints.constr_type = 'BGH'
@@ -103,7 +116,7 @@ def gen_lat_mpc_solver():
ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM'
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
ocp.solver_options.integrator_type = 'ERK'
- ocp.solver_options.nlp_solver_type = 'SQP_RTI'
+ ocp.solver_options.nlp_solver_type = ACADOS_SOLVER_TYPE
ocp.solver_options.qp_solver_iter_max = 1
ocp.solver_options.qp_solver_cond_N = 1
@@ -117,16 +130,16 @@ def gen_lat_mpc_solver():
class LateralMpc():
def __init__(self, x0=np.zeros(X_DIM)):
- self.solver = AcadosOcpSolverFast('lat', N, EXPORT_DIR)
+ self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset(x0)
def reset(self, x0=np.zeros(X_DIM)):
self.x_sol = np.zeros((N+1, X_DIM))
self.u_sol = np.zeros((N, 1))
- self.yref = np.zeros((N+1, 3))
+ self.yref = np.zeros((N+1, COST_DIM))
for i in range(N):
self.solver.cost_set(i, "yref", self.yref[i])
- self.solver.cost_set(N, "yref", self.yref[N][:2])
+ self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
# Somehow needed for stable init
for i in range(N+1):
@@ -139,14 +152,17 @@ class LateralMpc():
self.solve_time = 0.0
self.cost = 0
- def set_weights(self, path_weight, heading_weight, steer_rate_weight):
- W = np.asfortranarray(np.diag([path_weight, heading_weight, steer_rate_weight]))
+ def set_weights(self, path_weight, heading_weight,
+ lat_accel_weight, lat_jerk_weight,
+ steering_rate_weight):
+ W = np.asfortranarray(np.diag([path_weight, heading_weight,
+ lat_accel_weight, lat_jerk_weight,
+ steering_rate_weight]))
for i in range(N):
self.solver.cost_set(i, 'W', W)
- #TODO hacky weights to keep behavior the same
- self.solver.cost_set(N, 'W', (3/20.)*W[:2,:2])
+ self.solver.cost_set(N, 'W', W[:COST_E_DIM,:COST_E_DIM])
- def run(self, x0, p, y_pts, heading_pts):
+ def run(self, x0, p, y_pts, heading_pts, yaw_rate_pts):
x0_cp = np.copy(x0)
p_cp = np.copy(p)
self.solver.constraints_set(0, "lbx", x0_cp)
@@ -154,12 +170,13 @@ class LateralMpc():
self.yref[:,0] = y_pts
v_ego = p_cp[0]
# rotation_radius = p_cp[1]
- self.yref[:,1] = heading_pts*(v_ego+5.0)
+ self.yref[:,1] = heading_pts * (v_ego + SPEED_OFFSET)
+ self.yref[:,2] = yaw_rate_pts * (v_ego + SPEED_OFFSET)
for i in range(N):
self.solver.cost_set(i, "yref", self.yref[i])
self.solver.set(i, "p", p_cp)
self.solver.set(N, "p", p_cp)
- self.solver.cost_set(N, "yref", self.yref[N][:2])
+ self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
t = sec_since_boot()
self.solution_status = self.solver.solve()
@@ -173,5 +190,6 @@ class LateralMpc():
if __name__ == "__main__":
- ocp = gen_lat_mpc_solver()
- AcadosOcpSolver.generate(ocp, json_file=JSON_FILE, build=False)
+ ocp = gen_lat_ocp()
+ AcadosOcpSolver.generate(ocp, json_file=JSON_FILE)
+ # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True)
diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py
index a9c6411394..932ad49535 100644
--- a/selfdrive/controls/lib/lateral_planner.py
+++ b/selfdrive/controls/lib/lateral_planner.py
@@ -1,28 +1,42 @@
import numpy as np
from common.realtime import sec_since_boot, DT_MDL
from common.numpy_fast import interp
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc
-from selfdrive.controls.lib.drive_helpers import CONTROL_N, MPC_COST_LAT, LAT_MPC_N, CAR_ROTATION_RADIUS
-from selfdrive.controls.lib.lane_planner import LanePlanner, TRAJECTORY_SIZE
+from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N
+from selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED
from selfdrive.controls.lib.desire_helper import DesireHelper
import cereal.messaging as messaging
from cereal import log
+TRAJECTORY_SIZE = 33
+CAMERA_OFFSET = 0.04
+
+
+PATH_COST = 1.0
+LATERAL_MOTION_COST = 0.11
+LATERAL_ACCEL_COST = 0.0
+LATERAL_JERK_COST = 0.05
+# Extreme steering rate is unpleasant, even
+# when it does not cause bad jerk.
+# TODO this cost should be lowered when low
+# speed lateral control is stable on all cars
+STEERING_RATE_COST = 800.0
+
class LateralPlanner:
- def __init__(self, CP, use_lanelines=True, wide_camera=False):
- self.use_lanelines = use_lanelines
- self.LP = LanePlanner(wide_camera)
+ def __init__(self, CP):
self.DH = DesireHelper()
+ # Vehicle model parameters used to calculate lateral movement of car
+ self.factor1 = CP.wheelbase - CP.centerToFront
+ self.factor2 = (CP.centerToFront * CP.mass) / (CP.wheelbase * CP.tireStiffnessRear)
self.last_cloudlog_t = 0
- self.steer_rate_cost = CP.steerRateCost
self.solution_invalid_cnt = 0
self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3))
- self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3))
self.plan_yaw = np.zeros((TRAJECTORY_SIZE,))
+ self.plan_yaw_rate = np.zeros((TRAJECTORY_SIZE,))
self.t_idxs = np.arange(TRAJECTORY_SIZE)
self.y_pts = np.zeros(TRAJECTORY_SIZE)
@@ -34,52 +48,50 @@ class LateralPlanner:
self.lat_mpc.reset(x0=self.x0)
def update(self, sm):
- v_ego = sm['carState'].vEgo
+ # clip speed , lateral planning is not possible at 0 speed
+ self.v_ego = max(MIN_SPEED, sm['carState'].vEgo)
measured_curvature = sm['controlsState'].curvature
# Parse model predictions
md = sm['modelV2']
- self.LP.parse_model(md)
if len(md.position.x) == TRAJECTORY_SIZE and len(md.orientation.x) == TRAJECTORY_SIZE:
self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z])
self.t_idxs = np.array(md.position.t)
- self.plan_yaw = list(md.orientation.z)
- if len(md.position.xStd) == TRAJECTORY_SIZE:
- self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd])
+ self.plan_yaw = np.array(md.orientation.z)
+ self.plan_yaw_rate = np.array(md.orientationRate.z)
# Lane change logic
- lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
- self.DH.update(sm['carState'], sm['controlsState'].active, lane_change_prob)
-
- # Turn off lanes during lane change
- if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft:
- self.LP.lll_prob *= self.DH.lane_change_ll_prob
- self.LP.rll_prob *= self.DH.lane_change_ll_prob
-
- # Calculate final driving path and set MPC costs
- if self.use_lanelines:
- d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
- self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, self.steer_rate_cost)
- else:
- d_path_xyz = self.path_xyz
- path_cost = np.clip(abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 1.5) * MPC_COST_LAT.PATH
- # Heading cost is useful at low speed, otherwise end of plan can be off-heading
- heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0])
- self.lat_mpc.set_weights(path_cost, heading_cost, self.steer_rate_cost)
-
- y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
- heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw)
+ desire_state = md.meta.desireState
+ if len(desire_state):
+ self.l_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeLeft]
+ self.r_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeRight]
+ lane_change_prob = self.l_lane_change_prob + self.r_lane_change_prob
+ self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
+
+ d_path_xyz = self.path_xyz
+ self.lat_mpc.set_weights(PATH_COST, LATERAL_MOTION_COST,
+ LATERAL_ACCEL_COST, LATERAL_JERK_COST,
+ STEERING_RATE_COST)
+
+ y_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
+ heading_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw)
+ yaw_rate_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw_rate)
self.y_pts = y_pts
assert len(y_pts) == LAT_MPC_N + 1
assert len(heading_pts) == LAT_MPC_N + 1
- # self.x0[4] = v_ego
- p = np.array([v_ego, CAR_ROTATION_RADIUS])
+ assert len(yaw_rate_pts) == LAT_MPC_N + 1
+ lateral_factor = max(0, self.factor1 - (self.factor2 * self.v_ego**2))
+ p = np.array([self.v_ego, lateral_factor])
self.lat_mpc.run(self.x0,
p,
y_pts,
- heading_pts)
- # init state for next
+ heading_pts,
+ yaw_rate_pts)
+ # init state for next iteration
+ # mpc.u_sol is the desired second derivative of psi given x0 curv state.
+ # with x0[3] = measured_yaw_rate, this would be the actual desired yaw rate.
+ # instead, interpolate x_sol so that x0[3] is the desired yaw rate for lat_control.
self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3])
# Check for infeasible MPC solution
@@ -87,7 +99,7 @@ class LateralPlanner:
t = sec_since_boot()
if mpc_nans or self.lat_mpc.solution_status != 0:
self.reset_mpc()
- self.x0[3] = measured_curvature
+ self.x0[3] = measured_curvature * self.v_ego
if t > self.last_cloudlog_t + 5.0:
self.last_cloudlog_t = t
cloudlog.warning("Lateral mpc - nan: True")
@@ -100,23 +112,21 @@ class LateralPlanner:
def publish(self, sm, pm):
plan_solution_valid = self.solution_invalid_cnt < 2
plan_send = messaging.new_message('lateralPlan')
- plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'modelV2'])
+ plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2'])
lateralPlan = plan_send.lateralPlan
- lateralPlan.laneWidth = float(self.LP.lane_width)
+ lateralPlan.modelMonoTime = sm.logMonoTime['modelV2']
lateralPlan.dPathPoints = self.y_pts.tolist()
lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist()
- lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist()
- lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0]
- lateralPlan.lProb = float(self.LP.lll_prob)
- lateralPlan.rProb = float(self.LP.rll_prob)
- lateralPlan.dProb = float(self.LP.d_prob)
+
+ lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/self.v_ego).tolist()
+ lateralPlan.curvatureRates = [float(x/self.v_ego) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0]
lateralPlan.mpcSolutionValid = bool(plan_solution_valid)
lateralPlan.solverExecutionTime = self.lat_mpc.solve_time
lateralPlan.desire = self.DH.desire
- lateralPlan.useLaneLines = self.use_lanelines
+ lateralPlan.useLaneLines = False
lateralPlan.laneChangeState = self.DH.lane_change_state
lateralPlan.laneChangeDirection = self.DH.lane_change_direction
diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py
index 3ba50fd0cf..545a4c43ff 100644
--- a/selfdrive/controls/lib/longcontrol.py
+++ b/selfdrive/controls/lib/longcontrol.py
@@ -1,25 +1,30 @@
from cereal import car
from common.numpy_fast import clip, interp
from common.realtime import DT_CTRL
-from selfdrive.controls.lib.pid import PIController
-from selfdrive.controls.lib.drive_helpers import CONTROL_N
+from selfdrive.controls.lib.drive_helpers import CONTROL_N, apply_deadzone
+from selfdrive.controls.lib.pid import PIDController
from selfdrive.modeld.constants import T_IDXS
LongCtrlState = car.CarControl.Actuators.LongControlState
-# As per ISO 15622:2018 for all speeds
-ACCEL_MIN_ISO = -3.5 # m/s^2
-ACCEL_MAX_ISO = 2.0 # m/s^2
-
-def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future,
- brake_pressed, cruise_standstill):
- """Update longitudinal control state machine"""
- stopping_condition = (v_ego < 2.0 and cruise_standstill) or \
- (v_ego < CP.vEgoStopping and
- (v_target_future < CP.vEgoStopping or brake_pressed))
-
- starting_condition = v_target_future > CP.vEgoStarting and not cruise_standstill
+def long_control_state_trans(CP, active, long_control_state, v_ego, v_target,
+ v_target_1sec, brake_pressed, cruise_standstill):
+ # Ignore cruise standstill if car has a gas interceptor
+ cruise_standstill = cruise_standstill and not CP.enableGasInterceptor
+ accelerating = v_target_1sec > v_target
+ planned_stop = (v_target < CP.vEgoStopping and
+ v_target_1sec < CP.vEgoStopping and
+ not accelerating)
+ stay_stopped = (v_ego < CP.vEgoStopping and
+ (brake_pressed or cruise_standstill))
+ stopping_condition = planned_stop or stay_stopped
+
+ starting_condition = (v_target_1sec > CP.vEgoStarting and
+ accelerating and
+ not cruise_standstill and
+ not brake_pressed)
+ started_condition = v_ego > CP.vEgoStarting
if not active:
long_control_state = LongCtrlState.off
@@ -33,18 +38,27 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_fut
long_control_state = LongCtrlState.stopping
elif long_control_state == LongCtrlState.stopping:
- if starting_condition:
+ if starting_condition and CP.startingState:
+ long_control_state = LongCtrlState.starting
+ elif starting_condition:
+ long_control_state = LongCtrlState.pid
+
+ elif long_control_state == LongCtrlState.starting:
+ if stopping_condition:
+ long_control_state = LongCtrlState.stopping
+ elif started_condition:
long_control_state = LongCtrlState.pid
return long_control_state
-class LongControl():
+class LongControl:
def __init__(self, CP):
+ self.CP = CP
self.long_control_state = LongCtrlState.off # initialized to off
- self.pid = PIController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
- (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
- k_f = CP.longitudinalTuning.kf, rate=1 / DT_CTRL)
+ self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
+ (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
+ k_f=CP.longitudinalTuning.kf, rate=1 / DT_CTRL)
self.v_pid = 0.0
self.last_output_accel = 0.0
@@ -53,67 +67,68 @@ class LongControl():
self.pid.reset()
self.v_pid = v_pid
- def update(self, active, CS, CP, long_plan, accel_limits, t_since_plan):
+ def update(self, active, CS, long_plan, accel_limits, t_since_plan):
"""Update longitudinal control. This updates the state machine and runs a PID loop"""
# Interp control trajectory
speeds = long_plan.speeds
if len(speeds) == CONTROL_N:
- v_target = interp(t_since_plan, T_IDXS[:CONTROL_N], speeds)
- a_target = interp(t_since_plan, T_IDXS[:CONTROL_N], long_plan.accels)
+ v_target_now = interp(t_since_plan, T_IDXS[:CONTROL_N], speeds)
+ a_target_now = interp(t_since_plan, T_IDXS[:CONTROL_N], long_plan.accels)
+
+ v_target_lower = interp(self.CP.longitudinalActuatorDelayLowerBound + t_since_plan, T_IDXS[:CONTROL_N], speeds)
+ a_target_lower = 2 * (v_target_lower - v_target_now) / self.CP.longitudinalActuatorDelayLowerBound - a_target_now
- v_target_lower = interp(CP.longitudinalActuatorDelayLowerBound + t_since_plan, T_IDXS[:CONTROL_N], speeds)
- a_target_lower = 2 * (v_target_lower - v_target) / CP.longitudinalActuatorDelayLowerBound - a_target
+ v_target_upper = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan, T_IDXS[:CONTROL_N], speeds)
+ a_target_upper = 2 * (v_target_upper - v_target_now) / self.CP.longitudinalActuatorDelayUpperBound - a_target_now
- v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound + t_since_plan, T_IDXS[:CONTROL_N], speeds)
- a_target_upper = 2 * (v_target_upper - v_target) / CP.longitudinalActuatorDelayUpperBound - a_target
+ v_target = min(v_target_lower, v_target_upper)
a_target = min(a_target_lower, a_target_upper)
- v_target_future = speeds[-1]
+ v_target_1sec = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan + 1.0, T_IDXS[:CONTROL_N], speeds)
else:
v_target = 0.0
- v_target_future = 0.0
+ v_target_now = 0.0
+ v_target_1sec = 0.0
a_target = 0.0
- # TODO: This check is not complete and needs to be enforced by MPC
- a_target = clip(a_target, ACCEL_MIN_ISO, ACCEL_MAX_ISO)
-
self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1]
- # Update state machine
output_accel = self.last_output_accel
- self.long_control_state = long_control_state_trans(CP, active, self.long_control_state, CS.vEgo,
- v_target_future, CS.brakePressed,
+ self.long_control_state = long_control_state_trans(self.CP, active, self.long_control_state, CS.vEgo,
+ v_target, v_target_1sec, CS.brakePressed,
CS.cruiseState.standstill)
- if self.long_control_state == LongCtrlState.off or CS.gasPressed:
+ if self.long_control_state == LongCtrlState.off:
self.reset(CS.vEgo)
output_accel = 0.
- # tracking objects and driving
+ elif self.long_control_state == LongCtrlState.stopping:
+ if output_accel > self.CP.stopAccel:
+ output_accel = min(output_accel, 0.0)
+ output_accel -= self.CP.stoppingDecelRate * DT_CTRL
+ self.reset(CS.vEgo)
+
+ elif self.long_control_state == LongCtrlState.starting:
+ output_accel = self.CP.startAccel
+ self.reset(CS.vEgo)
+
elif self.long_control_state == LongCtrlState.pid:
- self.v_pid = v_target
+ self.v_pid = v_target_now
# Toyota starts braking more when it thinks you want to stop
# Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration
- prevent_overshoot = not CP.stoppingControl and CS.vEgo < 1.5 and v_target_future < 0.7 and v_target_future < self.v_pid
- deadzone = interp(CS.vEgo, CP.longitudinalTuning.deadzoneBP, CP.longitudinalTuning.deadzoneV)
+ # TODO too complex, needs to be simplified and tested on toyotas
+ prevent_overshoot = not self.CP.stoppingControl and CS.vEgo < 1.5 and v_target_1sec < 0.7 and v_target_1sec < self.v_pid
+ deadzone = interp(CS.vEgo, self.CP.longitudinalTuning.deadzoneBP, self.CP.longitudinalTuning.deadzoneV)
freeze_integrator = prevent_overshoot
- output_accel = self.pid.update(self.v_pid, CS.vEgo, speed=CS.vEgo, deadzone=deadzone, feedforward=a_target, freeze_integrator=freeze_integrator)
-
- if prevent_overshoot:
- output_accel = min(output_accel, 0.0)
-
- # Intention is to stop, switch to a different brake control until we stop
- elif self.long_control_state == LongCtrlState.stopping:
- # Keep applying brakes until the car is stopped
- if not CS.standstill or output_accel > CP.stopAccel:
- output_accel -= CP.stoppingDecelRate * DT_CTRL
- output_accel = clip(output_accel, accel_limits[0], accel_limits[1])
- self.reset(CS.vEgo)
+ error = self.v_pid - CS.vEgo
+ error_deadzone = apply_deadzone(error, deadzone)
+ output_accel = self.pid.update(error_deadzone, speed=CS.vEgo,
+ feedforward=a_target,
+ freeze_integrator=freeze_integrator)
- self.last_output_accel = output_accel
- final_accel = clip(output_accel, accel_limits[0], accel_limits[1])
+ self.last_output_accel = clip(output_accel, accel_limits[0], accel_limits[1])
- return final_accel
+ return self.last_output_accel
diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
index 4c43985d1f..5a9e69c297 100644
--- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
+++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
@@ -28,8 +28,6 @@ casadi_cost_0 = [
casadi_constraints = [
f'{gen}/long_constraints/long_constr_h_fun.c',
f'{gen}/long_constraints/long_constr_h_fun_jac_uxt_zt.c',
- f'{gen}/long_constraints/long_constr_h_e_fun.c',
- f'{gen}/long_constraints/long_constr_h_e_fun_jac_uxt_zt.c',
]
build_files = [f'{gen}/acados_solver_long.c'] + casadi_model + casadi_cost_y + casadi_cost_e + \
@@ -47,17 +45,26 @@ generated_files = [
f'{gen}/long_model/long_model.h',
f'{gen}/long_constraints/long_h_constraint.h',
- f'{gen}/long_constraints/long_h_e_constraint.h',
f'{gen}/long_cost/long_cost_y_fun.h',
f'{gen}/long_cost/long_cost_y_e_fun.h',
f'{gen}/long_cost/long_cost_y_0_fun.h',
] + build_files
+acados_dir = '#third_party/acados'
+acados_templates_dir = '#pyextra/acados_template/c_templates_tera'
+
+source_list = ['long_mpc.py',
+ f'{acados_dir}/include/acados_c/ocp_nlp_interface.h',
+ f'{acados_dir}/x86_64/lib/libacados.so',
+ f'{acados_dir}/larch64/lib/libacados.so',
+ f'{acados_templates_dir}/acados_solver.in.c',
+]
+
lenv = env.Clone()
lenv.Clean(generated_files, Dir(gen))
lenv.Command(generated_files,
- ["long_mpc.py"],
+ source_list,
f"cd {Dir('.').abspath} && python3 long_mpc.py")
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
index f9f9c31bb4..79a9ec4f0c 100644
--- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
+++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
@@ -3,28 +3,28 @@ import os
import numpy as np
from common.realtime import sec_since_boot
-from common.numpy_fast import clip, interp
-from selfdrive.swaglog import cloudlog
+from common.numpy_fast import clip
+from system.swaglog import cloudlog
from selfdrive.modeld.constants import index_function
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU
if __name__ == '__main__': # generating code
from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
else:
- # from pyextra.acados_template import AcadosOcpSolver as AcadosOcpSolverFast
- from selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverFast # pylint: disable=no-name-in-module, import-error
+ from selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error
from casadi import SX, vertcat
+MODEL_NAME = 'long'
LONG_MPC_DIR = os.path.dirname(os.path.abspath(__file__))
EXPORT_DIR = os.path.join(LONG_MPC_DIR, "c_generated_code")
-JSON_FILE = "acados_ocp_long.json"
+JSON_FILE = os.path.join(LONG_MPC_DIR, "acados_ocp_long.json")
-SOURCES = ['lead0', 'lead1', 'cruise']
+SOURCES = ['lead0', 'lead1', 'cruise', 'e2e']
X_DIM = 3
U_DIM = 1
-PARAM_DIM= 4
+PARAM_DIM = 6
COST_E_DIM = 5
COST_DIM = COST_E_DIM + 1
CONSTR_DIM = 4
@@ -34,21 +34,25 @@ X_EGO_COST = 0.
V_EGO_COST = 0.
A_EGO_COST = 0.
J_EGO_COST = 5.0
-A_CHANGE_COST = .5
+A_CHANGE_COST = 200.
DANGER_ZONE_COST = 100.
-CRASH_DISTANCE = .5
+CRASH_DISTANCE = .25
+LEAD_DANGER_FACTOR = 0.75
LIMIT_COST = 1e6
+ACADOS_SOLVER_TYPE = 'SQP_RTI'
# Fewer timestamps don't hurt performance and lead to
# much better convergence of the MPC with low iterations
N = 12
MAX_T = 10.0
-T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N+1) for idx in range(N+1)]
+T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)]
T_IDXS = np.array(T_IDXS_LST)
+FCW_IDXS = T_IDXS < 5.0
T_DIFFS = np.diff(T_IDXS, prepend=[0.])
MIN_ACCEL = -3.5
+MAX_ACCEL = 2.0
T_FOLLOW = 1.45
COMFORT_BRAKE = 2.5
STOP_DISTANCE = 6.0
@@ -56,8 +60,8 @@ STOP_DISTANCE = 6.0
def get_stopped_equivalence_factor(v_lead):
return (v_lead**2) / (2 * COMFORT_BRAKE)
-def get_safe_obstacle_distance(v_ego):
- return (v_ego**2) / (2 * COMFORT_BRAKE) + T_FOLLOW * v_ego + STOP_DISTANCE
+def get_safe_obstacle_distance(v_ego, t_follow=T_FOLLOW):
+ return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE
def desired_follow_distance(v_ego, v_lead):
return get_safe_obstacle_distance(v_ego) - get_stopped_equivalence_factor(v_lead)
@@ -65,7 +69,7 @@ def desired_follow_distance(v_ego, v_lead):
def gen_long_model():
model = AcadosModel()
- model.name = 'long'
+ model.name = MODEL_NAME
# set up states & controls
x_ego = SX.sym('x_ego')
@@ -88,7 +92,9 @@ def gen_long_model():
a_max = SX.sym('a_max')
x_obstacle = SX.sym('x_obstacle')
prev_a = SX.sym('prev_a')
- model.p = vertcat(a_min, a_max, x_obstacle, prev_a)
+ lead_t_follow = SX.sym('lead_t_follow')
+ lead_danger_factor = SX.sym('lead_danger_factor')
+ model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, lead_danger_factor)
# dynamics model
f_expl = vertcat(v_ego, a_ego, j_ego)
@@ -97,7 +103,7 @@ def gen_long_model():
return model
-def gen_long_mpc_solver():
+def gen_long_ocp():
ocp = AcadosOcp()
ocp.model = gen_long_model()
@@ -122,11 +128,13 @@ def gen_long_mpc_solver():
a_min, a_max = ocp.model.p[0], ocp.model.p[1]
x_obstacle = ocp.model.p[2]
prev_a = ocp.model.p[3]
+ lead_t_follow = ocp.model.p[4]
+ lead_danger_factor = ocp.model.p[5]
ocp.cost.yref = np.zeros((COST_DIM, ))
ocp.cost.yref_e = np.zeros((COST_E_DIM, ))
- desired_dist_comfort = get_safe_obstacle_distance(v_ego)
+ desired_dist_comfort = get_safe_obstacle_distance(v_ego, lead_t_follow)
# The main cost in normal operation is how close you are to the "desired" distance
# from an obstacle at every timestep. This obstacle can be a lead car
@@ -136,7 +144,7 @@ def gen_long_mpc_solver():
x_ego,
v_ego,
a_ego,
- 20*(a_ego - prev_a),
+ a_ego - prev_a,
j_ego]
ocp.model.cost_y_expr = vertcat(*costs)
ocp.model.cost_y_expr_e = vertcat(*costs[:-1])
@@ -147,13 +155,12 @@ def gen_long_mpc_solver():
constraints = vertcat(v_ego,
(a_ego - a_min),
(a_max - a_ego),
- ((x_obstacle - x_ego) - (3/4) * (desired_dist_comfort)) / (v_ego + 10.))
+ ((x_obstacle - x_ego) - lead_danger_factor * (desired_dist_comfort)) / (v_ego + 10.))
ocp.model.con_h_expr = constraints
- ocp.model.con_h_expr_e = vertcat(np.zeros(CONSTR_DIM))
x0 = np.zeros(X_DIM)
ocp.constraints.x0 = x0
- ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0])
+ ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, T_FOLLOW, LEAD_DANGER_FACTOR])
# We put all constraint cost weights to 0 and only set them at runtime
cost_weights = np.zeros(CONSTR_DIM)
@@ -163,9 +170,7 @@ def gen_long_mpc_solver():
ocp.cost.zu = cost_weights
ocp.constraints.lh = np.zeros(CONSTR_DIM)
- ocp.constraints.lh_e = np.zeros(CONSTR_DIM)
ocp.constraints.uh = 1e4*np.ones(CONSTR_DIM)
- ocp.constraints.uh_e = 1e4*np.ones(CONSTR_DIM)
ocp.constraints.idxsh = np.arange(CONSTR_DIM)
# The HPIPM solver can give decent solutions even when it is stopped early
@@ -175,12 +180,13 @@ def gen_long_mpc_solver():
ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM'
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
ocp.solver_options.integrator_type = 'ERK'
- ocp.solver_options.nlp_solver_type = 'SQP_RTI'
- ocp.solver_options.qp_solver_cond_N = N//4
+ ocp.solver_options.nlp_solver_type = ACADOS_SOLVER_TYPE
+ ocp.solver_options.qp_solver_cond_N = 1
# More iterations take too much time and less lead to inaccurate convergence in
# some situations. Ideally we would run just 1 iteration to ensure fixed runtime.
ocp.solver_options.qp_solver_iter_max = 10
+ ocp.solver_options.qp_tol = 1e-3
# set prediction horizon
ocp.solver_options.tf = Tf
@@ -191,13 +197,16 @@ def gen_long_mpc_solver():
class LongitudinalMpc:
- def __init__(self, e2e=False):
- self.e2e = e2e
+ def __init__(self, mode='acc'):
+ self.mode = mode
+ self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset()
self.source = SOURCES[2]
def reset(self):
- self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR)
+ # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
+ self.solver.reset()
+ # self.solver.options_set('print_level', 2)
self.v_solution = np.zeros(N+1)
self.a_solution = np.zeros(N+1)
self.prev_a = np.array(self.a_solution)
@@ -215,56 +224,50 @@ class LongitudinalMpc:
self.status = False
self.crash_cnt = 0.0
self.solution_status = 0
+ # timers
self.solve_time = 0.0
+ self.time_qp_solution = 0.0
+ self.time_linearization = 0.0
+ self.time_integrator = 0.0
self.x0 = np.zeros(X_DIM)
self.set_weights()
- def set_weights(self, prev_accel_constraint=True):
- if self.e2e:
- self.set_weights_for_xva_policy()
- self.params[:,0] = -10.
- self.params[:,1] = 10.
- self.params[:,2] = 1e5
- else:
- self.set_weights_for_lead_policy(prev_accel_constraint)
-
- def set_weights_for_lead_policy(self, prev_accel_constraint=True):
- a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
- W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST]))
+ def set_cost_weights(self, cost_weights, constraint_cost_weights):
+ W = np.asfortranarray(np.diag(cost_weights))
for i in range(N):
- W[4,4] = a_change_cost * np.interp(T_IDXS[i], [0.0, 1.0, 2.0], [1.0, 1.0, 0.0])
+ # TODO don't hardcode A_CHANGE_COST idx
+ # reduce the cost on (a-a_prev) later in the horizon.
+ W[4,4] = cost_weights[4] * np.interp(T_IDXS[i], [0.0, 1.0, 2.0], [1.0, 1.0, 0.0])
self.solver.cost_set(i, 'W', W)
# Setting the slice without the copy make the array not contiguous,
# causing issues with the C interface.
self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))
# Set L2 slack cost on lower bound constraints
- Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST])
+ Zl = np.array(constraint_cost_weights)
for i in range(N):
self.solver.cost_set(i, 'Zl', Zl)
- def set_weights_for_xva_policy(self):
- W = np.asfortranarray(np.diag([0., 10., 1., 10., 0.0, 1.]))
- for i in range(N):
- self.solver.cost_set(i, 'W', W)
- # Setting the slice without the copy make the array not contiguous,
- # causing issues with the C interface.
- self.solver.cost_set(N, 'W', np.copy(W[:COST_E_DIM, :COST_E_DIM]))
-
- # Set L2 slack cost on lower bound constraints
- Zl = np.array([LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0])
- for i in range(N):
- self.solver.cost_set(i, 'Zl', Zl)
+ def set_weights(self, prev_accel_constraint=True):
+ if self.mode == 'acc':
+ a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0
+ cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST]
+ constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST]
+ elif self.mode == 'blended':
+ a_change_cost = 40.0 if prev_accel_constraint else 0
+ cost_weights = [0., 0.1, 0.2, 5.0, a_change_cost, 1.0]
+ constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, 50.0]
+ else:
+ raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set')
+ self.set_cost_weights(cost_weights, constraint_cost_weights)
def set_cur_state(self, v, a):
- if abs(self.x0[1] - v) > 2.:
- self.x0[1] = v
- self.x0[2] = a
+ v_prev = self.x0[1]
+ self.x0[1] = v
+ self.x0[2] = a
+ if abs(v_prev - v) > 2.: # probably only helps if v < v_prev
for i in range(0, N+1):
self.solver.set(i, 'x', self.x0)
- else:
- self.x0[1] = v
- self.x0[2] = a
@staticmethod
def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau):
@@ -298,66 +301,109 @@ class LongitudinalMpc:
return lead_xv
def set_accel_limits(self, min_a, max_a):
+ # TODO this sets a max accel limit, but the minimum limit is only for cruise decel
+ # needs refactor
self.cruise_min_a = min_a
- self.cruise_max_a = max_a
+ self.max_a = max_a
- def update(self, carstate, radarstate, v_cruise):
+ def update(self, carstate, radarstate, v_cruise, x, v, a, j):
v_ego = self.x0[1]
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
- # set accel limits in params
- self.params[:,0] = interp(float(self.status), [0.0, 1.0], [self.cruise_min_a, MIN_ACCEL])
- self.params[:,1] = self.cruise_max_a
-
# To estimate a safe distance from a moving lead, we calculate how much stopping
# distance that lead needs as a minimum. We can add that to the current distance
# and then treat that as a stopped car/obstacle at this new distance.
lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1])
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
- # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
- # when the leads are no factor.
- v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
- v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05)
- v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
- v_lower,
- v_upper)
- cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped)
-
- x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
- self.source = SOURCES[np.argmin(x_obstacles[0])]
- self.params[:,2] = np.min(x_obstacles, axis=1)
- self.params[:,3] = np.copy(self.prev_a)
+ self.params[:,0] = MIN_ACCEL
+ self.params[:,1] = self.max_a
+
+ # Update in ACC mode or ACC/e2e blend
+ if self.mode == 'acc':
+ self.params[:,5] = LEAD_DANGER_FACTOR
+
+ # Fake an obstacle for cruise, this ensures smooth acceleration to set speed
+ # when the leads are no factor.
+ v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
+ v_upper = v_ego + (T_IDXS * self.max_a * 1.05)
+ v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
+ v_lower,
+ v_upper)
+ cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped)
+ x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
+ self.source = SOURCES[np.argmin(x_obstacles[0])]
+
+ # These are not used in ACC mode
+ x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0
+
+ elif self.mode == 'blended':
+ self.params[:,5] = 1.0
+
+ x_obstacles = np.column_stack([lead_0_obstacle,
+ lead_1_obstacle])
+ cruise_target = T_IDXS * np.clip(v_cruise, v_ego - 2.0, 1e3) + x[0]
+ xforward = ((v[1:] + v[:-1]) / 2) * (T_IDXS[1:] - T_IDXS[:-1])
+ x = np.cumsum(np.insert(xforward, 0, x[0]))
+
+ x_and_cruise = np.column_stack([x, cruise_target])
+ x = np.min(x_and_cruise, axis=1)
+
+ self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise'
- self.run()
- if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and
- radarstate.leadOne.modelProb > 0.9):
- self.crash_cnt += 1
else:
- self.crash_cnt = 0
+ raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update')
- def update_with_xva(self, x, v, a):
self.yref[:,1] = x
self.yref[:,2] = v
self.yref[:,3] = a
+ self.yref[:,5] = j
for i in range(N):
- self.solver.cost_set(i, "yref", self.yref[i])
- self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM])
+ self.solver.set(i, "yref", self.yref[i])
+ self.solver.set(N, "yref", self.yref[N][:COST_E_DIM])
+
+ self.params[:,2] = np.min(x_obstacles, axis=1)
self.params[:,3] = np.copy(self.prev_a)
+ self.params[:,4] = T_FOLLOW
+
self.run()
+ if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and
+ radarstate.leadOne.modelProb > 0.9):
+ self.crash_cnt += 1
+ else:
+ self.crash_cnt = 0
+
+ # Check if it got within lead comfort range
+ # TODO This should be done cleaner
+ if self.mode == 'blended':
+ if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], T_FOLLOW))- self.x_sol[:,0] < 0.0):
+ self.source = 'lead0'
+ if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], T_FOLLOW))- self.x_sol[:,0] < 0.0) and \
+ (lead_1_obstacle[0] - lead_0_obstacle[0]):
+ self.source = 'lead1'
def run(self):
+ # t0 = sec_since_boot()
+ # reset = 0
for i in range(N+1):
self.solver.set(i, 'p', self.params[i])
self.solver.constraints_set(0, "lbx", self.x0)
self.solver.constraints_set(0, "ubx", self.x0)
- t = sec_since_boot()
self.solution_status = self.solver.solve()
- self.solve_time = sec_since_boot() - t
+ self.solve_time = float(self.solver.get_stats('time_tot')[0])
+ self.time_qp_solution = float(self.solver.get_stats('time_qp')[0])
+ self.time_linearization = float(self.solver.get_stats('time_lin')[0])
+ self.time_integrator = float(self.solver.get_stats('time_sim')[0])
+
+ # qp_iter = self.solver.get_stats('statistics')[-1][-1] # SQP_RTI specific
+ # print(f"long_mpc timings: tot {self.solve_time:.2e}, qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e}, integrator {self.time_integrator:.2e}, qp_iter {qp_iter}")
+ # res = self.solver.get_residuals()
+ # print(f"long_mpc residuals: {res[0]:.2e}, {res[1]:.2e}, {res[2]:.2e}, {res[3]:.2e}")
+ # self.solver.print_statistics()
for i in range(N+1):
self.x_sol[i] = self.solver.get(i, 'x')
@@ -370,13 +416,17 @@ class LongitudinalMpc:
self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution)
+ t = sec_since_boot()
if self.solution_status != 0:
if t > self.last_cloudlog_t + 5.0:
self.last_cloudlog_t = t
cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}")
self.reset()
+ # reset = 1
+ # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(sec_since_boot() - t0):.2e} qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}")
if __name__ == "__main__":
- ocp = gen_long_mpc_solver()
- AcadosOcpSolver.generate(ocp, json_file=JSON_FILE, build=False)
+ ocp = gen_long_ocp()
+ AcadosOcpSolver.generate(ocp, json_file=JSON_FILE)
+ # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True)
diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py
index 865442f735..a0f6318323 100755
--- a/selfdrive/controls/lib/longitudinal_planner.py
+++ b/selfdrive/controls/lib/longitudinal_planner.py
@@ -1,24 +1,24 @@
#!/usr/bin/env python3
import math
import numpy as np
-from common.numpy_fast import interp
+from common.numpy_fast import clip, interp
import cereal.messaging as messaging
+from common.conversions import Conversions as CV
from common.filter_simple import FirstOrderFilter
from common.realtime import DT_MDL
from selfdrive.modeld.constants import T_IDXS
-from selfdrive.config import Conversions as CV
from selfdrive.controls.lib.longcontrol import LongCtrlState
-from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
+from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
LON_MPC_STEP = 0.2 # first step is 0.2s
AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted
A_CRUISE_MIN = -1.2
-A_CRUISE_MAX_VALS = [1.2, 1.2, 0.8, 0.6]
-A_CRUISE_MAX_BP = [0., 15., 25., 40.]
+A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
+A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
# Lookup table for turns
_A_TOTAL_MAX_V = [1.7, 3.2]
@@ -35,6 +35,8 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
this should avoid accelerating when losing the target in turns
"""
+ # FIXME: This function to calculate lateral accel is incorrect and should use the VehicleModel
+ # The lookup table for turns should also be updated if we do this
a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V)
a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase)
a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.))
@@ -42,46 +44,72 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
return [a_target[0], min(a_target[1], a_x_allowed)]
-class Planner:
+class LongitudinalPlanner:
def __init__(self, CP, init_v=0.0, init_a=0.0):
self.CP = CP
self.mpc = LongitudinalMpc()
-
self.fcw = False
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL)
+ self.v_model_error = 0.0
self.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N)
self.j_desired_trajectory = np.zeros(CONTROL_N)
+ self.solverExecutionTime = 0.0
+
+ @staticmethod
+ def parse_model(model_msg, model_error):
+ if (len(model_msg.position.x) == 33 and
+ len(model_msg.velocity.x) == 33 and
+ len(model_msg.acceleration.x) == 33):
+ x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
+ v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) - model_error
+ a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x)
+ j = np.zeros(len(T_IDXS_MPC))
+ else:
+ x = np.zeros(len(T_IDXS_MPC))
+ v = np.zeros(len(T_IDXS_MPC))
+ a = np.zeros(len(T_IDXS_MPC))
+ j = np.zeros(len(T_IDXS_MPC))
+ return x, v, a, j
def update(self, sm):
- v_ego = sm['carState'].vEgo
+ self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
+ v_ego = sm['carState'].vEgo
v_cruise_kph = sm['controlsState'].vCruise
v_cruise_kph = min(v_cruise_kph, V_CRUISE_MAX)
v_cruise = v_cruise_kph * CV.KPH_TO_MS
- long_control_state = sm['controlsState'].longControlState
+ long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
force_slow_decel = sm['controlsState'].forceDecel
# Reset current state when not engaged, or user is controlling the speed
- reset_state = long_control_state == LongCtrlState.off
- reset_state = reset_state or sm['carState'].gasPressed
+ reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['controlsState'].enabled
# No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
+ if self.mpc.mode == 'acc':
+ accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
+ accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
+ else:
+ accel_limits = [MIN_ACCEL, MAX_ACCEL]
+ accel_limits_turns = [MIN_ACCEL, MAX_ACCEL]
+
if reset_state:
self.v_desired_filter.x = v_ego
- self.a_desired = 0.0
+ # Clip aEgo to cruise limits to prevent large accelerations when becoming active
+ self.a_desired = clip(sm['carState'].aEgo, accel_limits[0], accel_limits[1])
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
+ # Compute model v_ego error
+ if len(sm['modelV2'].temporalPose.trans):
+ self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego
- accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
- accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
if force_slow_decel:
# if required so, force a smooth deceleration
accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL)
@@ -93,14 +121,15 @@ class Planner:
self.mpc.set_weights(prev_accel_constraint)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
- self.mpc.update(sm['carState'], sm['radarState'], v_cruise)
+ x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error)
+ self.mpc.update(sm['carState'], sm['radarState'], v_cruise, x, v, a, j)
self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution)
self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution)
self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution)
# TODO counter is only needed because radar is glitchy, remove once radar is gone
- self.fcw = self.mpc.crash_cnt > 5
+ self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
if self.fcw:
cloudlog.info("FCW triggered")
@@ -112,7 +141,7 @@ class Planner:
def publish(self, sm, pm):
plan_send = messaging.new_message('longitudinalPlan')
- plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState'])
+ plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState'])
longitudinalPlan = plan_send.longitudinalPlan
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
diff --git a/selfdrive/controls/lib/pid.py b/selfdrive/controls/lib/pid.py
index c91eb692cb..965158131b 100644
--- a/selfdrive/controls/lib/pid.py
+++ b/selfdrive/controls/lib/pid.py
@@ -3,30 +3,26 @@ from numbers import Number
from common.numpy_fast import clip, interp
-def apply_deadzone(error, deadzone):
- if error > deadzone:
- error -= deadzone
- elif error < - deadzone:
- error += deadzone
- else:
- error = 0.
- return error
-
-class PIController():
- def __init__(self, k_p, k_i, k_f=0., pos_limit=None, neg_limit=None, rate=100):
- self._k_p = k_p # proportional gain
- self._k_i = k_i # integral gain
+
+class PIDController():
+ def __init__(self, k_p, k_i, k_f=0., k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
+ self._k_p = k_p
+ self._k_i = k_i
+ self._k_d = k_d
self.k_f = k_f # feedforward gain
if isinstance(self._k_p, Number):
self._k_p = [[0], [self._k_p]]
if isinstance(self._k_i, Number):
self._k_i = [[0], [self._k_i]]
+ if isinstance(self._k_d, Number):
+ self._k_d = [[0], [self._k_d]]
self.pos_limit = pos_limit
self.neg_limit = neg_limit
self.i_unwind_rate = 0.3 / rate
self.i_rate = 1.0 / rate
+ self.speed = 0.0
self.reset()
@@ -38,24 +34,33 @@ class PIController():
def k_i(self):
return interp(self.speed, self._k_i[0], self._k_i[1])
+ @property
+ def k_d(self):
+ return interp(self.speed, self._k_d[0], self._k_d[1])
+
+ @property
+ def error_integral(self):
+ return self.i/self.k_i
+
def reset(self):
self.p = 0.0
self.i = 0.0
+ self.d = 0.0
self.f = 0.0
self.control = 0
- def update(self, setpoint, measurement, speed=0.0, override=False, feedforward=0., deadzone=0., freeze_integrator=False):
+ def update(self, error, error_rate=0.0, speed=0.0, override=False, feedforward=0., freeze_integrator=False):
self.speed = speed
- error = float(apply_deadzone(setpoint - measurement, deadzone))
- self.p = error * self.k_p
+ self.p = float(error) * self.k_p
self.f = feedforward * self.k_f
+ self.d = error_rate * self.k_d
if override:
self.i -= self.i_unwind_rate * float(np.sign(self.i))
else:
i = self.i + error * self.k_i * self.i_rate
- control = self.p + self.f + i
+ control = self.p + i + self.d + self.f
# Update when changing i will move the control away from the limits
# or when i will move towards the sign of the error
@@ -64,7 +69,7 @@ class PIController():
not freeze_integrator:
self.i = i
- control = self.p + self.f + self.i
+ control = self.p + self.i + self.d + self.f
self.control = clip(control, self.neg_limit, self.pos_limit)
return self.control
diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py
index 4f87fdf09b..4bb0179267 100644
--- a/selfdrive/controls/lib/radar_helpers.py
+++ b/selfdrive/controls/lib/radar_helpers.py
@@ -1,10 +1,8 @@
from common.numpy_fast import mean
from common.kalman.simple_kalman import KF1D
-from selfdrive.config import RADAR_TO_CAMERA
-# the longer lead decels, the more likely it will keep decelerating
-# TODO is this a good default?
+# Default lead acceleration decay set to 50% at 1s
_LEAD_ACCEL_TAU = 1.5
# radar tracks
@@ -13,6 +11,8 @@ SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters
v_ego_stationary = 4. # no stationary object flag below this speed
+RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car
+RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
class Track():
def __init__(self, v_lead, kalman_params):
@@ -151,7 +151,8 @@ class Cluster():
def potential_low_speed_lead(self, v_ego):
# stop for stuff in front of you and low speed, even without model confirmation
- return abs(self.yRel) < 1.5 and (v_ego < v_ego_stationary) and self.dRel < 25
+ # Radar points closer than 0.75, are almost always glitches on toyota radars
+ return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25)
def is_potential_fcw(self, model_prob):
return model_prob > .9
diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py
index 8345840eca..f15ab2fa56 100755
--- a/selfdrive/controls/lib/tests/test_latcontrol.py
+++ b/selfdrive/controls/lib/tests/test_latcontrol.py
@@ -9,7 +9,7 @@ from selfdrive.car.honda.values import CAR as HONDA
from selfdrive.car.toyota.values import CAR as TOYOTA
from selfdrive.car.nissan.values import CAR as NISSAN
from selfdrive.controls.lib.latcontrol_pid import LatControlPID
-from selfdrive.controls.lib.latcontrol_lqr import LatControlLQR
+from selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from selfdrive.controls.lib.latcontrol_indi import LatControlINDI
from selfdrive.controls.lib.latcontrol_angle import LatControlAngle
from selfdrive.controls.lib.vehicle_model import VehicleModel
@@ -17,7 +17,7 @@ from selfdrive.controls.lib.vehicle_model import VehicleModel
class TestLatControl(unittest.TestCase):
- @parameterized.expand([(HONDA.CIVIC, LatControlPID), (TOYOTA.RAV4, LatControlLQR), (TOYOTA.PRIUS, LatControlINDI), (NISSAN.LEAF, LatControlAngle)])
+ @parameterized.expand([(HONDA.CIVIC, LatControlPID), (TOYOTA.RAV4, LatControlTorque), (TOYOTA.PRIUS, LatControlINDI), (NISSAN.LEAF, LatControlAngle)])
def test_saturation(self, car_name, controller):
CarInterface, CarController, CarState = interfaces[car_name]
CP = CarInterface.get_params(car_name)
@@ -26,7 +26,6 @@ class TestLatControl(unittest.TestCase):
controller = controller(CP, CI)
-
CS = car.CarState.new_message()
CS.vEgo = 30
@@ -35,7 +34,7 @@ class TestLatControl(unittest.TestCase):
params = log.LiveParametersData.new_message()
for _ in range(1000):
- _, _, lac_log = controller.update(True, CS, CP, VM, params, last_actuators, 1, 0)
+ _, _, lac_log = controller.update(True, CS, CP, VM, params, last_actuators, True, 1, 0)
self.assertTrue(lac_log.saturated)
diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py
index f2636027e8..3e08cb0aa0 100755
--- a/selfdrive/controls/lib/tests/test_vehicle_model.py
+++ b/selfdrive/controls/lib/tests/test_vehicle_model.py
@@ -41,7 +41,7 @@ class TestVehicleModel(unittest.TestCase):
self.assertAlmostEqual(float(yr1), yr2)
def test_syn_ss_sol_simulate(self):
- """Verifies that dyn_ss_sol mathes a simulation"""
+ """Verifies that dyn_ss_sol matches a simulation"""
for roll in np.linspace(math.radians(-20), math.radians(20), num=11):
for u in np.linspace(1, 30, num=10):
diff --git a/selfdrive/controls/lib/vehicle_model.py b/selfdrive/controls/lib/vehicle_model.py
index 3f180d3252..0750384918 100755
--- a/selfdrive/controls/lib/vehicle_model.py
+++ b/selfdrive/controls/lib/vehicle_model.py
@@ -29,22 +29,22 @@ class VehicleModel:
CP: Car Parameters
"""
# for math readability, convert long names car params into short names
- self.m = CP.mass
- self.j = CP.rotationalInertia
- self.l = CP.wheelbase
- self.aF = CP.centerToFront
- self.aR = CP.wheelbase - CP.centerToFront
- self.chi = CP.steerRatioRear
-
- self.cF_orig = CP.tireStiffnessFront
- self.cR_orig = CP.tireStiffnessRear
+ self.m: float = CP.mass
+ self.j: float = CP.rotationalInertia
+ self.l: float = CP.wheelbase
+ self.aF: float = CP.centerToFront
+ self.aR: float = CP.wheelbase - CP.centerToFront
+ self.chi: float = CP.steerRatioRear
+
+ self.cF_orig: float = CP.tireStiffnessFront
+ self.cR_orig: float = CP.tireStiffnessRear
self.update_params(1.0, CP.steerRatio)
def update_params(self, stiffness_factor: float, steer_ratio: float) -> None:
"""Update the vehicle model with a new stiffness factor and steer ratio"""
- self.cF = stiffness_factor * self.cF_orig
- self.cR = stiffness_factor * self.cR_orig
- self.sR = steer_ratio
+ self.cF: float = stiffness_factor * self.cF_orig
+ self.cR: float = stiffness_factor * self.cR_orig
+ self.sR: float = steer_ratio
def steady_state_sol(self, sa: float, u: float, roll: float) -> np.ndarray:
"""Returns the steady state solution.
@@ -221,10 +221,10 @@ def dyn_ss_sol(sa: float, u: float, roll: float, VM: VehicleModel) -> np.ndarray
"""
A, B = create_dyn_state_matrices(u, VM)
inp = np.array([[sa], [roll]])
- return -solve(A, B) @ inp
+ return -solve(A, B) @ inp # type: ignore
-def calc_slip_factor(VM):
+def calc_slip_factor(VM: VehicleModel) -> float:
"""The slip factor is a measure of how the curvature changes with speed
it's positive for Oversteering vehicle, negative (usual case) otherwise.
"""
diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py
index 02f1c19a77..93d0c80dac 100755
--- a/selfdrive/controls/plannerd.py
+++ b/selfdrive/controls/plannerd.py
@@ -2,31 +2,25 @@
from cereal import car
from common.params import Params
from common.realtime import Priority, config_realtime_process
-from selfdrive.swaglog import cloudlog
-from selfdrive.controls.lib.longitudinal_planner import Planner
+from system.swaglog import cloudlog
+from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from selfdrive.controls.lib.lateral_planner import LateralPlanner
-from selfdrive.hardware import TICI
import cereal.messaging as messaging
def plannerd_thread(sm=None, pm=None):
- config_realtime_process(5 if TICI else 2, Priority.CTRL_LOW)
+ config_realtime_process(5, Priority.CTRL_LOW)
cloudlog.info("plannerd is waiting for CarParams")
params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True))
cloudlog.info("plannerd got CarParams: %s", CP.carName)
- use_lanelines = not params.get_bool('EndToEndToggle')
- wide_camera = params.get_bool('EnableWideCamera') if TICI else False
-
- cloudlog.event("e2e mode", on=use_lanelines)
-
- longitudinal_planner = Planner(CP)
- lateral_planner = LateralPlanner(CP, use_lanelines=use_lanelines, wide_camera=wide_camera)
+ longitudinal_planner = LongitudinalPlanner(CP)
+ lateral_planner = LateralPlanner(CP)
if sm is None:
- sm = messaging.SubMaster(['carState', 'controlsState', 'radarState', 'modelV2'],
+ sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'],
poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState'])
if pm is None:
diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py
index 65f8480c7c..3d958139d6 100755
--- a/selfdrive/controls/radard.py
+++ b/selfdrive/controls/radard.py
@@ -8,11 +8,9 @@ from cereal import car
from common.numpy_fast import interp
from common.params import Params
from common.realtime import Ratekeeper, Priority, config_realtime_process
-from selfdrive.config import RADAR_TO_CAMERA
from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid
-from selfdrive.controls.lib.radar_helpers import Cluster, Track
-from selfdrive.swaglog import cloudlog
-from selfdrive.hardware import TICI
+from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA
+from system.swaglog import cloudlog
class KalmanParams():
@@ -104,7 +102,7 @@ class RadarD():
self.ready = False
- def update(self, sm, rr, enable_lead):
+ def update(self, sm, rr):
self.current_time = 1e-9*max(sm.logMonoTime.values())
if sm.updated['carState']:
@@ -164,24 +162,23 @@ class RadarD():
# *** publish radarState ***
dat = messaging.new_message('radarState')
- dat.valid = sm.all_alive_and_valid() and len(rr.errors) == 0
+ dat.valid = sm.all_checks() and len(rr.errors) == 0
radarState = dat.radarState
radarState.mdMonoTime = sm.logMonoTime['modelV2']
radarState.canMonoTimes = list(rr.canMonoTimes)
radarState.radarErrors = list(rr.errors)
radarState.carStateMonoTime = sm.logMonoTime['carState']
- if enable_lead:
- leads_v3 = sm['modelV2'].leadsV3
- if len(leads_v3) > 1:
- radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True)
- radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False)
+ leads_v3 = sm['modelV2'].leadsV3
+ if len(leads_v3) > 1:
+ radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True)
+ radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False)
return dat
# fuses camera and radar data for best lead detection
def radard_thread(sm=None, pm=None, can_sock=None):
- config_realtime_process(5 if TICI else 2, Priority.CTRL_LOW)
+ config_realtime_process(5, Priority.CTRL_LOW)
# wait for stats about the car to come in from controls
cloudlog.info("radard is waiting for CarParams")
@@ -205,9 +202,6 @@ def radard_thread(sm=None, pm=None, can_sock=None):
rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None)
RD = RadarD(CP.radarTimeStep, RI.delay)
- # TODO: always log leads once we can hide them conditionally
- enable_lead = CP.openpilotLongitudinalControl or not CP.radarOffCan
-
while 1:
can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True)
rr = RI.update(can_strings)
@@ -217,7 +211,7 @@ def radard_thread(sm=None, pm=None, can_sock=None):
sm.update(0)
- dat = RD.update(sm, rr, enable_lead)
+ dat = RD.update(sm, rr)
dat.radarState.cumLagMs = -rk.remaining*1000.
pm.send('radarState', dat)
diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py
index 2502bed06b..9ed7eee122 100755
--- a/selfdrive/controls/tests/test_alerts.py
+++ b/selfdrive/controls/tests/test_alerts.py
@@ -10,6 +10,7 @@ from common.basedir import BASEDIR
from common.params import Params
from selfdrive.controls.lib.events import Alert, EVENTS, ET
from selfdrive.controls.lib.alertmanager import set_offroad_alert
+from selfdrive.test.process_replay.process_replay import FakeSubMaster, CONFIGS
AlertSize = log.ControlsState.AlertSize
@@ -19,8 +20,7 @@ OFFROAD_ALERTS_PATH = os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offro
ALERTS = []
for event_types in EVENTS.values():
for alert in event_types.values():
- if isinstance(alert, Alert):
- ALERTS.append(alert)
+ ALERTS.append(alert)
class TestAlerts(unittest.TestCase):
@@ -30,6 +30,12 @@ class TestAlerts(unittest.TestCase):
with open(OFFROAD_ALERTS_PATH) as f:
cls.offroad_alerts = json.loads(f.read())
+ # Create fake objects for callback
+ cls.CS = car.CarState.new_message()
+ cls.CP = car.CarParams.new_message()
+ cfg = [c for c in CONFIGS if c.proc_name == 'controlsd'][0]
+ cls.sm = FakeSubMaster(cfg.pub_sub.keys())
+
def test_events_defined(self):
# Ensure all events in capnp schema are defined in events.py
events = car.CarEvent.EventName.schema.enumerants
@@ -42,23 +48,23 @@ class TestAlerts(unittest.TestCase):
# ensure alert text doesn't exceed allowed width
def test_alert_text_length(self):
font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts")
- regular_font_path = os.path.join(font_path, "opensans_semibold.ttf")
- bold_font_path = os.path.join(font_path, "opensans_semibold.ttf")
- semibold_font_path = os.path.join(font_path, "opensans_semibold.ttf")
-
- max_text_width = 1920 - 300 # full screen width is useable, minus sidebar
- # TODO: get exact scale factor. found this empirically, works well enough
- font_scale_factor = 1.85 # factor to scale from nanovg units to PIL
+ regular_font_path = os.path.join(font_path, "Inter-SemiBold.ttf")
+ bold_font_path = os.path.join(font_path, "Inter-Bold.ttf")
+ semibold_font_path = os.path.join(font_path, "Inter-SemiBold.ttf")
+ max_text_width = 2160 - 300 # full screen width is usable, minus sidebar
draw = ImageDraw.Draw(Image.new('RGB', (0, 0)))
fonts = {
- AlertSize.small: [ImageFont.truetype(semibold_font_path, int(40 * font_scale_factor))],
- AlertSize.mid: [ImageFont.truetype(bold_font_path, int(48 * font_scale_factor)),
- ImageFont.truetype(regular_font_path, int(36 * font_scale_factor))],
+ AlertSize.small: [ImageFont.truetype(semibold_font_path, 74)],
+ AlertSize.mid: [ImageFont.truetype(bold_font_path, 88),
+ ImageFont.truetype(regular_font_path, 66)],
}
for alert in ALERTS:
+ if not isinstance(alert, Alert):
+ alert = alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100)
+
# for full size alerts, both text fields wrap the text,
# so it's unlikely that they would go past the max width
if alert.alert_size in (AlertSize.none, AlertSize.full):
diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py
old mode 100644
new mode 100755
index 364be8db79..cd1d31cf07
--- a/selfdrive/controls/tests/test_cruise_speed.py
+++ b/selfdrive/controls/tests/test_cruise_speed.py
@@ -1,32 +1,149 @@
#!/usr/bin/env python3
-import unittest
import numpy as np
+from parameterized import parameterized_class
+import unittest
+
+from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN, IMPERIAL_INCREMENT
+from cereal import car
+from common.conversions import Conversions as CV
from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
-def run_cruise_simulation(cruise, t_end=100.):
+ButtonEvent = car.CarState.ButtonEvent
+ButtonType = car.CarState.ButtonEvent.Type
+
+
+def run_cruise_simulation(cruise, e2e, t_end=20.):
man = Maneuver(
'',
duration=t_end,
- initial_speed=float(0.),
+ initial_speed=max(cruise - 1., 0.0),
lead_relevancy=True,
initial_distance_lead=100,
cruise_values=[cruise],
prob_lead_values=[0.0],
breakpoints=[0.],
+ e2e=e2e,
)
valid, output = man.evaluate()
assert valid
- return output[-1,3]
+ return output[-1, 3]
class TestCruiseSpeed(unittest.TestCase):
def test_cruise_speed(self):
- for speed in np.arange(5, 40, 5):
- print(f'Testing {speed} m/s')
- cruise_speed = float(speed)
+ for e2e in [False, True]:
+ for speed in np.arange(5, 40, 5):
+ print(f'Testing {speed} m/s')
+ cruise_speed = float(speed)
+
+ simulation_steady_state = run_cruise_simulation(cruise_speed, e2e)
+ self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s')
+
+
+# TODO: test pcmCruise
+@parameterized_class(('pcm_cruise',), [(False,)])
+class TestVCruiseHelper(unittest.TestCase):
+ def setUp(self):
+ self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101
+ self.v_cruise_helper = VCruiseHelper(self.CP)
+ self.reset_cruise_speed_state()
+
+ def reset_cruise_speed_state(self):
+ # Two resets previous cruise speed
+ for _ in range(2):
+ self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False)
+
+ def enable(self, v_ego):
+ # Simulates user pressing set with a current speed
+ self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego))
+
+ def test_adjust_speed(self):
+ """
+ Asserts speed changes on falling edges of buttons.
+ """
+
+ self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS)
+
+ for btn in (ButtonType.accelCruise, ButtonType.decelCruise):
+ for pressed in (True, False):
+ CS = car.CarState(cruiseState={"available": True})
+ CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)]
+
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
+ self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
+
+ def test_rising_edge_enable(self):
+ """
+ Some car interfaces may enable on rising edge of a button,
+ ensure we don't adjust speed if enabled changes mid-press.
+ """
+
+ # NOTE: enabled is always one frame behind the result from button press in controlsd
+ for enabled, pressed in ((False, False),
+ (False, True),
+ (True, False)):
+ CS = car.CarState(cruiseState={"available": True})
+ CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False)
+ if pressed:
+ self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS)
+
+ # Expected diff on enabling. Speed should not change on falling edge of pressed
+ self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
+
+ def test_resume_in_standstill(self):
+ """
+ Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill.
+ """
+
+ self.enable(0)
+
+ for standstill in (True, False):
+ for pressed in (True, False):
+ CS = car.CarState(cruiseState={"available": True, "standstill": standstill})
+ CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
+
+ # speed should only update if not at standstill and button falling edge
+ should_equal = standstill or pressed
+ self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
+
+ def test_set_gas_pressed(self):
+ """
+ Asserts pressing set while enabled with gas pressed sets
+ the speed to the maximum of vEgo and current cruise speed.
+ """
+
+ for v_ego in np.linspace(0, 100, 101):
+ self.reset_cruise_speed_state()
+ self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS)
+
+ # first decrement speed, then perform gas pressed logic
+ expected_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - IMPERIAL_INCREMENT
+ expected_v_cruise_kph = max(expected_v_cruise_kph, v_ego * CV.MS_TO_KPH) # clip to min of vEgo
+ expected_v_cruise_kph = float(np.clip(round(expected_v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX))
+
+ CS = car.CarState(vEgo=float(v_ego), gasPressed=True, cruiseState={"available": True})
+ CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=False)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
+
+ # TODO: fix skipping first run due to enabled on rising edge exception
+ if v_ego == 0.0:
+ continue
+ self.assertEqual(expected_v_cruise_kph, self.v_cruise_helper.v_cruise_kph)
+
+ def test_initialize_v_cruise(self):
+ """
+ Asserts allowed cruise speeds on enabling with SET.
+ """
+
+ for v_ego in np.linspace(0, 100, 101):
+ self.reset_cruise_speed_state()
+ self.assertFalse(self.v_cruise_helper.v_cruise_initialized)
- simulation_steady_state = run_cruise_simulation(cruise_speed)
- self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s')
+ self.enable(float(v_ego))
+ self.assertTrue(V_CRUISE_ENABLE_MIN <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX)
+ self.assertTrue(self.v_cruise_helper.v_cruise_initialized)
if __name__ == "__main__":
diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py
index a0110a4dab..5185867d2d 100644
--- a/selfdrive/controls/tests/test_following_distance.py
+++ b/selfdrive/controls/tests/test_following_distance.py
@@ -5,7 +5,8 @@ import numpy as np
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance
from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
-def run_following_distance_simulation(v_lead, t_end=100.0):
+
+def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False):
man = Maneuver(
'',
duration=t_end,
@@ -14,6 +15,7 @@ def run_following_distance_simulation(v_lead, t_end=100.0):
initial_distance_lead=100,
speed_lead_values=[v_lead],
breakpoints=[0.],
+ e2e=e2e,
)
valid, output = man.evaluate()
assert valid
@@ -21,15 +23,15 @@ def run_following_distance_simulation(v_lead, t_end=100.0):
class TestFollowingDistance(unittest.TestCase):
- def test_following_distanc(self):
- for speed in np.arange(0, 40, 5):
- print(f'Testing {speed} m/s')
- v_lead = float(speed)
-
- simulation_steady_state = run_following_distance_simulation(v_lead)
- correct_steady_state = desired_follow_distance(v_lead, v_lead)
-
- self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(correct_steady_state*.1 + .5))
+ def test_following_distance(self):
+ for e2e in [False, True]:
+ for speed in np.arange(0, 40, 5):
+ print(f'Testing {speed} m/s')
+ v_lead = float(speed)
+ simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e)
+ correct_steady_state = desired_follow_distance(v_lead, v_lead)
+ err_ratio = 0.2 if e2e else 0.1
+ self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5))
if __name__ == "__main__":
diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py
index 4630e28a61..df5154b2b4 100644
--- a/selfdrive/controls/tests/test_lateral_mpc.py
+++ b/selfdrive/controls/tests/test_lateral_mpc.py
@@ -1,7 +1,8 @@
import unittest
import numpy as np
from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc
-from selfdrive.controls.lib.drive_helpers import LAT_MPC_N, CAR_ROTATION_RADIUS
+from selfdrive.controls.lib.drive_helpers import CAR_ROTATION_RADIUS
+from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N
def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvature_init=0.,
@@ -9,10 +10,11 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur
if lat_mpc is None:
lat_mpc = LateralMpc()
- lat_mpc.set_weights(1., 1., 1.)
+ lat_mpc.set_weights(1., .1, 0.0, .05, 800)
y_pts = poly_shift * np.ones(LAT_MPC_N + 1)
heading_pts = np.zeros(LAT_MPC_N + 1)
+ curv_rate_pts = np.zeros(LAT_MPC_N + 1)
x0 = np.array([x_init, y_init, psi_init, curvature_init])
p = np.array([v_ref, CAR_ROTATION_RADIUS])
@@ -20,7 +22,7 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur
# converge in no more than 10 iterations
for _ in range(10):
lat_mpc.run(x0, p,
- y_pts, heading_pts)
+ y_pts, heading_pts, curv_rate_pts)
return lat_mpc.x_sol
@@ -75,9 +77,9 @@ class TestLateralMpc(unittest.TestCase):
def test_switch_convergence(self):
lat_mpc = LateralMpc()
- sol = run_mpc(lat_mpc=lat_mpc, poly_shift=30.0, v_ref=7.0)
+ sol = run_mpc(lat_mpc=lat_mpc, poly_shift=3.0, v_ref=7.0)
right_psi_deg = np.degrees(sol[:,2])
- sol = run_mpc(lat_mpc=lat_mpc, poly_shift=-30.0, v_ref=7.0)
+ sol = run_mpc(lat_mpc=lat_mpc, poly_shift=-3.0, v_ref=7.0)
left_psi_deg = np.degrees(sol[:,2])
np.testing.assert_almost_equal(right_psi_deg, -left_psi_deg, decimal=3)
diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py
index 9d13453045..ba2d2f5c02 100755
--- a/selfdrive/controls/tests/test_startup.py
+++ b/selfdrive/controls/tests/test_startup.py
@@ -18,7 +18,7 @@ Ecu = car.CarParams.Ecu
COROLLA_FW_VERSIONS = [
(Ecu.engine, 0x7e0, None, b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00'),
- (Ecu.esp, 0x7b0, None, b'F152602190\x00\x00\x00\x00\x00\x00'),
+ (Ecu.abs, 0x7b0, None, b'F152602190\x00\x00\x00\x00\x00\x00'),
(Ecu.eps, 0x7a1, None, b'8965B02181\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdRadar, 0x750, 0xf, b'8821F4702100\x00\x00\x00\x00'),
(Ecu.fwdCamera, 0x750, 0x6d, b'8646F0201101\x00\x00\x00\x00'),
@@ -29,7 +29,7 @@ COROLLA_FW_VERSIONS_NO_DSU = COROLLA_FW_VERSIONS[:-1]
CX5_FW_VERSIONS = [
(Ecu.engine, 0x7e0, None, b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
- (Ecu.esp, 0x760, None, b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
+ (Ecu.abs, 0x760, None, b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.eps, 0x730, None, b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdRadar, 0x764, None, b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdCamera, 0x706, None, b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
@@ -42,27 +42,27 @@ class TestStartup(unittest.TestCase):
# TODO: test EventName.startup for release branches
# officially supported car
- (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS),
- (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS),
+ (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"),
+ (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"),
# dashcamOnly car
- (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS),
- (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS),
+ (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"),
+ (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"),
# unrecognized car with no fw
- (EventName.startupNoFw, None, None),
- (EventName.startupNoFw, None, None),
+ (EventName.startupNoFw, None, None, ""),
+ (EventName.startupNoFw, None, None, ""),
# unrecognized car
- (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1]),
- (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1]),
+ (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"),
+ (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"),
# fuzzy match
- (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY),
- (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY),
+ (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"),
+ (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"),
])
@with_processes(['controlsd'])
- def test_startup_alert(self, expected_event, car_model, fw_versions):
+ def test_startup_alert(self, expected_event, car_model, fw_versions, brand):
# TODO: this should be done without any real sockets
controls_sock = messaging.sub_sock("controlsState")
@@ -82,6 +82,7 @@ class TestStartup(unittest.TestCase):
f.ecu = ecu
f.address = addr
f.fwVersion = version
+ f.brand = brand
if subaddress is not None:
f.subAddress = subaddress
@@ -93,6 +94,9 @@ class TestStartup(unittest.TestCase):
time.sleep(2) # wait for controlsd to be ready
+ pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]]))
+ time.sleep(0.1)
+
msg = messaging.new_message('pandaStates', 1)
msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno
pm.send('pandaStates', msg)
diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py
new file mode 100755
index 0000000000..8f263a98e7
--- /dev/null
+++ b/selfdrive/controls/tests/test_state_machine.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+import unittest
+
+from cereal import car, log
+from common.realtime import DT_CTRL
+from selfdrive.car.car_helpers import interfaces
+from selfdrive.controls.controlsd import Controls, SOFT_DISABLE_TIME
+from selfdrive.controls.lib.events import Events, ET, Alert, Priority, AlertSize, AlertStatus, VisualAlert, \
+ AudibleAlert, EVENTS
+
+State = log.ControlsState.OpenpilotState
+
+# The event types that maintain the current state
+MAINTAIN_STATES = {State.enabled: (None,), State.disabled: (None,), State.softDisabling: (ET.SOFT_DISABLE,),
+ State.preEnabled: (ET.PRE_ENABLE,), State.overriding: (ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)}
+ALL_STATES = tuple(State.schema.enumerants.values())
+# The event types checked in DISABLED section of state machine
+ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
+
+
+def make_event(event_types):
+ event = {}
+ for ev in event_types:
+ event[ev] = Alert("", "", AlertStatus.normal, AlertSize.small, Priority.LOW,
+ VisualAlert.none, AudibleAlert.none, 1.)
+ EVENTS[0] = event
+ return 0
+
+
+class TestStateMachine(unittest.TestCase):
+
+ def setUp(self):
+ CarInterface, CarController, CarState = interfaces["mock"]
+ CP = CarInterface.get_params("mock")
+ CI = CarInterface(CP, CarController, CarState)
+
+ self.controlsd = Controls(CI=CI)
+ self.controlsd.events = Events()
+ self.controlsd.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL)
+ self.CS = car.CarState()
+
+ def test_immediate_disable(self):
+ for state in ALL_STATES:
+ for et in MAINTAIN_STATES[state]:
+ self.controlsd.events.add(make_event([et, ET.IMMEDIATE_DISABLE]))
+ self.controlsd.state = state
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(State.disabled, self.controlsd.state)
+ self.controlsd.events.clear()
+
+ def test_user_disable(self):
+ for state in ALL_STATES:
+ for et in MAINTAIN_STATES[state]:
+ self.controlsd.events.add(make_event([et, ET.USER_DISABLE]))
+ self.controlsd.state = state
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(State.disabled, self.controlsd.state)
+ self.controlsd.events.clear()
+
+ def test_soft_disable(self):
+ for state in ALL_STATES:
+ if state == State.preEnabled: # preEnabled considers NO_ENTRY instead
+ continue
+ for et in MAINTAIN_STATES[state]:
+ self.controlsd.events.add(make_event([et, ET.SOFT_DISABLE]))
+ self.controlsd.state = state
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(self.controlsd.state, State.disabled if state == State.disabled else State.softDisabling)
+ self.controlsd.events.clear()
+
+ def test_soft_disable_timer(self):
+ self.controlsd.state = State.enabled
+ self.controlsd.events.add(make_event([ET.SOFT_DISABLE]))
+ self.controlsd.state_transition(self.CS)
+ for _ in range(int(SOFT_DISABLE_TIME / DT_CTRL)):
+ self.assertEqual(self.controlsd.state, State.softDisabling)
+ self.controlsd.state_transition(self.CS)
+
+ self.assertEqual(self.controlsd.state, State.disabled)
+
+ def test_no_entry(self):
+ # Make sure noEntry keeps us disabled
+ for et in ENABLE_EVENT_TYPES:
+ self.controlsd.events.add(make_event([ET.NO_ENTRY, et]))
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(self.controlsd.state, State.disabled)
+ self.controlsd.events.clear()
+
+ def test_no_entry_pre_enable(self):
+ # preEnabled with noEntry event
+ self.controlsd.state = State.preEnabled
+ self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE]))
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(self.controlsd.state, State.preEnabled)
+
+ def test_maintain_states(self):
+ # Given current state's event type, we should maintain state
+ for state in ALL_STATES:
+ for et in MAINTAIN_STATES[state]:
+ self.controlsd.state = state
+ self.controlsd.events.add(make_event([et]))
+ self.controlsd.state_transition(self.CS)
+ self.assertEqual(self.controlsd.state, state)
+ self.controlsd.events.clear()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md
index d49db25d09..83b8a994db 100644
--- a/selfdrive/debug/README.md
+++ b/selfdrive/debug/README.md
@@ -19,7 +19,7 @@ optional arguments:
```
usage: dump.py [-h] [--pipe] [--raw] [--json] [--dump-json] [--no-print] [--addr ADDR] [--values VALUES] [socket [socket ...]]
-Dump communcation sockets. See cereal/services.py for a complete list of available sockets.
+Dump communication sockets. See cereal/services.py for a complete list of available sockets.
positional arguments:
socket socket names to dump. defaults to all services defined in cereal
@@ -52,7 +52,7 @@ optional arguments:
-h, --help show this help message and exit
--debug enable ISO-TP/UDS stack debugging output
-This tool is meant to run directly on a vehicle-installed comma two or comma three, with
+This tool is meant to run directly on a vehicle-installed comma three, with
the openpilot/tmux processes stopped. It should also work on a separate PC with a USB-
attached comma panda. Vehicle ignition must be on. Recommend engine not be running when
making changes. Must turn ignition off and on again for any changes to take effect.
diff --git a/selfdrive/debug/adb.sh b/selfdrive/debug/adb.sh
index 8a04d4aefd..919a82fefc 100755
--- a/selfdrive/debug/adb.sh
+++ b/selfdrive/debug/adb.sh
@@ -4,12 +4,7 @@ set -e
PORT=5555
setprop service.adb.tcp.port $PORT
-if [ -f /EON ]; then
- stop adbd
- start adbd
-else
- sudo systemctl start adbd
-fi
+sudo systemctl start adbd
IP=$(echo $SSH_CONNECTION | awk '{ print $3}')
echo "then, connect on your computer:"
diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py
index 4fa775ac17..ff98c20e60 100755
--- a/selfdrive/debug/can_print_changes.py
+++ b/selfdrive/debug/can_print_changes.py
@@ -1,46 +1,107 @@
#!/usr/bin/env python3
+import argparse
import binascii
-import sys
+import time
from collections import defaultdict
import cereal.messaging as messaging
-from common.realtime import sec_since_boot
+from selfdrive.debug.can_table import can_table
+from tools.lib.logreader import logreader_from_route_or_segment
+RED = '\033[91m'
+CLEAR = '\033[0m'
+
+def update(msgs, bus, dat, low_to_high, high_to_low, quiet=False):
+ for x in msgs:
+ if x.which() != 'can':
+ continue
+
+ for y in x.can:
+ if y.src == bus:
+ dat[y.address] = y.dat
+
+ i = int.from_bytes(y.dat, byteorder='big')
+ l_h = low_to_high[y.address]
+ h_l = high_to_low[y.address]
+
+ change = None
+ if (i | l_h) != l_h:
+ low_to_high[y.address] = i | l_h
+ change = "+"
+
+ if (~i | h_l) != h_l:
+ high_to_low[y.address] = ~i | h_l
+ change = "-"
+
+ if change and not quiet:
+ print(f"{time.monotonic():.2f}\t{hex(y.address)} ({y.address})\t{change}{binascii.hexlify(y.dat)}")
-def can_printer(bus=0):
- """Collects messages and prints when a new bit transition is observed.
- This is very useful to find signals based on user triggered actions, such as blinkers and seatbelt.
- Leave the script running until no new transitions are seen, then perform the action."""
- logcan = messaging.sub_sock('can')
+def can_printer(bus=0, init_msgs=None, new_msgs=None, table=False):
+ logcan = messaging.sub_sock('can', timeout=10)
+
+ dat = defaultdict(int)
low_to_high = defaultdict(int)
high_to_low = defaultdict(int)
- while 1:
- can_recv = messaging.drain_sock(logcan, wait_for_one=True)
- for x in can_recv:
- for y in x.can:
- if y.src == bus:
- i = int.from_bytes(y.dat, byteorder='big')
+ if init_msgs is not None:
+ update(init_msgs, bus, dat, low_to_high, high_to_low, quiet=True)
- l_h = low_to_high[y.address]
- h_l = high_to_low[y.address]
+ low_to_high_init = low_to_high.copy()
+ high_to_low_init = high_to_low.copy()
- change = None
- if (i | l_h) != l_h:
- low_to_high[y.address] = i | l_h
- change = "+"
+ if new_msgs is not None:
+ update(new_msgs, bus, dat, low_to_high, high_to_low)
+ else:
+ # Live mode
+ print(f"Waiting for messages on bus {bus}")
+ try:
+ while 1:
+ can_recv = messaging.drain_sock(logcan)
+ update(can_recv, bus, dat, low_to_high, high_to_low)
+ time.sleep(0.02)
+ except KeyboardInterrupt:
+ pass
- if (~i | h_l) != h_l:
- high_to_low[y.address] = ~i | h_l
- change = "-"
+ print("\n\n")
+ tables = ""
+ for addr in sorted(dat.keys()):
+ init = low_to_high_init[addr] & high_to_low_init[addr]
+ now = low_to_high[addr] & high_to_low[addr]
+ d = now & ~init
+ if d == 0:
+ continue
+ b = d.to_bytes(len(dat[addr]), byteorder='big')
- if change:
- print(f"{sec_since_boot():.2f}\t{hex(y.address)} ({y.address})\t{change}{binascii.hexlify(y.dat)}")
+ byts = ''.join([(c if c == '0' else f'{RED}{c}{CLEAR}') for c in str(binascii.hexlify(b))[2:-1]])
+ header = f"{hex(addr).ljust(6)}({str(addr).ljust(4)})"
+ print(header, byts)
+ tables += f"{header}\n"
+ tables += can_table(b) + "\n\n"
+ if table:
+ print(tables)
if __name__ == "__main__":
- if len(sys.argv) > 1:
- can_printer(int(sys.argv[1]))
- else:
- can_printer()
+ desc = """Collects messages and prints when a new bit transition is observed.
+ This is very useful to find signals based on user triggered actions, such as blinkers and seatbelt.
+ Leave the script running until no new transitions are seen, then perform the action."""
+ parser = argparse.ArgumentParser(description=desc,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument("--bus", type=int, help="CAN bus to print out", default=0)
+ parser.add_argument("--table", action="store_true", help="Print a cabana-like table")
+ parser.add_argument("init", type=str, nargs='?', help="Route or segment to initialize with. Use empty quotes to compare against all zeros.")
+ parser.add_argument("comp", type=str, nargs='?', help="Route or segment to compare against init")
+
+ args = parser.parse_args()
+
+ init_lr, new_lr = None, None
+ if args.init:
+ if args.init == '':
+ init_lr = []
+ else:
+ init_lr = logreader_from_route_or_segment(args.init)
+ if args.comp:
+ new_lr = logreader_from_route_or_segment(args.comp)
+
+ can_printer(args.bus, init_msgs=init_lr, new_msgs=new_lr, table=args.table)
diff --git a/selfdrive/debug/can_printer.py b/selfdrive/debug/can_printer.py
index 0aaaf1350b..3f991d4e6c 100755
--- a/selfdrive/debug/can_printer.py
+++ b/selfdrive/debug/can_printer.py
@@ -26,8 +26,9 @@ def can_printer(bus, max_msg, addr, ascii_decode):
for addr in sorted(msgs.keys()):
a = f"\"{msgs[addr][-1].decode('ascii', 'backslashreplace')}\"" if ascii_decode else ""
x = binascii.hexlify(msgs[addr][-1]).decode('ascii')
+ freq = len(msgs[addr]) / (sec_since_boot() - start)
if max_msg is None or addr < max_msg:
- dd += "%04X(%4d)(%6d) %s %s\n" % (addr, addr, len(msgs[addr]), x.ljust(20), a)
+ dd += "%04X(%4d)(%6d)(%3dHz) %s %s\n" % (addr, addr, len(msgs[addr]), freq, x.ljust(20), a)
print(dd)
lp = sec_since_boot()
diff --git a/selfdrive/debug/can_table.py b/selfdrive/debug/can_table.py
index 1569849053..11d070e708 100755
--- a/selfdrive/debug/can_table.py
+++ b/selfdrive/debug/can_table.py
@@ -1,10 +1,23 @@
#!/usr/bin/env python3
import argparse
-import pandas as pd # pylint: disable=import-error
+import pandas as pd
import cereal.messaging as messaging
+def can_table(dat):
+ rows = []
+ for b in dat:
+ r = list(bin(b).lstrip('0b').zfill(8))
+ r += [hex(b)]
+ rows.append(r)
+
+ df = pd.DataFrame(data=rows)
+ df.columns = [str(n) for n in range(7, -1, -1)] + [' ']
+ table = df.to_markdown(tablefmt='grid')
+ return table
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Cabana-like table of bits for your terminal",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@@ -28,12 +41,5 @@ if __name__ == "__main__":
if latest is None:
continue
- rows = []
- for b in latest.dat:
- r = list(bin(b).lstrip('0b').zfill(8))
- r += [hex(b)]
- rows.append(r)
-
- df = pd.DataFrame(data=rows)
- table = df.to_markdown(tablefmt='grid')
+ table = can_table(latest.dat)
print(f"\n\n{hex(addr)} ({addr}) on bus {args.bus}\n{table}")
diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py
index 424ad67b6d..6436abb4f1 100755
--- a/selfdrive/debug/check_freq.py
+++ b/selfdrive/debug/check_freq.py
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
-# type: ignore
-
import argparse
import numpy as np
from collections import defaultdict, deque
+from typing import DefaultDict, Deque, MutableSequence
+
from common.realtime import sec_since_boot
import cereal.messaging as messaging
@@ -19,8 +19,8 @@ if __name__ == "__main__":
socket_names = args.socket
sockets = {}
- rcv_times = defaultdict(lambda: deque(maxlen=100))
- valids = defaultdict(lambda: deque(maxlen=100))
+ rcv_times: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100))
+ valids: DefaultDict[str, Deque[bool]] = defaultdict(lambda: deque(maxlen=100))
t = sec_since_boot()
for name in socket_names:
@@ -31,6 +31,9 @@ if __name__ == "__main__":
while True:
for socket in poller.poll(100):
msg = messaging.recv_one(socket)
+ if msg is None:
+ continue
+
name = msg.which()
t = sec_since_boot()
diff --git a/selfdrive/debug/check_lag.py b/selfdrive/debug/check_lag.py
index c922642982..141156db91 100755
--- a/selfdrive/debug/check_lag.py
+++ b/selfdrive/debug/check_lag.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# type: ignore
+from typing import Dict
import cereal.messaging as messaging
from cereal.services import service_list
@@ -10,7 +10,7 @@ TO_CHECK = ['carState']
if __name__ == "__main__":
sm = messaging.SubMaster(TO_CHECK)
- prev_t = {}
+ prev_t: Dict[str, float] = {}
while True:
sm.update()
diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py
index 03e39fd70d..fb8467a3c4 100755
--- a/selfdrive/debug/check_timings.py
+++ b/selfdrive/debug/check_timings.py
@@ -1,14 +1,15 @@
#!/usr/bin/env python3
-# type: ignore
+
import sys
import time
import numpy as np
+from typing import DefaultDict, MutableSequence
from collections import defaultdict, deque
import cereal.messaging as messaging
socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]}
-ts = defaultdict(lambda: deque(maxlen=100))
+ts: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100))
if __name__ == "__main__":
while True:
@@ -18,7 +19,7 @@ if __name__ == "__main__":
for m in msgs:
ts[s].append(m.logMonoTime / 1e6)
- if len(ts[s]):
+ if len(ts[s]) > 2:
d = np.diff(ts[s])
print(f"{s:25} {np.mean(d):.2f} {np.std(d):.2f} {np.max(d):.2f} {np.min(d):.2f}")
time.sleep(1)
diff --git a/selfdrive/debug/clear_dtc.py b/selfdrive/debug/clear_dtc.py
index f4d38367b1..d84828079d 100755
--- a/selfdrive/debug/clear_dtc.py
+++ b/selfdrive/debug/clear_dtc.py
@@ -1,9 +1,16 @@
#!/usr/bin/env python3
import sys
+import argparse
from subprocess import check_output, CalledProcessError
from panda import Panda
from panda.python.uds import UdsClient, MessageTimeoutError, SESSION_TYPE, DTC_GROUP_TYPE
+parser = argparse.ArgumentParser(description="clear DTC status")
+parser.add_argument("addr", type=lambda x: int(x,0), nargs="?", default=0x7DF) # default is functional (broadcast) address
+parser.add_argument("--bus", type=int, default=0)
+parser.add_argument('--debug', action='store_true')
+args = parser.parse_args()
+
try:
check_output(["pidof", "boardd"])
print("boardd is running, please kill openpilot before running this script! (aborted)")
@@ -14,17 +21,20 @@ except CalledProcessError as e:
panda = Panda()
panda.set_safety_mode(Panda.SAFETY_ELM327)
-address = 0x7DF # functional (broadcast) address
-uds_client = UdsClient(panda, address, bus=0, debug=False)
+uds_client = UdsClient(panda, args.addr, bus=args.bus, debug=args.debug)
print("extended diagnostic session ...")
try:
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
except MessageTimeoutError:
- pass # functional address isn't properly handled so a timeout occurs
+ # functional address isn't properly handled so a timeout occurs
+ if args.addr != 0x7DF:
+ raise
print("clear diagnostic info ...")
try:
uds_client.clear_diagnostic_information(DTC_GROUP_TYPE.ALL)
except MessageTimeoutError:
- pass # functional address isn't properly handled so a timeout occurs
+ # functional address isn't properly handled so a timeout occurs
+ if args.addr != 0x7DF:
+ pass
print("")
print("you may need to power cycle your vehicle now")
diff --git a/selfdrive/debug/compare_fingerprints.py b/selfdrive/debug/compare_fingerprints.py
deleted file mode 100755
index 5b7ba58b1e..0000000000
--- a/selfdrive/debug/compare_fingerprints.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python3
-# flake8: noqa
-
-# put 2 fingeprints and print the diffs
-f1 = {
-168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8
-}
-
-f2 = {
-168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8
-}
-
-for k in f1:
- if k not in f2 or f1[k] != f2[k]:
- print(k, "not in f2")
-
-for k in f2:
- if k not in f1 or f2[k] != f1[k]:
- print(k, "not in f1")
diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py
index 8b32ce9d21..93dd5bdc47 100755
--- a/selfdrive/debug/count_events.py
+++ b/selfdrive/debug/count_events.py
@@ -1,9 +1,13 @@
#!/usr/bin/env python3
import sys
+import math
+import datetime
from collections import Counter
from pprint import pprint
from tqdm import tqdm
+from typing import cast
+from cereal.services import service_list
from tools.lib.route import Route
from tools.lib.logreader import LogReader
@@ -13,6 +17,11 @@ if __name__ == "__main__":
cnt_valid: Counter = Counter()
cnt_events: Counter = Counter()
+ cams = [s for s in service_list if s.endswith('CameraState')]
+ cnt_cameras = dict.fromkeys(cams, 0)
+
+ start_time = math.inf
+ end_time = -math.inf
for q in tqdm(r.qlog_paths()):
if q is None:
continue
@@ -21,12 +30,30 @@ if __name__ == "__main__":
if msg.which() == 'carEvents':
for e in msg.carEvents:
cnt_events[e.name] += 1
+ elif msg.which() in cams:
+ cnt_cameras[msg.which()] += 1
+
if not msg.valid:
cnt_valid[msg.which()] += 1
+ end_time = max(end_time, msg.logMonoTime)
+ start_time = min(start_time, msg.logMonoTime)
+
+ duration = (end_time - start_time) / 1e9
+
print("Events")
pprint(cnt_events)
- print("\n\n")
+ print("\n")
print("Not valid")
pprint(cnt_valid)
+
+ print("\n")
+ print("Cameras")
+ for k, v in cnt_cameras.items():
+ s = service_list[k]
+ expected_frames = int(s.frequency * duration / cast(float, s.decimation))
+ print(" ", k.ljust(20), f"{v}, {v/expected_frames:.1%} of expected")
+
+ print("\n")
+ print("Route duration", datetime.timedelta(seconds=duration))
diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py
index 76e809d2c4..b3294c8728 100755
--- a/selfdrive/debug/cpu_usage_stat.py
+++ b/selfdrive/debug/cpu_usage_stat.py
@@ -5,8 +5,8 @@ System tools like top/htop can only show current cpu usage values, so I write th
Features:
Use psutil library to sample cpu usage(avergage for all cores) of openpilot processes, at a rate of 5 samples/sec.
Do cpu usage statistics periodically, 5 seconds as a cycle.
- Caculate the average cpu usage within this cycle.
- Caculate minumium/maximium/accumulated_average cpu usage as long term inspections.
+ Calculate the average cpu usage within this cycle.
+ Calculate minumium/maximum/accumulated_average cpu usage as long term inspections.
Monitor multiple processes simuteneously.
Sample usage:
root@localhost:/data/openpilot$ python selfdrive/debug/cpu_usage_stat.py boardd,ubloxd
diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py
index f28d5373f4..b40c8e304c 100755
--- a/selfdrive/debug/cycle_alerts.py
+++ b/selfdrive/debug/cycle_alerts.py
@@ -1,19 +1,20 @@
#!/usr/bin/env python3
-# flake8: noqa
-# pylint: skip-file
-# type: ignore
-
import time
+import random
from cereal import car, log
import cereal.messaging as messaging
from common.realtime import DT_CTRL
from selfdrive.car.honda.interface import CarInterface
-from selfdrive.controls.lib.events import ET, EVENTS, Events
+from selfdrive.controls.lib.events import ET, Events
from selfdrive.controls.lib.alertmanager import AlertManager
+from selfdrive.manager.process_config import managed_processes
EventName = car.CarEvent.EventName
+def randperc() -> float:
+ return 100. * random.random()
+
def cycle_alerts(duration=200, is_metric=False):
# all alerts
#alerts = list(EVENTS.keys())
@@ -33,9 +34,27 @@ def cycle_alerts(duration=200, is_metric=False):
(EventName.driverDistracted, ET.WARNING),
]
+ # debug alerts
+ alerts = [
+ #(EventName.highCpuUsage, ET.NO_ENTRY),
+ #(EventName.lowMemory, ET.PERMANENT),
+ #(EventName.overheat, ET.PERMANENT),
+ #(EventName.outOfSpace, ET.PERMANENT),
+ #(EventName.modeldLagging, ET.PERMANENT),
+ #(EventName.processNotRunning, ET.NO_ENTRY),
+ #(EventName.commIssue, ET.NO_ENTRY),
+ #(EventName.calibrationInvalid, ET.PERMANENT),
+ (EventName.cameraMalfunction, ET.PERMANENT),
+ (EventName.cameraFrameRate, ET.PERMANENT),
+ ]
+
+ cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState']
+
+ CS = car.CarState.new_message()
CP = CarInterface.get_params("HONDA CIVIC 2016")
sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration',
- 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman'])
+ 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
+ 'managerState'] + cameras)
pm = messaging.PubMaster(['controlsState', 'pandaStates', 'deviceState'])
@@ -44,30 +63,53 @@ def cycle_alerts(duration=200, is_metric=False):
frame = 0
while True:
- current_alert_types = [ET.PERMANENT, ET.USER_DISABLE, ET.IMMEDIATE_DISABLE,
- ET.SOFT_DISABLE, ET.PRE_ENABLE, ET.NO_ENTRY,
- ET.ENABLE, ET.WARNING]
-
for alert, et in alerts:
events.clear()
events.add(alert)
- a = events.create_alerts([et, ], [CP, sm, is_metric, 0])
+ sm['deviceState'].freeSpacePercent = randperc()
+ sm['deviceState'].memoryUsagePercent = int(randperc())
+ sm['deviceState'].cpuTempC = [randperc() for _ in range(3)]
+ sm['deviceState'].gpuTempC = [randperc() for _ in range(3)]
+ sm['deviceState'].cpuUsagePercent = [int(randperc()) for _ in range(8)]
+ sm['modelV2'].frameDropPerc = randperc()
+
+ if random.random() > 0.25:
+ sm['modelV2'].velocity.x = [random.random(), ]
+ if random.random() > 0.25:
+ CS.vEgo = random.random()
+
+ procs = [p.get_process_state_msg() for p in managed_processes.values()]
+ random.shuffle(procs)
+ for i in range(random.randint(0, 10)):
+ procs[i].shouldBeRunning = True
+ sm['managerState'].processes = procs
+
+ sm['liveCalibration'].rpyCalib = [-1 * random.random() for _ in range(random.randint(0, 3))]
+
+ for s in sm.data.keys():
+ prob = 0.3 if s in cameras else 0.08
+ sm.alive[s] = random.random() > prob
+ sm.valid[s] = random.random() > prob
+ sm.freq_ok[s] = random.random() > prob
+
+ a = events.create_alerts([et, ], [CP, CS, sm, is_metric, 0])
AM.add_many(frame, a)
- AM.process_alerts(frame)
- print(AM.alert)
+ alert = AM.process_alerts(frame, [])
+ print(alert)
for _ in range(duration):
dat = messaging.new_message()
dat.init('controlsState')
- dat.controlsState.enabled = True
-
- dat.controlsState.alertText1 = AM.alert_text_1
- dat.controlsState.alertText2 = AM.alert_text_2
- dat.controlsState.alertSize = AM.alert_size
- dat.controlsState.alertStatus = AM.alert_status
- dat.controlsState.alertBlinkingRate = AM.alert_rate
- dat.controlsState.alertType = AM.alert_type
- dat.controlsState.alertSound = AM.audible_alert
+ dat.controlsState.enabled = False
+
+ if alert:
+ dat.controlsState.alertText1 = alert.alert_text_1
+ dat.controlsState.alertText2 = alert.alert_text_2
+ dat.controlsState.alertSize = alert.alert_size
+ dat.controlsState.alertStatus = alert.alert_status
+ dat.controlsState.alertBlinkingRate = alert.alert_rate
+ dat.controlsState.alertType = alert.alert_type
+ dat.controlsState.alertSound = alert.audible_alert
pm.send('controlsState', dat)
dat = messaging.new_message()
diff --git a/selfdrive/debug/disable_ecu.py b/selfdrive/debug/disable_ecu.py
deleted file mode 100644
index f0faf40017..0000000000
--- a/selfdrive/debug/disable_ecu.py
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python3
-import traceback
-
-import cereal.messaging as messaging
-from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
-from selfdrive.swaglog import cloudlog
-
-EXT_DIAG_REQUEST = b'\x10\x03'
-EXT_DIAG_RESPONSE = b'\x50\x03'
-COM_CONT_REQUEST = b'\x28\x83\x03'
-COM_CONT_RESPONSE = b''
-
-def disable_ecu(ecu_addr, logcan, sendcan, bus, timeout=0.5, retry=5, debug=False):
- print(f"ecu disable {hex(ecu_addr)} ...")
- for i in range(retry):
- try:
- # enter extended diagnostic session
- query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug)
- for addr, dat in query.get_data(timeout).items(): # pylint: disable=unused-variable
- print("ecu communication control disable tx/rx ...")
- # communication control disable tx and rx
- query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [COM_CONT_REQUEST], [COM_CONT_RESPONSE], debug=debug)
- query.get_data(0)
- return True
- print(f"ecu disable retry ({i+1}) ...")
- except Exception:
- cloudlog.warning(f"ecu disable exception: {traceback.format_exc()}")
-
- return False
-
-
-if __name__ == "__main__":
- import time
- sendcan = messaging.pub_sock('sendcan')
- logcan = messaging.sub_sock('can')
- time.sleep(1)
-
- # honda bosch radar disable
- disabled = disable_ecu(0x18DAB0F1, logcan, sendcan, 1, debug=False)
- print(f"disabled: {disabled}")
diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py
index f208920f12..fdb825eead 100755
--- a/selfdrive/debug/dump.py
+++ b/selfdrive/debug/dump.py
@@ -13,7 +13,7 @@ from cereal.services import service_list
if __name__ == "__main__":
- parser = argparse.ArgumentParser(description='Dump communcation sockets. See cereal/services.py for a complete list of available sockets.')
+ parser = argparse.ArgumentParser(description='Dump communication sockets. See cereal/services.py for a complete list of available sockets.')
parser.add_argument('--pipe', action='store_true')
parser.add_argument('--raw', action='store_true')
parser.add_argument('--json', action='store_true')
diff --git a/selfdrive/debug/dump_car_info.py b/selfdrive/debug/dump_car_info.py
new file mode 100755
index 0000000000..c9a21c2848
--- /dev/null
+++ b/selfdrive/debug/dump_car_info.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+import argparse
+import pickle
+
+from selfdrive.car.docs import get_all_car_info
+
+
+def dump_car_info(path):
+ with open(path, 'wb') as f:
+ pickle.dump(get_all_car_info(), f)
+ print(f'Dumping car info to {path}')
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--path", required=True)
+ args = parser.parse_args()
+ dump_car_info(args.path)
diff --git a/selfdrive/debug/filter_log_message.py b/selfdrive/debug/filter_log_message.py
index 98e7d87246..af52953936 100755
--- a/selfdrive/debug/filter_log_message.py
+++ b/selfdrive/debug/filter_log_message.py
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
+import os
import argparse
import json
import cereal.messaging as messaging
-from tools.lib.robust_logreader import RobustLogReader as LogReader
+from tools.lib.logreader import LogReader
from tools.lib.route import Route
LEVELS = {
@@ -46,7 +47,6 @@ def print_androidlog(t, msg):
if __name__ == "__main__":
-
parser = argparse.ArgumentParser()
parser.add_argument('--level', default='DEBUG')
parser.add_argument('--addr', default='127.0.0.1')
@@ -55,8 +55,11 @@ if __name__ == "__main__":
logs = None
if len(args.route):
- r = Route(args.route[0])
- logs = r.log_paths()
+ if os.path.exists(args.route[0]):
+ logs = [args.route[0]]
+ else:
+ r = Route(args.route[0])
+ logs = [q_log if r_log is None else r_log for (q_log, r_log) in zip(r.qlog_paths(), r.log_paths())]
if len(args.route) == 2 and logs:
n = int(args.route[1])
@@ -71,6 +74,8 @@ if __name__ == "__main__":
for m in lr:
if m.which() == 'logMessage':
print_logmessage(m.logMonoTime, m.logMessage, min_level)
+ elif m.which() == 'errorLogMessage' and 'qlog' in log:
+ print_logmessage(m.logMonoTime, m.errorLogMessage, min_level)
elif m.which() == 'androidLog':
print_androidlog(m.logMonoTime, m.androidLog)
else:
diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py
index 8132a46552..ac7e7102d0 100755
--- a/selfdrive/debug/hyundai_enable_radar_points.py
+++ b/selfdrive/debug/hyundai_enable_radar_points.py
@@ -6,7 +6,7 @@ firmware versions. If you want to try on a new radar make sure to note the defau
in case it's different from the other radars and you need to revert the changes.
After changing the config the car should not show any faults when openpilot is not running.
-These config changes are persistent accross car reboots. You need to run this script again
+These config changes are persistent across car reboots. You need to run this script again
to go back to the default values.
USE AT YOUR OWN RISK! Safety features, like AEB and FCW, might be affected by these changes."""
@@ -74,7 +74,7 @@ if __name__ == "__main__":
print("\nyou didn't type 'OK! (aborted)")
sys.exit(0)
- panda = Panda() # type: ignore
+ panda = Panda()
panda.set_safety_mode(Panda.SAFETY_ELM327)
uds_client = UdsClient(panda, 0x7D0, bus=args.bus, debug=args.debug)
diff --git a/selfdrive/debug/internal/core_voltage_sweep.py b/selfdrive/debug/internal/core_voltage_sweep.py
deleted file mode 100755
index c7c609560d..0000000000
--- a/selfdrive/debug/internal/core_voltage_sweep.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-import time
-
-print("starting at")
-os.system("cat /sys/kernel/debug/regulator/pm8994_s11/voltage")
-print("volts")
-
-os.system("echo 99e8000.cpr3-ctrl > /sys/devices/soc/spm-regulator-10/regulator/regulator.56/99e8000.cpr3-ctrl-vdd/driver/unbind")
-os.system("echo 1 > /sys/kernel/debug/regulator/pm8994_s11/enable")
-
-if len(sys.argv) > 1:
- i = int(sys.argv[1])
- os.system("echo %d %d > /sys/kernel/debug/regulator/pm8994_s11/voltage" % (i,i))
- os.system("cat /sys/kernel/debug/regulator/pm8994_s11/voltage")
-else:
- for i in range(900000, 465000, -10000):
- print("setting voltage to",i)
- os.system("echo %d %d > /sys/kernel/debug/regulator/pm8994_s11/voltage" % (i,i))
- os.system("cat /sys/kernel/debug/regulator/pm8994_s11/voltage")
- time.sleep(1)
-
diff --git a/selfdrive/debug/internal/measure_modeld_packet_drop.py b/selfdrive/debug/internal/measure_modeld_packet_drop.py
index d5f65d06b1..6b7f7dbd13 100755
--- a/selfdrive/debug/internal/measure_modeld_packet_drop.py
+++ b/selfdrive/debug/internal/measure_modeld_packet_drop.py
@@ -1,11 +1,12 @@
#!/usr/bin/env python3
import cereal.messaging as messaging
+from typing import Optional
if __name__ == "__main__":
modeld_sock = messaging.sub_sock("modelV2")
last_frame_id = None
- start_t = None
+ start_t: Optional[int] = None
frame_cnt = 0
dropped = 0
diff --git a/selfdrive/debug/internal/measure_steering_accuracy.py b/selfdrive/debug/internal/measure_steering_accuracy.py
deleted file mode 100755
index e06e43cdcf..0000000000
--- a/selfdrive/debug/internal/measure_steering_accuracy.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-# type: ignore
-
-import os
-import argparse
-import signal
-from collections import defaultdict
-
-import cereal.messaging as messaging
-
-def sigint_handler(signal, frame):
- print("handler!")
- exit(0)
-signal.signal(signal.SIGINT, sigint_handler)
-
-if __name__ == "__main__":
-
- parser = argparse.ArgumentParser(description='Sniff a communication socket')
- parser.add_argument('--addr', default='127.0.0.1')
- args = parser.parse_args()
-
- if args.addr != "127.0.0.1":
- os.environ["ZMQ"] = "1"
- messaging.context = messaging.Context()
-
- carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True)
- sm = messaging.SubMaster(['carState', 'carControl', 'controlsState'], addr=args.addr)
-
- msg_cnt = 0
- stats = defaultdict(lambda: {'err': 0, "cnt": 0, "=": 0, "+": 0, "-": 0})
- cnt = 0
- total_error = 0
-
- while messaging.recv_one(carControl):
- sm.update()
- msg_cnt += 1
-
- actual_speed = sm['carState'].vEgo
- enabled = sm['controlsState'].enabled
- steer_override = sm['controlsState'].steerOverride
-
- # must be above 10 m/s, engaged and not overriding steering
- if actual_speed > 10.0 and enabled and not steer_override:
- cnt += 1
-
- # wait 5 seconds after engage/override
- if cnt >= 500:
- # calculate error before rounding
- actual_angle = sm['controlsState'].angleSteers
- desired_angle = sm['carControl'].actuators.steeringAngleDeg
- angle_error = abs(desired_angle - actual_angle)
-
- # round numbers
- actual_angle = round(actual_angle, 1)
- desired_angle = round(desired_angle, 1)
- angle_error = round(angle_error, 2)
- angle_abs = int(abs(round(desired_angle, 0)))
-
- # collect stats
- stats[angle_abs]["err"] += angle_error
- stats[angle_abs]["cnt"] += 1
- if actual_angle == desired_angle:
- stats[angle_abs]["="] += 1
- else:
- if desired_angle == 0.:
- overshoot = True
- else:
- overshoot = desired_angle < actual_angle if desired_angle > 0. else desired_angle > actual_angle
- stats[angle_abs]["+" if overshoot else "-"] += 1
- else:
- cnt = 0
-
- if msg_cnt % 100 == 0:
- print(chr(27) + "[2J")
- if cnt != 0:
- print("COLLECTING ...")
- else:
- print("DISABLED (speed too low, not engaged, or steer override)")
- for k in sorted(stats.keys()):
- v = stats[k]
- print(f'angle: {k:#2} | error: {round(v["err"] / v["cnt"], 2):2.2f} | =:{int(v["="] / v["cnt"] * 100):#3}% | +:{int(v["+"] / v["cnt"] * 100):#4}% | -:{int(v["-"] / v["cnt"] * 100):#3}% | count: {v["cnt"]:#4}')
diff --git a/selfdrive/debug/internal/power_monitor.py b/selfdrive/debug/internal/power_monitor.py
index c995ef6ff2..34d2a12adc 100755
--- a/selfdrive/debug/internal/power_monitor.py
+++ b/selfdrive/debug/internal/power_monitor.py
@@ -10,6 +10,7 @@ def average(avg, sample):
if __name__ == '__main__':
+ start_time = datetime.now()
try:
if len(sys.argv) > 1 and sys.argv[1] == "--charge":
print("not disabling charging")
@@ -22,7 +23,6 @@ if __name__ == '__main__':
power_average = (0., 0)
capacity_average = (0., 0)
bat_temp_average = (0., 0)
- start_time = datetime.now()
while 1:
with open("/sys/class/power_supply/bms/voltage_now") as f:
voltage = int(f.read()) / 1e6 # volts
@@ -59,6 +59,6 @@ if __name__ == '__main__':
print(f" {(stop_time - start_time).total_seconds():.2f} Seconds {voltage_average[1]} samples")
print("----------------------------------------------------------------")
- # reenable charging
+ # re-enable charging
os.system('echo "1" > /sys/class/power_supply/battery/charging_enabled')
print("charging enabled\n")
diff --git a/selfdrive/debug/internal/sensor_test_bootloop.py b/selfdrive/debug/internal/sensor_test_bootloop.py
deleted file mode 100755
index 36eb112e44..0000000000
--- a/selfdrive/debug/internal/sensor_test_bootloop.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/python3
-import sys
-import os
-import stat
-import subprocess
-import json
-from common.text_window import TextWindow
-import time
-
-# Required for sensord not to bus-error on startup
-# commaai/cereal#22
-try:
- os.mkdir("/dev/shm")
-except FileExistsError:
- pass
-except PermissionError:
- print("WARNING: failed to make /dev/shm")
-
-try:
- with open('/tmp/sensor-test-results.json') as infile:
- data = json.load(infile)
-except Exception:
- data = {'sensor-pass': 0, 'sensor-fail': 0}
-
-STARTUP_SCRIPT = "/data/data/com.termux/files/continue.sh"
-try:
- with open(STARTUP_SCRIPT, 'w') as startup_script:
- startup_script.write("#!/usr/bin/bash\n\n/data/openpilot/selfdrive/debug/internal/sensor_test_bootloop.py\n")
- os.chmod(STARTUP_SCRIPT, stat.S_IRWXU)
-except Exception:
- print("Failed to install new startup script -- aborting")
- sys.exit(-1)
-
-sensord_env = {**os.environ, 'SENSOR_TEST': '1'}
-process = subprocess.run("./sensord", cwd="/data/openpilot/selfdrive/sensord", env=sensord_env) # pylint: disable=subprocess-run-check
-
-if process.returncode == 40:
- text = "Current run: SUCCESS\n"
- data['sensor-pass'] += 1
-else:
- text = "Current run: FAIL\n"
- data['sensor-fail'] += 1
-
- timestr = str(int(time.time()))
- with open('/tmp/dmesg-' + timestr + '.log', 'w') as dmesg_out:
- subprocess.call('dmesg', stdout=dmesg_out, shell=False)
- with open("/tmp/logcat-" + timestr + '.log', 'w') as logcat_out:
- subprocess.call(['logcat', '-d'], stdout=logcat_out, shell=False)
-
-text += "Sensor pass history: " + str(data['sensor-pass']) + "\n"
-text += "Sensor fail history: " + str(data['sensor-fail']) + "\n"
-
-print(text)
-
-with open('/tmp/sensor-test-results.json', 'w') as outfile:
- json.dump(data, outfile, indent=4)
-
-with TextWindow(text) as status:
- for _ in range(100):
- if status.get_status() == 1:
- with open(STARTUP_SCRIPT, 'w') as startup_script:
- startup_script.write("#!/usr/bin/bash\n\ncd /data/openpilot\nexec ./launch_openpilot.sh\n")
- os.chmod(STARTUP_SCRIPT, stat.S_IRWXU)
- break
- time.sleep(0.1)
-
-subprocess.Popen("reboot")
diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py
index e46c0b0a1f..c35afc474b 100755
--- a/selfdrive/debug/live_cpu_and_temp.py
+++ b/selfdrive/debug/live_cpu_and_temp.py
@@ -1,9 +1,11 @@
#!/usr/bin/env python3
import argparse
+import capnp
+from collections import defaultdict
from cereal.messaging import SubMaster
from common.numpy_fast import mean
-
+from typing import Optional, Dict
def cputime_total(ct):
return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq
@@ -40,8 +42,8 @@ if __name__ == "__main__":
total_times = [0.]*8
busy_times = [0.]*8
- prev_proclog = None
- prev_proclog_t = None
+ prev_proclog: Optional[capnp._DynamicStructReader] = None
+ prev_proclog_t: Optional[int] = None
while True:
sm.update()
@@ -73,8 +75,8 @@ if __name__ == "__main__":
print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C")
- if args.cpu and prev_proclog is not None:
- procs = {}
+ if args.cpu and prev_proclog is not None and prev_proclog_t is not None:
+ procs: Dict[str, float] = defaultdict(float)
dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9
for proc in m.procs:
try:
@@ -82,7 +84,7 @@ if __name__ == "__main__":
prev_proc = [p for p in prev_proclog.procs if proc.pid == p.pid][0]
cpu_time = proc_cputime_total(proc) - proc_cputime_total(prev_proc)
cpu_usage = cpu_time / dt * 100.
- procs[name] = cpu_usage
+ procs[name] += cpu_usage
except IndexError:
pass
diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py
new file mode 100755
index 0000000000..b804286458
--- /dev/null
+++ b/selfdrive/debug/print_docs_diff.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+import argparse
+from collections import defaultdict
+import difflib
+import pickle
+
+from selfdrive.car.docs import get_all_car_info
+from selfdrive.car.docs_definitions import Column
+
+FOOTNOTE_TAG = "{}"
+STAR_ICON = '
'
+COLUMNS = "|" + "|".join([column.value for column in Column]) + "|"
+COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3)))
+ARROW_SYMBOL = "➡️"
+
+
+def load_base_car_info(path):
+ with open(path, "rb") as f:
+ return pickle.load(f)
+
+
+def match_cars(base_cars, new_cars):
+ changes = []
+ additions = []
+ for new in new_cars:
+ # Addition if no close matches or close match already used
+ # Change if close match and not already used
+ matches = difflib.get_close_matches(new.name, [b.name for b in base_cars], cutoff=0.)
+ if not len(matches) or matches[0] in [c[1].name for c in changes]:
+ additions.append(new)
+ else:
+ changes.append((new, next(car for car in base_cars if car.name == matches[0])))
+
+ # Removal if base car not in changes
+ removals = [b for b in base_cars if b.name not in [c[1].name for c in changes]]
+ return changes, additions, removals
+
+
+def build_column_diff(base_car, new_car):
+ row_builder = []
+ for column in Column:
+ base_column = base_car.get_column(column, STAR_ICON, FOOTNOTE_TAG)
+ new_column = new_car.get_column(column, STAR_ICON, FOOTNOTE_TAG)
+
+ if base_column != new_column:
+ row_builder.append(f"{base_column} {ARROW_SYMBOL} {new_column}")
+ else:
+ row_builder.append(new_column)
+
+ return format_row(row_builder)
+
+
+def format_row(builder):
+ return "|" + "|".join(builder) + "|"
+
+
+def print_car_info_diff(path):
+ base_car_info = defaultdict(list)
+ new_car_info = defaultdict(list)
+
+ for car in load_base_car_info(path):
+ base_car_info[car.car_fingerprint].append(car)
+ for car in get_all_car_info():
+ new_car_info[car.car_fingerprint].append(car)
+
+ # Add new platforms to base cars so we can detect additions and removals in one pass
+ base_car_info.update({car: [] for car in new_car_info if car not in base_car_info})
+
+ changes = defaultdict(list)
+ for base_car_model, base_cars in base_car_info.items():
+ # Match car info changes, and get additions and removals
+ new_cars = new_car_info[base_car_model]
+ car_changes, car_additions, car_removals = match_cars(base_cars, new_cars)
+
+ # Removals
+ for car_info in car_removals:
+ changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column]))
+
+ # Additions
+ for car_info in car_additions:
+ changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column]))
+
+ for new_car, base_car in car_changes:
+ # Column changes
+ row_diff = build_column_diff(base_car, new_car)
+ if ARROW_SYMBOL in row_diff:
+ changes["column"].append(row_diff)
+
+ # Detail sentence changes
+ if base_car.detail_sentence != new_car.detail_sentence:
+ changes["detail"].append(f"- Sentence for {base_car.name} changed!\n" +
+ " ```diff\n" +
+ f" - {base_car.detail_sentence}\n" +
+ f" + {new_car.detail_sentence}\n" +
+ " ```")
+
+ # Print diff
+ if any(len(c) for c in changes.values()):
+ markdown_builder = ["### ⚠️ This PR makes changes to [CARS.md](../blob/master/docs/CARS.md) ⚠️"]
+
+ for title, category in (("## 🔀 Column Changes", "column"), ("## ❌ Removed", "removals"), ("## ➕ Added", "additions"), ("## 📖 Detail Sentence Changes", "detail")):
+ if len(changes[category]):
+ markdown_builder.append(title)
+ if category not in ("detail",):
+ markdown_builder.append(COLUMNS)
+ markdown_builder.append(COLUMN_HEADER)
+ markdown_builder.extend(changes[category])
+
+ print("\n".join(markdown_builder))
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--path", required=True)
+ args = parser.parse_args()
+ print_car_info_diff(args.path)
diff --git a/selfdrive/debug/profiling/ftrace.sh b/selfdrive/debug/profiling/ftrace.sh
new file mode 100755
index 0000000000..fe91a3c0d9
--- /dev/null
+++ b/selfdrive/debug/profiling/ftrace.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/bash
+set -e
+
+cd /sys/kernel/tracing
+
+echo 1 > tracing_on
+echo boot > trace_clock
+echo 1000 > buffer_size_kb
+
+# /sys/kernel/tracing/available_events
+echo 1 > events/irq/enable
+echo 1 > events/sched/enable
+echo 1 > events/kgsl/enable
+echo 1 > events/camera/enable
+echo 1 > events/workqueue/enable
+
+echo > trace
+sleep 5
+echo 0 > tracing_on
+
+cp trace /tmp/trace
+chown comma: /tmp/trace
+echo /tmp/trace
diff --git a/selfdrive/debug/profiling/snapdragon/setup-agnos.sh b/selfdrive/debug/profiling/snapdragon/setup-agnos.sh
index 5099fb09cb..f036ca2111 100755
--- a/selfdrive/debug/profiling/snapdragon/setup-agnos.sh
+++ b/selfdrive/debug/profiling/snapdragon/setup-agnos.sh
@@ -4,4 +4,4 @@
cd SnapdragonProfiler/service
mv android real_android
-ln -s iot_rb5_lu/ android
+ln -s agl/ android
diff --git a/selfdrive/debug/read_dtc_status.py b/selfdrive/debug/read_dtc_status.py
new file mode 100755
index 0000000000..9ad5563975
--- /dev/null
+++ b/selfdrive/debug/read_dtc_status.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+import sys
+import argparse
+from subprocess import check_output, CalledProcessError
+from panda import Panda
+from panda.python.uds import UdsClient, SESSION_TYPE, DTC_REPORT_TYPE, DTC_STATUS_MASK_TYPE
+from panda.python.uds import get_dtc_num_as_str, get_dtc_status_names
+
+parser = argparse.ArgumentParser(description="read DTC status")
+parser.add_argument("addr", type=lambda x: int(x,0))
+parser.add_argument("--bus", type=int, default=0)
+parser.add_argument('--debug', action='store_true')
+args = parser.parse_args()
+
+try:
+ check_output(["pidof", "boardd"])
+ print("boardd is running, please kill openpilot before running this script! (aborted)")
+ sys.exit(1)
+except CalledProcessError as e:
+ if e.returncode != 1: # 1 == no process found (boardd not running)
+ raise e
+
+panda = Panda()
+panda.set_safety_mode(Panda.SAFETY_ELM327)
+uds_client = UdsClient(panda, args.addr, bus=args.bus, debug=args.debug)
+print("extended diagnostic session ...")
+uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
+print("read diagnostic codes ...")
+data = uds_client.read_dtc_information(DTC_REPORT_TYPE.DTC_BY_STATUS_MASK, DTC_STATUS_MASK_TYPE.ALL)
+print("status availability:", " ".join(get_dtc_status_names(data[0])))
+print("DTC status:")
+for i in range(1, len(data), 4):
+ dtc_num = get_dtc_num_as_str(data[i:i+3])
+ dtc_status = " ".join(get_dtc_status_names(data[i+3]))
+ print(dtc_num, dtc_status)
diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py
index a6e67d2b24..63b0733bba 100755
--- a/selfdrive/debug/run_process_on_route.py
+++ b/selfdrive/debug/run_process_on_route.py
@@ -25,7 +25,7 @@ if __name__ == "__main__":
# Remove message generated by the process under test and merge in the new messages
produces = {o.which() for o in outputs}
inputs = [i for i in inputs if i.which() not in produces]
- outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
+ outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) # type: ignore
fn = f"{args.route}_{args.process}.bz2"
save_log(fn, outputs)
diff --git a/selfdrive/debug/sensor_data_to_hist.py b/selfdrive/debug/sensor_data_to_hist.py
new file mode 100755
index 0000000000..ceed4b0ec3
--- /dev/null
+++ b/selfdrive/debug/sensor_data_to_hist.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+'''
+printing the gap between interrupts in a histogram to check if the
+frequency is what we expect, the bmx is not interrupt driven for as we
+get interrupts in a 2kHz rate.
+'''
+
+import argparse
+import sys
+import numpy as np
+from collections import defaultdict
+
+from tools.lib.logreader import LogReader
+from tools.lib.route import Route
+
+import matplotlib.pyplot as plt
+
+SRC_BMX = "bmx055"
+SRC_LSM = "lsm6ds3"
+
+
+def parseEvents(log_reader):
+ bmx_data = defaultdict(list)
+ lsm_data = defaultdict(list)
+
+ for m in log_reader:
+ if m.which() not in ['accelerometer', 'gyroscope']:
+ continue
+
+ d = getattr(m, m.which()).to_dict()
+
+ if d["source"] == SRC_BMX and "acceleration" in d:
+ bmx_data["accel"].append(d["timestamp"] / 1e9)
+
+ if d["source"] == SRC_BMX and "gyroUncalibrated" in d:
+ bmx_data["gyro"].append(d["timestamp"] / 1e9)
+
+ if d["source"] == SRC_LSM and "acceleration" in d:
+ lsm_data["accel"].append(d["timestamp"] / 1e9)
+
+ if d["source"] == SRC_LSM and "gyroUncalibrated" in d:
+ lsm_data["gyro"].append(d["timestamp"] / 1e9)
+
+ return bmx_data, lsm_data
+
+
+def cleanData(data):
+ if len(data) == 0:
+ return [], []
+
+ data.sort()
+ diffs = np.diff(data)
+ return data, diffs
+
+
+def logAvgValues(data, sensor):
+ if len(data) == 0:
+ print(f"{sensor}: no data to average")
+ return
+
+ avg = sum(data) / len(data)
+ hz = 1 / avg
+ print(f"{sensor}: data_points: {len(data)} avg [ns]: {avg} avg [Hz]: {hz}")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("route", type=str, help="route name")
+ parser.add_argument("segment", type=int, help="segment number")
+ args = parser.parse_args()
+
+ r = Route(args.route)
+ logs = r.log_paths()
+
+ if len(logs) == 0:
+ print("NO data routes")
+ sys.exit(0)
+
+ if args.segment >= len(logs):
+ print(f"RouteID: {args.segment} out of range, max: {len(logs) -1}")
+ sys.exit(0)
+
+ lr = LogReader(logs[args.segment])
+ bmx_data, lsm_data = parseEvents(lr)
+
+ # sort bmx accel data, and then cal all the diffs, and to a histogram of those
+ bmx_accel, bmx_accel_diffs = cleanData(bmx_data["accel"])
+ bmx_gyro, bmx_gyro_diffs = cleanData(bmx_data["gyro"])
+ lsm_accel, lsm_accel_diffs = cleanData(lsm_data["accel"])
+ lsm_gyro, lsm_gyro_diffs = cleanData(lsm_data["gyro"])
+
+ # get out the averages
+ logAvgValues(bmx_accel_diffs, "bmx accel")
+ logAvgValues(bmx_gyro_diffs, "bmx gyro ")
+ logAvgValues(lsm_accel_diffs, "lsm accel")
+ logAvgValues(lsm_gyro_diffs, "lsm gyro ")
+
+ fig, axs = plt.subplots(1, 2, tight_layout=True)
+ axs[0].hist(bmx_accel_diffs, bins=50)
+ axs[0].set_title("bmx_accel")
+ axs[1].hist(bmx_gyro_diffs, bins=50)
+ axs[1].set_title("bmx_gyro")
+
+ figl, axsl = plt.subplots(1, 2, tight_layout=True)
+ axsl[0].hist(lsm_accel_diffs, bins=50)
+ axsl[0].set_title("lsm_accel")
+ axsl[1].hist(lsm_gyro_diffs, bins=50)
+ axsl[1].set_title("lsm_gyro")
+
+ print("check plot...")
+ plt.show()
diff --git a/selfdrive/debug/set_car_params.py b/selfdrive/debug/set_car_params.py
index bcdaed0778..24258db9f2 100755
--- a/selfdrive/debug/set_car_params.py
+++ b/selfdrive/debug/set_car_params.py
@@ -1,11 +1,22 @@
#!/usr/bin/env python3
import sys
+from cereal import car
from common.params import Params
from tools.lib.route import Route
from tools.lib.logreader import LogReader
if __name__ == "__main__":
- r = Route(sys.argv[1])
- cp = [m for m in LogReader(r.qlog_paths()[0]) if m.which() == 'carParams']
- Params().put("CarParams", cp[0].carParams.as_builder().to_bytes())
+ CP = None
+ if len(sys.argv) > 1:
+ r = Route(sys.argv[1])
+ cps = [m for m in LogReader(r.qlog_paths()[0]) if m.which() == 'carParams']
+ CP = cps[0].carParams.as_builder()
+ else:
+ CP = car.CarParams.new_message()
+ CP.openpilotLongitudinalControl = True
+ CP.experimentalLongitudinalAvailable = False
+
+ cp_bytes = CP.to_bytes()
+ for p in ("CarParams", "CarParamsCache", "CarParamsPersistent"):
+ Params().put(p, cp_bytes)
diff --git a/selfdrive/debug/show_matching_cars.py b/selfdrive/debug/show_matching_cars.py
index 79a23c1f82..d5199b2a9e 100755
--- a/selfdrive/debug/show_matching_cars.py
+++ b/selfdrive/debug/show_matching_cars.py
@@ -10,11 +10,11 @@ candidate_cars = all_legacy_fingerprint_cars()
for addr, l in fingerprint.items():
- dat = messaging.new_message('can', 1)
+ dat = messaging.new_message('can', 1)
- msg = dat.can[0]
- msg.address = addr
- msg.dat = " " * l
+ msg = dat.can[0]
+ msg.address = addr
+ msg.dat = " " * l
- candidate_cars = eliminate_incompatible_cars(msg, candidate_cars)
- print(candidate_cars)
+ candidate_cars = eliminate_incompatible_cars(msg, candidate_cars)
+ print(candidate_cars)
diff --git a/selfdrive/debug/test_car_model.py b/selfdrive/debug/test_car_model.py
new file mode 100755
index 0000000000..4de5b26762
--- /dev/null
+++ b/selfdrive/debug/test_car_model.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+import argparse
+import sys
+from typing import List, Tuple
+import unittest
+
+from selfdrive.car.tests.routes import CarTestRoute
+from selfdrive.car.tests.test_models import TestCarModel
+
+
+def create_test_models_suite(routes: List[Tuple[str, CarTestRoute]], ci=False) -> unittest.TestSuite:
+ test_suite = unittest.TestSuite()
+ for car_model, test_route in routes:
+ # create new test case and discover tests
+ test_case_args = {"car_model": car_model, "test_route": test_route, "ci": ci}
+ CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args)
+ test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase))
+ return test_suite
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Test any route against common issues with a new car port. " +
+ "Uses selfdrive/car/tests/test_models.py")
+ parser.add_argument("route", help="Specify route to run tests on")
+ parser.add_argument("--car", help="Specify car model for test route")
+ parser.add_argument("--segment", type=int, nargs="?", help="Specify segment of route to test")
+ parser.add_argument("--ci", action="store_true", help="Attempt to get logs using openpilotci, need to specify car")
+ args = parser.parse_args()
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit()
+
+ test_route = CarTestRoute(args.route, args.car, segment=args.segment)
+ test_suite = create_test_models_suite([(args.car, test_route)], ci=args.ci)
+
+ unittest.TextTestRunner().run(test_suite)
diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py
index 6b29ae820d..ba7d96dba0 100755
--- a/selfdrive/debug/test_fw_query_on_routes.py
+++ b/selfdrive/debug/test_fw_query_on_routes.py
@@ -8,23 +8,16 @@ import traceback
from tqdm import tqdm
from tools.lib.logreader import LogReader
from tools.lib.route import Route
+from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.car_helpers import interface_names
-from selfdrive.car.fw_versions import match_fw_to_car_exact, match_fw_to_car_fuzzy, build_fw_dict
-from selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS
-from selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS
-from selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS
-from selfdrive.car.volkswagen.values import FW_VERSIONS as VW_FW_VERSIONS
-from selfdrive.car.mazda.values import FW_VERSIONS as MAZDA_FW_VERSIONS
-from selfdrive.car.subaru.values import FW_VERSIONS as SUBARU_FW_VERSIONS
+from selfdrive.car.fw_versions import match_fw_to_car
NO_API = "NO_API" in os.environ
-SUPPORTED_CARS = set(interface_names['toyota'])
-SUPPORTED_CARS |= set(interface_names['honda'])
-SUPPORTED_CARS |= set(interface_names['hyundai'])
-SUPPORTED_CARS |= set(interface_names['volkswagen'])
-SUPPORTED_CARS |= set(interface_names['mazda'])
-SUPPORTED_CARS |= set(interface_names['subaru'])
+VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True)
+SUPPORTED_BRANDS = VERSIONS.keys()
+SUPPORTED_CARS = [brand for brand in SUPPORTED_BRANDS for brand in interface_names[brand]]
+UNKNOWN_BRAND = "unknown"
try:
from xx.pipeline.c.CarState import migration
@@ -63,7 +56,7 @@ if __name__ == "__main__":
qlog_path = f"cd:/{dongle_id}/{time}/0/qlog.bz2"
else:
route = Route(route)
- qlog_path = route.qlog_paths()[0]
+ qlog_path = next((p for p in route.qlog_paths() if p is not None), None)
if qlog_path is None:
continue
@@ -72,30 +65,32 @@ if __name__ == "__main__":
lr = LogReader(qlog_path)
dongles.append(dongle_id)
+ CP = None
for msg in lr:
if msg.which() == "pandaStates":
- if msg.pandaStates[0].pandaType not in ['uno', 'blackPanda', 'dos']:
+ if msg.pandaStates[0].pandaType in ('unknown', 'whitePanda', 'greyPanda', 'pedal'):
+ print("wrong panda type")
break
elif msg.which() == "carParams":
- bts = msg.carParams.as_builder().to_bytes()
-
- car_fw = msg.carParams.carFw
+ CP = msg.carParams
+ car_fw = CP.carFw
if len(car_fw) == 0:
+ print("no fw")
break
- live_fingerprint = msg.carParams.carFingerprint
+ live_fingerprint = CP.carFingerprint
live_fingerprint = migration.get(live_fingerprint, live_fingerprint)
if args.car is not None:
live_fingerprint = args.car
if live_fingerprint not in SUPPORTED_CARS:
+ print("not in supported cars")
break
- fw_versions_dict = build_fw_dict(car_fw)
- exact_matches = match_fw_to_car_exact(fw_versions_dict)
- fuzzy_matches = match_fw_to_car_fuzzy(fw_versions_dict)
+ _, exact_matches = match_fw_to_car(car_fw, allow_exact=True, allow_fuzzy=False)
+ _, fuzzy_matches = match_fw_to_car(car_fw, allow_exact=False, allow_fuzzy=True)
if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint):
good_exact += 1
@@ -112,22 +107,26 @@ if __name__ == "__main__":
break
print(f"{dongle_id}|{time}")
- print("Old style:", live_fingerprint, "Vin", msg.carParams.carVin)
+ print("Old style:", live_fingerprint, "Vin", CP.carVin)
print("New style (exact):", exact_matches)
print("New style (fuzzy):", fuzzy_matches)
- for version in car_fw:
+ padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw])
+ for version in sorted(car_fw, key=lambda fw: fw.brand):
subaddr = None if version.subAddress == 0 else hex(version.subAddress)
- print(f" (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],")
+ print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],")
print("Mismatches")
found = False
- for car_fws in [TOYOTA_FW_VERSIONS, HONDA_FW_VERSIONS, HYUNDAI_FW_VERSIONS, VW_FW_VERSIONS, MAZDA_FW_VERSIONS, SUBARU_FW_VERSIONS]:
+ for brand in SUPPORTED_BRANDS:
+ car_fws = VERSIONS[brand]
if live_fingerprint in car_fws:
found = True
expected = car_fws[live_fingerprint]
for (_, expected_addr, expected_sub_addr), v in expected.items():
for version in car_fw:
+ if version.brand != brand and len(version.brand):
+ continue
sub_addr = None if version.subAddress == 0 else version.subAddress
addr = version.address
@@ -160,13 +159,16 @@ if __name__ == "__main__":
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
break
+
+ if CP is None:
+ print("no CarParams in logs")
except Exception:
traceback.print_exc()
except KeyboardInterrupt:
break
print()
- # Print FW versions that need to be added seperated out by car and address
+ # Print FW versions that need to be added separated out by car and address
for car, m in sorted(mismatches.items()):
print(car)
addrs = defaultdict(list)
diff --git a/selfdrive/debug/uiview.py b/selfdrive/debug/uiview.py
index 29465ecc8d..93d901f7c9 100755
--- a/selfdrive/debug/uiview.py
+++ b/selfdrive/debug/uiview.py
@@ -1,11 +1,15 @@
#!/usr/bin/env python3
import time
-from cereal import messaging, log
+
+from cereal import car, log, messaging
+from common.params import Params
from selfdrive.manager.process_config import managed_processes
if __name__ == "__main__":
- procs = ['camerad', 'ui', 'modeld', 'calibrationd']
+ CP = car.CarParams(notCar=True)
+ Params().put("CarParams", CP.to_bytes())
+ procs = ['camerad', 'ui', 'modeld', 'calibrationd']
for p in procs:
managed_processes[p].start()
diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/vw_mqb_config.py
index 1dd28d371f..8c4dbc55ee 100755
--- a/selfdrive/debug/vw_mqb_config.py
+++ b/selfdrive/debug/vw_mqb_config.py
@@ -23,7 +23,7 @@ if __name__ == "__main__":
desc_text = "Shows Volkswagen EPS software and coding info, and enables or disables Heading Control Assist " + \
"(Lane Assist). Useful for enabling HCA on cars without factory Lane Assist that want to use " + \
"openpilot integrated at the CAN gateway (J533)."
- epilog_text = "This tool is meant to run directly on a vehicle-installed comma two or comma three, with the " + \
+ epilog_text = "This tool is meant to run directly on a vehicle-installed comma three, with the " + \
"openpilot/tmux processes stopped. It should also work on a separate PC with a USB-attached comma " + \
"panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must " + \
"turn ignition off and on again for any changes to take effect."
@@ -67,20 +67,30 @@ if __name__ == "__main__":
print("Timeout fetching data from EPS")
quit()
- coding_variant, current_coding_array = None, None
- # EV_SteerAssisMQB covers the majority of MQB racks (EPS_MQB_ZFLS)
- # APA racks (MQB_PP_APA) have a different coding layout, which should
- # be easy to support once we identify the specific config bit
- if odx_file == "EV_SteerAssisMQB\x00":
+ coding_variant, current_coding_array, coding_byte, coding_bit = None, None, 0, 0
+ coding_length = len(current_coding)
+
+ # EV_SteerAssisMQB/MNB cover the majority of MQB racks (EPS_MQB_ZFLS)
+ if odx_file in ("EV_SteerAssisMQB\x00", "EV_SteerAssisMNB\x00"):
coding_variant = "ZF"
- current_coding_array = struct.unpack("!4B", current_coding)
- hca_enabled = (current_coding_array[0] & (1 << 4) != 0)
- hca_text = ("DISABLED", "ENABLED")[hca_enabled]
- print(f" Lane Assist: {hca_text}")
+ coding_byte = 0
+ coding_bit = 4
+
+ # APA racks (MQB_PP_APA) have a different coding layout
+ elif odx_file == "EV_SteerAssisVWBSMQBA\x00\x00\x00\x00":
+ coding_variant = "APA"
+ coding_byte = 3
+ coding_bit = 0
+
else:
print("Configuration changes not yet supported on this EPS!")
quit()
+ current_coding_array = struct.unpack(f"!{coding_length}B", current_coding)
+ hca_enabled = (current_coding_array[coding_byte] & (1 << coding_bit) != 0)
+ hca_text = ("DISABLED", "ENABLED")[hca_enabled]
+ print(f" Lane Assist: {hca_text}")
+
try:
params = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION).decode("utf-8")
param_version_system_params = params[1:3]
@@ -101,14 +111,14 @@ if __name__ == "__main__":
if args.action in ["enable", "disable"]:
print("\nAttempting configuration update")
- assert(coding_variant == "ZF") # revisit when we have the APA rack coding bit
+ assert(coding_variant in ("ZF", "APA"))
# ZF EPS config coding length can be anywhere from 1 to 4 bytes, but the
# bit we care about is always in the same place in the first byte
if args.action == "enable":
- new_byte_0 = current_coding_array[0] | (1 << 4)
+ new_byte = current_coding_array[coding_byte] | (1 << coding_bit)
else:
- new_byte_0 = current_coding_array[0] & ~(1 << 4)
- new_coding = new_byte_0.to_bytes(1, "little") + current_coding[1:]
+ new_byte = current_coding_array[coding_byte] & ~(1 << coding_bit)
+ new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:]
try:
seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore
diff --git a/selfdrive/hardware b/selfdrive/hardware
new file mode 120000
index 0000000000..02a42c502f
--- /dev/null
+++ b/selfdrive/hardware
@@ -0,0 +1 @@
+../system/hardware/
\ No newline at end of file
diff --git a/selfdrive/hardware/__init__.py b/selfdrive/hardware/__init__.py
deleted file mode 100644
index 3babf1bb5d..0000000000
--- a/selfdrive/hardware/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import os
-from typing import cast
-
-from selfdrive.hardware.base import HardwareBase
-from selfdrive.hardware.eon.hardware import Android
-from selfdrive.hardware.tici.hardware import Tici
-from selfdrive.hardware.pc.hardware import Pc
-
-EON = os.path.isfile('/EON')
-TICI = os.path.isfile('/TICI')
-PC = not (EON or TICI)
-
-
-if EON:
- HARDWARE = cast(HardwareBase, Android())
-elif TICI:
- HARDWARE = cast(HardwareBase, Tici())
-else:
- HARDWARE = cast(HardwareBase, Pc())
diff --git a/selfdrive/hardware/eon/androidd.py b/selfdrive/hardware/eon/androidd.py
deleted file mode 100755
index 3d91468b90..0000000000
--- a/selfdrive/hardware/eon/androidd.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python3
-import os
-import time
-import psutil
-from typing import Optional
-
-import cereal.messaging as messaging
-from common.realtime import set_core_affinity, set_realtime_priority
-from selfdrive.swaglog import cloudlog
-
-
-MAX_MODEM_CRASHES = 3
-MODEM_PATH = "/sys/devices/soc/2080000.qcom,mss/subsys5"
-WATCHED_PROCS = ["zygote", "zygote64", "system_server", "/system/bin/servicemanager", "/system/bin/surfaceflinger"]
-
-
-def get_modem_crash_count() -> Optional[int]:
- try:
- with open(os.path.join(MODEM_PATH, "crash_count")) as f:
- return int(f.read())
- except Exception:
- cloudlog.exception("Error reading modem crash count")
- return None
-
-def get_modem_state() -> str:
- try:
- with open(os.path.join(MODEM_PATH, "state")) as f:
- return f.read().strip()
- except Exception:
- cloudlog.exception("Error reading modem state")
- return ""
-
-def main():
- set_core_affinity(1)
- set_realtime_priority(1)
-
- procs = {}
- crash_count = 0
- modem_killed = False
- modem_state = "ONLINE"
- androidLog = messaging.sub_sock('androidLog')
-
- while True:
- # check critical android services
- if any(p is None or not p.is_running() for p in procs.values()) or not len(procs):
- cur = {p: None for p in WATCHED_PROCS}
- for p in psutil.process_iter(attrs=['cmdline']):
- cmdline = None if not len(p.info['cmdline']) else p.info['cmdline'][0]
- if cmdline in WATCHED_PROCS:
- cur[cmdline] = p
-
- if len(procs):
- for p in WATCHED_PROCS:
- if cur[p] != procs[p]:
- cloudlog.event("android service pid changed", proc=p, cur=cur[p], prev=procs[p], error=True)
- procs.update(cur)
-
- # log caught NetworkPolicy exceptions
- msgs = messaging.drain_sock(androidLog)
- for m in msgs:
- try:
- if m.androidLog.tag == "NetworkPolicy" and m.androidLog.message.startswith("problem with advise persist threshold"):
- cloudlog.event("network policy exception caught", androidLog=m.androidLog, error=True)
- except UnicodeDecodeError:
- pass
-
- if os.path.exists(MODEM_PATH):
- # check modem state
- state = get_modem_state()
- if state != modem_state and not modem_killed:
- cloudlog.event("modem state changed", state=state)
- modem_state = state
-
- # check modem crashes
- cnt = get_modem_crash_count()
- if cnt is not None:
- if cnt > crash_count:
- cloudlog.event("modem crash", count=cnt)
- crash_count = cnt
-
- # handle excessive modem crashes
- if crash_count > MAX_MODEM_CRASHES and not modem_killed:
- cloudlog.event("killing modem", error=True)
- with open("/sys/kernel/debug/msm_subsys/modem", "w") as f:
- f.write("put")
- modem_killed = True
-
- time.sleep(1)
-
-if __name__ == "__main__":
- main()
diff --git a/selfdrive/hardware/eon/hardware.h b/selfdrive/hardware/eon/hardware.h
deleted file mode 100644
index bcd1aaba74..0000000000
--- a/selfdrive/hardware/eon/hardware.h
+++ /dev/null
@@ -1,73 +0,0 @@
-#pragma once
-
-#include
-#include
-
-#include
-#include
-#include
-
-#include "selfdrive/common/util.h"
-#include "selfdrive/hardware/base.h"
-
-class HardwareEon : public HardwareNone {
-public:
- static constexpr float MAX_VOLUME = 1.0;
- static constexpr float MIN_VOLUME = 0.5;
-
- static bool EON() { return true; }
- static std::string get_os_version() {
- return "NEOS " + util::read_file("/VERSION");
- };
-
- static void reboot() { std::system("reboot"); };
- static void poweroff() { std::system("LD_LIBRARY_PATH= svc power shutdown"); };
- static void set_brightness(int percent) {
- std::ofstream brightness_control("/sys/class/leds/lcd-backlight/brightness");
- if (brightness_control.is_open()) {
- brightness_control << (int)(percent * (255/100.)) << "\n";
- brightness_control.close();
- }
- };
- static void set_display_power(bool on) {
- auto dtoken = android::SurfaceComposerClient::getBuiltInDisplay(android::ISurfaceComposer::eDisplayIdMain);
- android::SurfaceComposerClient::setDisplayPowerMode(dtoken, on ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF);
- };
-
- static bool get_ssh_enabled() {
- return std::system("getprop persist.neos.ssh | grep -qF '1'") == 0;
- };
- static void set_ssh_enabled(bool enabled) {
- std::string cmd = util::string_format("setprop persist.neos.ssh %d", enabled ? 1 : 0);
- std::system(cmd.c_str());
- };
-
- // android only
- inline static bool launched_activity = false;
- static void check_activity() {
- int ret = std::system("dumpsys SurfaceFlinger --list | grep -Fq 'com.android.settings'");
- launched_activity = ret == 0;
- }
-
- static void close_activities() {
- if(launched_activity) {
- std::system("pm disable com.android.settings && pm enable com.android.settings");
- }
- }
-
- static void launch_activity(std::string activity, std::string opts = "") {
- if (!launched_activity) {
- std::string cmd = "am start -n " + activity + " " + opts +
- " --ez extra_prefs_show_button_bar true \
- --es extra_prefs_set_next_text ''";
- std::system(cmd.c_str());
- }
- launched_activity = true;
- }
- static void launch_wifi() {
- launch_activity("com.android.settings/.wifi.WifiPickerActivity", "-a android.net.wifi.PICK_WIFI_NETWORK");
- }
- static void launch_tethering() {
- launch_activity("com.android.settings/.TetherSettings");
- }
-};
diff --git a/selfdrive/hardware/eon/hardware.py b/selfdrive/hardware/eon/hardware.py
deleted file mode 100644
index 4ab9f81fcf..0000000000
--- a/selfdrive/hardware/eon/hardware.py
+++ /dev/null
@@ -1,421 +0,0 @@
-import binascii
-import itertools
-import os
-import re
-import serial
-import struct
-import subprocess
-from typing import List, Union
-
-from cereal import log
-from selfdrive.hardware.base import HardwareBase, ThermalConfig
-
-try:
- from common.params import Params
-except Exception:
- # openpilot is not built yet
- Params = None
-
-NetworkType = log.DeviceState.NetworkType
-NetworkStrength = log.DeviceState.NetworkStrength
-
-MODEM_PATH = "/dev/smd11"
-
-def service_call(call: List[str]) -> Union[bytes, None]:
- try:
- ret = subprocess.check_output(["service", "call", *call], encoding='utf8').strip()
- if 'Parcel' not in ret:
- return None
- return parse_service_call_bytes(ret)
- except subprocess.CalledProcessError:
- return None
-
-
-def parse_service_call_unpack(r, fmt) -> Union[bytes, None]:
- try:
- return struct.unpack(fmt, r)[0]
- except Exception:
- return None
-
-
-def parse_service_call_string(r: bytes) -> Union[str, None]:
- try:
- r = r[8:] # Cut off length field
- r_str = r.decode('utf_16_be')
-
- # All pairs of two characters seem to be swapped. Not sure why
- result = ""
- for a, b, in itertools.zip_longest(r_str[::2], r_str[1::2], fillvalue='\x00'):
- result += b + a
-
- return result.replace('\x00', '')
- except Exception:
- return None
-
-
-def parse_service_call_bytes(ret: str) -> Union[bytes, None]:
- try:
- r = b""
- for hex_part in re.findall(r'[ (]([0-9a-f]{8})', ret):
- r += binascii.unhexlify(hex_part)
- return r
- except Exception:
- return None
-
-
-def getprop(key: str) -> Union[str, None]:
- try:
- return subprocess.check_output(["getprop", key], encoding='utf8').strip()
- except subprocess.CalledProcessError:
- return None
-
-
-class Android(HardwareBase):
- def get_os_version(self):
- with open("/VERSION") as f:
- return f.read().strip()
-
- def get_device_type(self):
- try:
- if int(Params().get("LastPeripheralPandaType")) == log.PandaState.PandaType.uno:
- return "two"
- except Exception:
- pass
- return "eon"
-
- def get_sound_card_online(self):
- return (os.path.isfile('/proc/asound/card0/state') and
- open('/proc/asound/card0/state').read().strip() == 'ONLINE')
-
- def get_imei(self, slot):
- slot = str(slot)
- if slot not in ("0", "1"):
- raise ValueError("SIM slot must be 0 or 1")
-
- return parse_service_call_string(service_call(["iphonesubinfo", "3", "i32", str(slot)]))
-
- def get_serial(self):
- ret = getprop("ro.serialno")
- if len(ret) == 0:
- ret = "cccccccc"
- return ret
-
- def get_subscriber_info(self):
- ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
- if ret is None or len(ret) < 8:
- return ""
- return ret
-
- def reboot(self, reason=None):
- # e.g. reason="recovery" to go into recover mode
- if reason is None:
- reason_args = ["null"]
- else:
- reason_args = ["s16", reason]
-
- subprocess.check_output([
- "service", "call", "power", "16", # IPowerManager.reboot
- "i32", "0", # no confirmation,
- *reason_args,
- "i32", "1" # wait
- ])
-
- def uninstall(self):
- with open('/cache/recovery/command', 'w') as f:
- f.write('--wipe_data\n')
- # IPowerManager.reboot(confirm=false, reason="recovery", wait=true)
- self.reboot(reason="recovery")
-
- def get_sim_info(self):
- # Used for athena
- # TODO: build using methods from this class
- sim_state = getprop("gsm.sim.state").split(",")
- network_type = getprop("gsm.network.type").split(',')
- mcc_mnc = getprop("gsm.sim.operator.numeric") or None
-
- sim_id = parse_service_call_string(service_call(['iphonesubinfo', '11']))
- cell_data_state = parse_service_call_unpack(service_call(['phone', '46']), ">q")
- cell_data_connected = (cell_data_state == 2)
-
- return {
- 'sim_id': sim_id,
- 'mcc_mnc': mcc_mnc,
- 'network_type': network_type,
- 'sim_state': sim_state,
- 'data_connected': cell_data_connected
- }
-
- def get_network_info(self):
- msg = log.DeviceState.NetworkInfo.new_message()
- msg.state = getprop("gsm.sim.state") or ""
- msg.technology = getprop("gsm.network.type") or ""
- msg.operator = getprop("gsm.sim.operator.numeric") or ""
-
- try:
- modem = serial.Serial(MODEM_PATH, 115200, timeout=0.1)
- modem.write(b"AT$QCRSRP?\r")
- msg.extra = modem.read_until(b"OK\r\n").decode('utf-8')
-
- rsrp = msg.extra.split("$QCRSRP: ")[1].split("\r")[0].split(",")
- msg.channel = int(rsrp[1])
- except Exception:
- pass
-
- return msg
-
- def get_network_type(self):
- wifi_check = parse_service_call_string(service_call(["connectivity", "2"]))
- if wifi_check is None:
- return NetworkType.none
- elif 'WIFI' in wifi_check:
- return NetworkType.wifi
- else:
- cell_check = parse_service_call_unpack(service_call(['phone', '59']), ">q")
- # from TelephonyManager.java
- cell_networks = {
- 0: NetworkType.none,
- 1: NetworkType.cell2G,
- 2: NetworkType.cell2G,
- 3: NetworkType.cell3G,
- 4: NetworkType.cell2G,
- 5: NetworkType.cell3G,
- 6: NetworkType.cell3G,
- 7: NetworkType.cell3G,
- 8: NetworkType.cell3G,
- 9: NetworkType.cell3G,
- 10: NetworkType.cell3G,
- 11: NetworkType.cell2G,
- 12: NetworkType.cell3G,
- 13: NetworkType.cell4G,
- 14: NetworkType.cell4G,
- 15: NetworkType.cell3G,
- 16: NetworkType.cell2G,
- 17: NetworkType.cell3G,
- 18: NetworkType.cell4G,
- 19: NetworkType.cell4G
- }
- return cell_networks.get(cell_check, NetworkType.none)
-
- def get_network_strength(self, network_type):
- network_strength = NetworkStrength.unknown
-
- # from SignalStrength.java
- def get_lte_level(rsrp, rssnr):
- INT_MAX = 2147483647
- if rsrp == INT_MAX:
- lvl_rsrp = NetworkStrength.unknown
- elif rsrp >= -95:
- lvl_rsrp = NetworkStrength.great
- elif rsrp >= -105:
- lvl_rsrp = NetworkStrength.good
- elif rsrp >= -115:
- lvl_rsrp = NetworkStrength.moderate
- else:
- lvl_rsrp = NetworkStrength.poor
- if rssnr == INT_MAX:
- lvl_rssnr = NetworkStrength.unknown
- elif rssnr >= 45:
- lvl_rssnr = NetworkStrength.great
- elif rssnr >= 10:
- lvl_rssnr = NetworkStrength.good
- elif rssnr >= -30:
- lvl_rssnr = NetworkStrength.moderate
- else:
- lvl_rssnr = NetworkStrength.poor
- return max(lvl_rsrp, lvl_rssnr)
-
- def get_tdscdma_level(tdscmadbm):
- lvl = NetworkStrength.unknown
- if tdscmadbm > -25:
- lvl = NetworkStrength.unknown
- elif tdscmadbm >= -49:
- lvl = NetworkStrength.great
- elif tdscmadbm >= -73:
- lvl = NetworkStrength.good
- elif tdscmadbm >= -97:
- lvl = NetworkStrength.moderate
- elif tdscmadbm >= -110:
- lvl = NetworkStrength.poor
- return lvl
-
- def get_gsm_level(asu):
- if asu <= 2 or asu == 99:
- lvl = NetworkStrength.unknown
- elif asu >= 12:
- lvl = NetworkStrength.great
- elif asu >= 8:
- lvl = NetworkStrength.good
- elif asu >= 5:
- lvl = NetworkStrength.moderate
- else:
- lvl = NetworkStrength.poor
- return lvl
-
- def get_evdo_level(evdodbm, evdosnr):
- lvl_evdodbm = NetworkStrength.unknown
- lvl_evdosnr = NetworkStrength.unknown
- if evdodbm >= -65:
- lvl_evdodbm = NetworkStrength.great
- elif evdodbm >= -75:
- lvl_evdodbm = NetworkStrength.good
- elif evdodbm >= -90:
- lvl_evdodbm = NetworkStrength.moderate
- elif evdodbm >= -105:
- lvl_evdodbm = NetworkStrength.poor
- if evdosnr >= 7:
- lvl_evdosnr = NetworkStrength.great
- elif evdosnr >= 5:
- lvl_evdosnr = NetworkStrength.good
- elif evdosnr >= 3:
- lvl_evdosnr = NetworkStrength.moderate
- elif evdosnr >= 1:
- lvl_evdosnr = NetworkStrength.poor
- return max(lvl_evdodbm, lvl_evdosnr)
-
- def get_cdma_level(cdmadbm, cdmaecio):
- lvl_cdmadbm = NetworkStrength.unknown
- lvl_cdmaecio = NetworkStrength.unknown
- if cdmadbm >= -75:
- lvl_cdmadbm = NetworkStrength.great
- elif cdmadbm >= -85:
- lvl_cdmadbm = NetworkStrength.good
- elif cdmadbm >= -95:
- lvl_cdmadbm = NetworkStrength.moderate
- elif cdmadbm >= -100:
- lvl_cdmadbm = NetworkStrength.poor
- if cdmaecio >= -90:
- lvl_cdmaecio = NetworkStrength.great
- elif cdmaecio >= -110:
- lvl_cdmaecio = NetworkStrength.good
- elif cdmaecio >= -130:
- lvl_cdmaecio = NetworkStrength.moderate
- elif cdmaecio >= -150:
- lvl_cdmaecio = NetworkStrength.poor
- return max(lvl_cdmadbm, lvl_cdmaecio)
-
- if network_type == NetworkType.none:
- return network_strength
- if network_type == NetworkType.wifi:
- out = subprocess.check_output('dumpsys connectivity', shell=True).decode('utf-8')
- network_strength = NetworkStrength.unknown
- for line in out.split('\n'):
- signal_str = "SignalStrength: "
- if signal_str in line:
- lvl_idx_start = line.find(signal_str) + len(signal_str)
- lvl_idx_end = line.find(']', lvl_idx_start)
- lvl = int(line[lvl_idx_start : lvl_idx_end])
- if lvl >= -50:
- network_strength = NetworkStrength.great
- elif lvl >= -60:
- network_strength = NetworkStrength.good
- elif lvl >= -70:
- network_strength = NetworkStrength.moderate
- else:
- network_strength = NetworkStrength.poor
- return network_strength
- else:
- # check cell strength
- out = subprocess.check_output('dumpsys telephony.registry', shell=True).decode('utf-8')
- for line in out.split('\n'):
- if "mSignalStrength" in line:
- arr = line.split(' ')
- ns = 0
- if ("gsm" in arr[14]):
- rsrp = int(arr[9])
- rssnr = int(arr[11])
- ns = get_lte_level(rsrp, rssnr)
- if ns == NetworkStrength.unknown:
- tdscmadbm = int(arr[13])
- ns = get_tdscdma_level(tdscmadbm)
- if ns == NetworkStrength.unknown:
- asu = int(arr[1])
- ns = get_gsm_level(asu)
- else:
- cdmadbm = int(arr[3])
- cdmaecio = int(arr[4])
- evdodbm = int(arr[5])
- evdosnr = int(arr[7])
- lvl_cdma = get_cdma_level(cdmadbm, cdmaecio)
- lvl_edmo = get_evdo_level(evdodbm, evdosnr)
- if lvl_edmo == NetworkStrength.unknown:
- ns = lvl_cdma
- elif lvl_cdma == NetworkStrength.unknown:
- ns = lvl_edmo
- else:
- ns = min(lvl_cdma, lvl_edmo)
- network_strength = max(network_strength, ns)
-
- return network_strength
-
- def get_battery_capacity(self):
- return self.read_param_file("/sys/class/power_supply/battery/capacity", int, 100)
-
- def get_battery_status(self):
- # This does not correspond with actual charging or not.
- # If a USB cable is plugged in, it responds with 'Charging', even when charging is disabled
- return self.read_param_file("/sys/class/power_supply/battery/status", lambda x: x.strip(), '')
-
- def get_battery_current(self):
- return self.read_param_file("/sys/class/power_supply/battery/current_now", int)
-
- def get_battery_voltage(self):
- return self.read_param_file("/sys/class/power_supply/battery/voltage_now", int)
-
- def get_battery_charging(self):
- # This does correspond with actually charging
- return self.read_param_file("/sys/class/power_supply/battery/charge_type", lambda x: x.strip() != "N/A", True)
-
- def set_battery_charging(self, on):
- with open('/sys/class/power_supply/battery/charging_enabled', 'w') as f:
- f.write(f"{1 if on else 0}\n")
-
- def get_usb_present(self):
- return self.read_param_file("/sys/class/power_supply/usb/present", lambda x: bool(int(x)), False)
-
- def get_current_power_draw(self):
- # We don't have a good direct way to measure this on android
- return None
-
- def shutdown(self):
- os.system('LD_LIBRARY_PATH="" svc power shutdown')
-
- def get_thermal_config(self):
- return ThermalConfig(cpu=((5, 7, 10, 12), 10), gpu=((16,), 10), mem=(2, 10), bat=(29, 1000), ambient=(25, 1), pmic=((22,), 1000))
-
- def set_screen_brightness(self, percentage):
- with open("/sys/class/leds/lcd-backlight/brightness", "w") as f:
- f.write(str(int(percentage * 2.55)))
-
- def get_screen_brightness(self):
- try:
- with open("/sys/class/leds/lcd-backlight/brightness") as f:
- return int(float(f.read()) / 2.55)
- except Exception:
- return 0
-
- def set_power_save(self, powersave_enabled):
- pass
-
- def get_gpu_usage_percent(self):
- try:
- used, total = open('/sys/devices/soc/b00000.qcom,kgsl-3d0/kgsl/kgsl-3d0/gpubusy').read().strip().split()
- perc = 100.0 * int(used) / int(total)
- return min(max(perc, 0), 100)
- except Exception:
- return 0
-
- def get_modem_version(self):
- return None
-
- def get_modem_temperatures(self):
- # Not sure if we can get this on the LeEco
- return []
-
- def get_nvme_temperatures(self):
- return []
-
- def initialize_hardware(self):
- pass
-
- def get_networks(self):
- return None
diff --git a/selfdrive/hardware/eon/neos.json b/selfdrive/hardware/eon/neos.json
deleted file mode 100644
index 4010f7126a..0000000000
--- a/selfdrive/hardware/eon/neos.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "ota_url": "https://commadist.azureedge.net/neosupdate/ota-signed-50da8800caa5cbc224acbaa21f3a83d21802a31d89cccfc62a898903a8eb19e7.zip",
- "ota_hash": "50da8800caa5cbc224acbaa21f3a83d21802a31d89cccfc62a898903a8eb19e7",
- "recovery_url": "https://commadist.azureedge.net/neosupdate/recovery-fe76739438d28ea6111853a737b616afdf340ba75c01c0ee99e6a28c19ecc29f.img",
- "recovery_len": 15222060,
- "recovery_hash": "fe76739438d28ea6111853a737b616afdf340ba75c01c0ee99e6a28c19ecc29f"
-}
diff --git a/selfdrive/hardware/eon/neos.py b/selfdrive/hardware/eon/neos.py
deleted file mode 100755
index 6f290fbcd1..0000000000
--- a/selfdrive/hardware/eon/neos.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import hashlib
-import json
-import logging
-import os
-import requests
-
-NEOSUPDATE_DIR = "/data/neoupdate"
-
-RECOVERY_DEV = "/dev/block/bootdevice/by-name/recovery"
-RECOVERY_COMMAND = "/cache/recovery/command"
-
-
-def get_fn(url: str):
- return os.path.join(NEOSUPDATE_DIR, os.path.basename(url))
-
-
-def download_file(url: str, fn: str, sha256: str, display_name: str, cloudlog=logging) -> None:
- # check if already downloaded
- if check_hash(fn, sha256):
- cloudlog.info(f"{display_name} already cached")
- return
-
- try:
- with open(fn, "ab+") as f:
- headers = {"Range": f"bytes={f.tell()}-"}
- r = requests.get(url, stream=True, allow_redirects=True, headers=headers)
- r.raise_for_status()
-
- total = int(r.headers['Content-Length'])
- if 'Content-Range' in r.headers:
- total = int(r.headers['Content-Range'].split('/')[-1])
-
- for chunk in r.iter_content(chunk_size=1024 * 1024):
- f.write(chunk)
- print(f"Downloading {display_name}: {f.tell() / total * 100}", flush=True)
- except Exception:
- cloudlog.error("download error")
- if os.path.isfile(fn):
- os.unlink(fn)
- raise
-
- if not check_hash(fn, sha256):
- if os.path.isfile(fn):
- os.unlink(fn)
- raise Exception("downloaded update failed hash check")
-
-
-def check_hash(fn: str, sha256: str, length: int = -1) -> bool:
- if not os.path.exists(fn):
- return False
-
- h = hashlib.sha256()
- with open(fn, "rb") as f:
- while f.tell() != length:
- r = min(max(0, length - f.tell()), 1024 * 1024) if length > 0 else 1024 * 1024
- dat = f.read(r)
- if not dat:
- break
- h.update(dat)
- return h.hexdigest().lower() == sha256.lower()
-
-
-def flash_update(update_fn: str, out_path: str) -> None:
- with open(update_fn, "rb") as update, open(out_path, "w+b") as out:
- while True:
- dat = update.read(8192)
- if len(dat) == 0:
- break
- out.write(dat)
-
-
-def download_neos_update(manifest_path: str, cloudlog=logging) -> None:
- with open(manifest_path) as f:
- m = json.load(f)
-
- os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
-
- # handle recovery updates
- if not check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len']):
- cloudlog.info("recovery needs update")
- recovery_fn = os.path.join(NEOSUPDATE_DIR, os.path.basename(m['recovery_url']))
- download_file(m['recovery_url'], recovery_fn, m['recovery_hash'], "recovery", cloudlog)
-
- flash_update(recovery_fn, RECOVERY_DEV)
- assert check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len']), "recovery flash corrupted"
- cloudlog.info("recovery successfully flashed")
-
- # download OTA update
- download_file(m['ota_url'], get_fn(m['ota_url']), m['ota_hash'], "system", cloudlog)
-
-
-def verify_update_ready(manifest_path: str) -> bool:
- with open(manifest_path) as f:
- m = json.load(f)
-
- ota_downloaded = check_hash(get_fn(m['ota_url']), m['ota_hash'])
- recovery_flashed = check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len'])
- return ota_downloaded and recovery_flashed
-
-
-def perform_ota_update(manifest_path: str) -> None:
- with open(manifest_path) as f:
- m = json.load(f)
-
- # reboot into recovery
- ota_fn = get_fn(m['ota_url'])
- with open(RECOVERY_COMMAND, "w") as rf:
- rf.write(f"--update_package={ota_fn}\n")
- os.system("service call power 16 i32 0 s16 recovery i32 1")
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="NEOS update utility",
- formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- parser.add_argument("--swap", action="store_true", help="Peform update after downloading")
- parser.add_argument("--swap-if-ready", action="store_true", help="Perform update if already downloaded")
- parser.add_argument("manifest", help="Manifest json")
- args = parser.parse_args()
-
- logging.basicConfig(level=logging.INFO)
-
- if args.swap_if_ready:
- if verify_update_ready(args.manifest):
- perform_ota_update(args.manifest)
- else:
- download_neos_update(args.manifest, logging)
- if args.swap:
- perform_ota_update(args.manifest)
diff --git a/selfdrive/hardware/eon/shutdownd.py b/selfdrive/hardware/eon/shutdownd.py
deleted file mode 100755
index 15112e7d5e..0000000000
--- a/selfdrive/hardware/eon/shutdownd.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python3
-import os
-import time
-import datetime
-
-from common.params import Params
-from selfdrive.hardware.eon.hardware import getprop
-from selfdrive.swaglog import cloudlog
-
-def main():
- prev = b""
- params = Params()
- while True:
- with open("/dev/__properties__", 'rb') as f:
- cur = f.read()
-
- if cur != prev:
- prev = cur
-
- # 0 for shutdown, 1 for reboot
- prop = getprop("sys.shutdown.requested")
- if prop is not None and len(prop) > 0:
- os.system("pkill -9 loggerd")
- params.put("LastSystemShutdown", f"'{prop}' {datetime.datetime.now()}")
- os.sync()
-
- time.sleep(120)
- cloudlog.error('shutdown false positive')
- break
-
- time.sleep(0.1)
-
-if __name__ == "__main__":
- main()
diff --git a/selfdrive/hardware/eon/test_neos_updater.py b/selfdrive/hardware/eon/test_neos_updater.py
deleted file mode 100755
index e258f943d2..0000000000
--- a/selfdrive/hardware/eon/test_neos_updater.py
+++ /dev/null
@@ -1,145 +0,0 @@
-#!/usr/bin/env python3
-import hashlib
-import http.server
-import json
-import os
-import unittest
-import random
-import requests
-import shutil
-import socketserver
-import tempfile
-import multiprocessing
-from pathlib import Path
-
-from selfdrive.hardware.eon.neos import RECOVERY_DEV, NEOSUPDATE_DIR, get_fn, download_file, \
- verify_update_ready, download_neos_update
-
-EON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
-MANIFEST = os.path.join(EON_DIR, "neos.json")
-
-PORT = 8000
-
-def server_thread(port):
- socketserver.TCPServer.allow_reuse_address = True
- httpd = socketserver.TCPServer(("", port), http.server.SimpleHTTPRequestHandler)
- httpd.serve_forever()
-
-
-class TestNeosUpdater(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- # generate a fake manifest
- cls.manifest = {}
- for i in ('ota', 'recovery'):
- with tempfile.NamedTemporaryFile(delete=False, dir=os.getcwd()) as f:
- dat = os.urandom(random.randint(1000, 100000))
- f.write(dat)
- cls.manifest[f"{i}_url"] = f"http://localhost:{PORT}/" + os.path.relpath(f.name)
- cls.manifest[F"{i}_hash"] = hashlib.sha256(dat).hexdigest()
- if i == "recovery":
- cls.manifest["recovery_len"] = len(dat)
-
- with tempfile.NamedTemporaryFile(delete=False, mode='w') as f:
- f.write(json.dumps(cls.manifest))
- cls.fake_manifest = f.name
-
- @classmethod
- def tearDownClass(cls):
- os.unlink(cls.fake_manifest)
- os.unlink(os.path.basename(cls.manifest['ota_url']))
- os.unlink(os.path.basename(cls.manifest['recovery_url']))
-
- def setUp(self):
- # server for update files
- self.server = multiprocessing.Process(target=server_thread, args=(PORT, ))
- self.server.start()
-
- # clean up
- if os.path.exists(NEOSUPDATE_DIR):
- shutil.rmtree(NEOSUPDATE_DIR)
-
- def tearDown(self):
- self.server.kill()
- self.server.join(1)
-
- def _corrupt_recovery(self):
- with open(RECOVERY_DEV, "wb") as f:
- f.write(b'\x00'*100)
-
- def test_manifest(self):
- with open(MANIFEST) as f:
- m = json.load(f)
-
- assert m['ota_hash'] in m['ota_url']
- assert m['recovery_hash'] in m['recovery_url']
- assert m['recovery_len'] > 0
-
- for url in (m['ota_url'], m['recovery_url']):
- r = requests.head(m['recovery_url'])
- r.raise_for_status()
- self.assertEqual(r.headers['Content-Type'], "application/octet-stream")
- if url == m['recovery_url']:
- self.assertEqual(int(r.headers['Content-Length']), m['recovery_len'])
-
- def test_download_hash_check(self):
- os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
- Path(get_fn(self.manifest['ota_url'])).touch()
- with self.assertRaisesRegex(Exception, "failed hash check"):
- download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
- self.manifest['ota_hash']+'a', "system")
-
- # should've unlinked after the failed hash check, should succeed now
- download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
- self.manifest['ota_hash'], "system")
-
- # TODO: needs an http server that supports Content-Range
- #def test_download_resume(self):
- # os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
- # with open(os.path.basename(self.manifest['ota_url']), "rb") as src, \
- # open(get_fn(self.manifest['ota_url']), "wb") as dest:
- # l = dest.write(src.read(8192))
- # assert l == 8192
- # download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
- # self.manifest['ota_hash'], "system")
-
- def test_download_no_internet(self):
- self.server.kill()
- os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
- # fail, no internet
- with self.assertRaises(requests.exceptions.ConnectionError):
- download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
- self.manifest['ota_hash'], "system")
-
- # already cached, ensure we don't hit the server
- shutil.copyfile(os.path.basename(self.manifest['ota_url']), get_fn(self.manifest['ota_url']))
- download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
- self.manifest['ota_hash'], "system")
-
-
- def test_download_update(self):
- download_neos_update(self.fake_manifest)
- self.assertTrue(verify_update_ready(self.fake_manifest))
-
- def test_verify_update(self):
- # good state
- download_neos_update(self.fake_manifest)
- self.assertTrue(verify_update_ready(self.fake_manifest))
-
- # corrupt recovery
- self._corrupt_recovery()
- self.assertFalse(verify_update_ready(self.fake_manifest))
-
- # back to good state
- download_neos_update(self.fake_manifest)
- self.assertTrue(verify_update_ready(self.fake_manifest))
-
- # corrupt ota
- self._corrupt_recovery()
- with open(os.path.join(NEOSUPDATE_DIR, os.path.basename(self.manifest['ota_url'])), "ab") as f:
- f.write(b'\x00')
- self.assertFalse(verify_update_ready(self.fake_manifest))
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/selfdrive/hardware/eon/update_neos.sh b/selfdrive/hardware/eon/update_neos.sh
deleted file mode 100755
index ccc6ecce44..0000000000
--- a/selfdrive/hardware/eon/update_neos.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/bash
-
-ROOT=$PWD/../../..
-$ROOT/installer/updater/updater "file://$ROOT/installer/updater/update.json"
diff --git a/selfdrive/hardware/eon/updater b/selfdrive/hardware/eon/updater
deleted file mode 100755
index eaf34e957c..0000000000
Binary files a/selfdrive/hardware/eon/updater and /dev/null differ
diff --git a/selfdrive/hardware/tici/pins.py b/selfdrive/hardware/tici/pins.py
deleted file mode 100644
index 7139f5e955..0000000000
--- a/selfdrive/hardware/tici/pins.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# TODO: these are also defined in a header
-# GPIO pin definitions
-GPIO_HUB_RST_N = 30
-GPIO_UBLOX_RST_N = 32
-GPIO_UBLOX_SAFEBOOT_N = 33
-GPIO_UBLOX_PWR_EN = 34
-GPIO_STM_RST_N = 124
-GPIO_STM_BOOT0 = 134
diff --git a/selfdrive/hardware/tici/power_monitor.py b/selfdrive/hardware/tici/power_monitor.py
deleted file mode 100755
index d7f113cf2c..0000000000
--- a/selfdrive/hardware/tici/power_monitor.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python3
-import sys
-import time
-
-def average(avg, sample):
- # Weighted avg between existing value and new sample
- return ((avg[0] * avg[1] + sample) / (avg[1] + 1), avg[1] + 1)
-
-if __name__ == '__main__':
-
- sample_time = None
- if len(sys.argv) > 1:
- sample_time = int(sys.argv[1])
-
- start_time = time.monotonic()
- try:
- voltage_average = (0, 0) # average, count
- current_average = (0, 0)
- power_average = (0, 0)
- power_total_average = (0, 0)
- while sample_time is None or time.monotonic() - start_time < sample_time:
- with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/in1_input") as f:
- voltage_total = int(f.read()) / 1000.
-
- with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/curr1_input") as f:
- current_total = int(f.read())
-
- with open("/sys/class/power_supply/bms/voltage_now") as f:
- voltage = int(f.read()) / 1e6 # volts
-
- with open("/sys/class/power_supply/bms/current_now") as f:
- current = int(f.read()) / 1e3 # ma
-
- power = voltage*current
- power_total = voltage_total*current_total
-
- # compute averages
- voltage_average = average(voltage_average, voltage)
- current_average = average(current_average, current)
- power_average = average(power_average, power)
- power_total_average = average(power_total_average, power_total)
-
- print(f"{power:12.2f} mW {power_total:12.2f} mW {power_total - power:12.2f} mW")
- time.sleep(0.25)
- finally:
- stop_time = time.monotonic()
- print("\n----------------------Average-----------------------------------")
- voltage = voltage_average[0]
- current = current_average[0]
- power = power_average[0]
- print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {power_total:12.2f} mW")
- print(f" {stop_time - start_time:.2f} Seconds {voltage_average[1]} samples")
- print("----------------------------------------------------------------")
diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py
index ae314e38c4..1c68eb67bd 100755
--- a/selfdrive/locationd/calibrationd.py
+++ b/selfdrive/locationd/calibrationd.py
@@ -8,19 +8,17 @@ and the image input into the neural network is not corrected for roll.
import gc
import os
+import capnp
import numpy as np
-from typing import NoReturn
+from typing import List, NoReturn, Optional
from cereal import log
import cereal.messaging as messaging
+from common.conversions import Conversions as CV
from common.params import Params, put_nonblocking
from common.realtime import set_realtime_priority
-from common.transformations.model import model_height
-from common.transformations.camera import get_view_frame_from_road_frame
from common.transformations.orientation import rot_from_euler, euler_from_rot
-from selfdrive.config import Conversions as CV
-from selfdrive.hardware import TICI
-from selfdrive.swaglog import cloudlog
+from system.swaglog import cloudlog
MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS
MAX_VEL_ANGLE_STD = np.radians(0.25)
@@ -33,6 +31,7 @@ INPUTS_NEEDED = 5 # Minimum blocks needed for valid calibration
INPUTS_WANTED = 50 # We want a little bit more than we need for stability
MAX_ALLOWED_SPREAD = np.radians(2)
RPY_INIT = np.array([0.0,0.0,0.0])
+WIDE_FROM_DEVICE_EULER_INIT = np.array([0.0, 0.0, 0.0])
# These values are needed to accommodate biggest modelframe
PITCH_LIMITS = np.array([-0.09074112085129739, 0.14907572052989657])
@@ -46,11 +45,11 @@ class Calibration:
INVALID = 2
-def is_calibration_valid(rpy):
- return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1])
+def is_calibration_valid(rpy: np.ndarray) -> bool:
+ return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) # type: ignore
-def sanity_clip(rpy):
+def sanity_clip(rpy: np.ndarray) -> np.ndarray:
if np.isnan(rpy).any():
rpy = RPY_INIT
return np.array([rpy[0],
@@ -58,15 +57,17 @@ def sanity_clip(rpy):
np.clip(rpy[2], YAW_LIMITS[0] - .005, YAW_LIMITS[1] + .005)])
-class Calibrator():
- def __init__(self, param_put=False):
+class Calibrator:
+ def __init__(self, param_put: bool = False):
self.param_put = param_put
+ self.not_car = False
+
# Read saved calibration
params = Params()
calibration_params = params.get("CalibrationParams")
- self.wide_camera = TICI and params.get_bool('EnableWideCamera')
rpy_init = RPY_INIT
+ wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
valid_blocks = 0
if param_put and calibration_params:
@@ -74,28 +75,38 @@ class Calibrator():
msg = log.Event.from_bytes(calibration_params)
rpy_init = np.array(msg.liveCalibration.rpyCalib)
valid_blocks = msg.liveCalibration.validBlocks
+ wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler)
except Exception:
cloudlog.exception("Error reading cached CalibrationParams")
- self.reset(rpy_init, valid_blocks)
+ self.reset(rpy_init, valid_blocks, wide_from_device_euler)
self.update_status()
- def reset(self, rpy_init=RPY_INIT, valid_blocks=0, smooth_from=None):
+ def reset(self, rpy_init: np.ndarray = RPY_INIT,
+ valid_blocks: int = 0,
+ wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT,
+ smooth_from: Optional[np.ndarray] = None) -> None:
if not np.isfinite(rpy_init).all():
self.rpy = RPY_INIT.copy()
else:
self.rpy = rpy_init.copy()
+ if not np.isfinite(wide_from_device_euler_init).all() or len(wide_from_device_euler_init) != 3:
+ self.wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT.copy()
+ else:
+ self.wide_from_device_euler = wide_from_device_euler_init.copy()
+
if not np.isfinite(valid_blocks) or valid_blocks < 0:
self.valid_blocks = 0
else:
self.valid_blocks = valid_blocks
self.rpys = np.tile(self.rpy, (INPUTS_WANTED, 1))
+ self.wide_from_device_eulers = np.tile(self.wide_from_device_euler, (INPUTS_WANTED, 1))
self.idx = 0
self.block_idx = 0
- self.v_ego = 0
+ self.v_ego = 0.0
if smooth_from is None:
self.old_rpy = RPY_INIT
@@ -104,15 +115,16 @@ class Calibrator():
self.old_rpy = smooth_from
self.old_rpy_weight = 1.0
- def get_valid_idxs(self):
+ def get_valid_idxs(self) -> List[int]:
# exclude current block_idx from validity window
before_current = list(range(self.block_idx))
after_current = list(range(min(self.valid_blocks, self.block_idx + 1), self.valid_blocks))
return before_current + after_current
- def update_status(self):
+ def update_status(self) -> None:
valid_idxs = self.get_valid_idxs()
if valid_idxs:
+ self.wide_from_device_euler = np.mean(self.wide_from_device_eulers[valid_idxs], axis=0)
rpys = self.rpys[valid_idxs]
self.rpy = np.mean(rpys, axis=0)
max_rpy_calib = np.array(np.max(rpys, axis=0))
@@ -129,7 +141,7 @@ class Calibrator():
self.cal_status = Calibration.INVALID
# If spread is too high, assume mounting was changed and reset to last block.
- # Make the transition smooth. Abrupt transitions are not good foor feedback loop through supercombo model.
+ # Make the transition smooth. Abrupt transitions are not good for feedback loop through supercombo model.
if max(self.calib_spread) > MAX_ALLOWED_SPREAD and self.cal_status == Calibration.CALIBRATED:
self.reset(self.rpys[self.block_idx - 1], valid_blocks=INPUTS_NEEDED, smooth_from=self.rpy)
@@ -137,23 +149,23 @@ class Calibrator():
if self.param_put and write_this_cycle:
put_nonblocking("CalibrationParams", self.get_msg().to_bytes())
- def handle_v_ego(self, v_ego):
+ def handle_v_ego(self, v_ego: float) -> None:
self.v_ego = v_ego
- def get_smooth_rpy(self):
+ def get_smooth_rpy(self) -> np.ndarray:
if self.old_rpy_weight > 0:
return self.old_rpy_weight * self.old_rpy + (1.0 - self.old_rpy_weight) * self.rpy
else:
return self.rpy
- def handle_cam_odom(self, trans, rot, trans_std, rot_std):
+ def handle_cam_odom(self, trans: List[float],
+ rot: List[float],
+ wide_from_device_euler: List[float],
+ trans_std: List[float]) -> Optional[np.ndarray]:
self.old_rpy_weight = min(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES)
straight_and_fast = ((self.v_ego > MIN_SPEED_FILTER) and (trans[0] > MIN_SPEED_FILTER) and (abs(rot[2]) < MAX_YAW_RATE_FILTER))
- if self.wide_camera:
- angle_std_threshold = 4*MAX_VEL_ANGLE_STD
- else:
- angle_std_threshold = MAX_VEL_ANGLE_STD
+ angle_std_threshold = MAX_VEL_ANGLE_STD
certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or
(self.valid_blocks < INPUTS_NEEDED))
if not (straight_and_fast and certain_if_calib):
@@ -165,7 +177,14 @@ class Calibrator():
new_rpy = euler_from_rot(rot_from_euler(self.get_smooth_rpy()).dot(rot_from_euler(observed_rpy)))
new_rpy = sanity_clip(new_rpy)
- self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] + (BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE)
+ if len(wide_from_device_euler) == 3:
+ new_wide_from_device_euler = np.array(wide_from_device_euler)
+ else:
+ new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT
+ self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] +
+ (BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE)
+ self.wide_from_device_eulers[self.block_idx] = (self.idx*self.wide_from_device_eulers[self.block_idx] +
+ (BLOCK_SIZE - self.idx) * new_wide_from_device_euler) / float(BLOCK_SIZE)
self.idx = (self.idx + 1) % BLOCK_SIZE
if self.idx == 0:
self.block_idx += 1
@@ -176,29 +195,38 @@ class Calibrator():
return new_rpy
- def get_msg(self):
+ def get_msg(self) -> capnp.lib.capnp._DynamicStructBuilder:
smooth_rpy = self.get_smooth_rpy()
- extrinsic_matrix = get_view_frame_from_road_frame(0, smooth_rpy[1], smooth_rpy[2], model_height)
msg = messaging.new_message('liveCalibration')
- msg.liveCalibration.validBlocks = self.valid_blocks
- msg.liveCalibration.calStatus = self.cal_status
- msg.liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
- msg.liveCalibration.extrinsicMatrix = extrinsic_matrix.flatten().tolist()
- msg.liveCalibration.rpyCalib = smooth_rpy.tolist()
- msg.liveCalibration.rpyCalibSpread = self.calib_spread.tolist()
+ liveCalibration = msg.liveCalibration
+
+ liveCalibration.validBlocks = self.valid_blocks
+ liveCalibration.calStatus = self.cal_status
+ liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
+ liveCalibration.rpyCalib = smooth_rpy.tolist()
+ liveCalibration.rpyCalibSpread = self.calib_spread.tolist()
+ liveCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist()
+
+ if self.not_car:
+ liveCalibration.validBlocks = INPUTS_NEEDED
+ liveCalibration.calStatus = Calibration.CALIBRATED
+ liveCalibration.calPerc = 100.
+ liveCalibration.rpyCalib = [0, 0, 0]
+ liveCalibration.rpyCalibSpread = self.calib_spread.tolist()
+
return msg
- def send_data(self, pm) -> None:
+ def send_data(self, pm: messaging.PubMaster) -> None:
pm.send('liveCalibration', self.get_msg())
-def calibrationd_thread(sm=None, pm=None) -> NoReturn:
+def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn:
gc.disable()
set_realtime_priority(1)
if sm is None:
- sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll=['cameraOdometry'])
+ sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
if pm is None:
pm = messaging.PubMaster(['liveCalibration'])
@@ -209,12 +237,14 @@ def calibrationd_thread(sm=None, pm=None) -> NoReturn:
timeout = 0 if sm.frame == -1 else 100
sm.update(timeout)
+ calibrator.not_car = sm['carParams'].notCar
+
if sm.updated['cameraOdometry']:
calibrator.handle_v_ego(sm['carState'].vEgo)
new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans,
sm['cameraOdometry'].rot,
- sm['cameraOdometry'].transStd,
- sm['cameraOdometry'].rotStd)
+ sm['cameraOdometry'].wideFromDeviceEuler,
+ sm['cameraOdometry'].transStd)
if DEBUG and new_rpy is not None:
print('got new rpy', new_rpy)
@@ -224,7 +254,7 @@ def calibrationd_thread(sm=None, pm=None) -> NoReturn:
calibrator.send_data(pm)
-def main(sm=None, pm=None) -> NoReturn:
+def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn:
calibrationd_thread(sm, pm)
diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py
new file mode 100755
index 0000000000..6936d88acc
--- /dev/null
+++ b/selfdrive/locationd/laikad.py
@@ -0,0 +1,395 @@
+#!/usr/bin/env python3
+import json
+import math
+import os
+import time
+from collections import defaultdict
+from concurrent.futures import Future, ProcessPoolExecutor
+from datetime import datetime
+from enum import IntEnum
+from typing import List, Optional
+
+import numpy as np
+
+from cereal import log, messaging
+from common.params import Params, put_nonblocking
+from laika import AstroDog
+from laika.constants import SECS_IN_HR, SECS_IN_MIN
+from laika.downloader import DownloadFailed
+from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem, parse_qcom_ephem
+from laika.gps_time import GPSTime
+from laika.helpers import ConstellationId
+from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom
+from selfdrive.locationd.laikad_helpers import calc_pos_fix_gauss_newton, get_posfix_sympy_fun
+from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind
+from selfdrive.locationd.models.gnss_kf import GNSSKalman
+from selfdrive.locationd.models.gnss_kf import States as GStates
+from system.swaglog import cloudlog
+
+MAX_TIME_GAP = 10
+EPHEMERIS_CACHE = 'LaikadEphemeris'
+DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache/"
+CACHE_VERSION = 0.1
+POS_FIX_RESIDUAL_THRESHOLD = 100.0
+
+
+class Laikad:
+ def __init__(self, valid_const=("GPS", "GLONASS"), auto_fetch_orbits=True, auto_update=False,
+ valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV),
+ save_ephemeris=False, use_qcom=False):
+ """
+ valid_const: GNSS constellation which can be used
+ auto_fetch_orbits: If true fetch orbits from internet when needed
+ auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere.
+ valid_ephem_types: Valid ephemeris types to be used by AstroDog
+ save_ephemeris: If true saves and loads nav and orbit ephemeris to cache.
+ """
+ self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER)
+ self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom)
+
+ self.auto_fetch_orbits = auto_fetch_orbits
+ self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None
+ self.orbit_fetch_future: Optional[Future] = None
+
+ self.last_fetch_orbits_t = None
+ self.got_first_gnss_msg = False
+ self.last_cached_t = None
+ self.save_ephemeris = save_ephemeris
+ self.load_cache()
+
+ self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)}
+ self.last_pos_fix = []
+ self.last_pos_residual = []
+ self.last_pos_fix_t = None
+ self.gps_week = None
+ self.use_qcom = use_qcom
+
+ def load_cache(self):
+ if not self.save_ephemeris:
+ return
+
+ cache = Params().get(EPHEMERIS_CACHE)
+ if not cache:
+ return
+
+ try:
+ cache = json.loads(cache, object_hook=deserialize_hook)
+ self.astro_dog.add_orbits(cache['orbits'])
+ self.astro_dog.add_navs(cache['nav'])
+ self.last_fetch_orbits_t = cache['last_fetch_orbits_t']
+ except json.decoder.JSONDecodeError:
+ cloudlog.exception("Error parsing cache")
+ timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan'
+ cloudlog.debug(
+ f"Loaded nav ({sum([len(v) for v in cache['nav']])}) and orbits ({sum([len(v) for v in cache['orbits']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " +
+ f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.orbit_fetched_times._ranges]}")
+
+ def cache_ephemeris(self, t: GPSTime):
+ if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN):
+ put_nonblocking(EPHEMERIS_CACHE, json.dumps(
+ {'version': CACHE_VERSION, 'last_fetch_orbits_t': self.last_fetch_orbits_t, 'orbits': self.astro_dog.orbits, 'nav': self.astro_dog.nav},
+ cls=CacheSerializer))
+ cloudlog.debug("Cache saved")
+ self.last_cached_t = t
+
+ def get_est_pos(self, t, processed_measurements):
+ if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2:
+ min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5
+ pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements)
+ if len(pos_fix) > 0:
+ self.last_pos_fix_t = t
+ residual_median = np.median(np.abs(pos_fix_residual))
+ if np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD:
+ cloudlog.debug(f"Pos fix is within threshold with median: {residual_median.round()}")
+ self.last_pos_fix = pos_fix[:3]
+ self.last_pos_residual = pos_fix_residual
+ else:
+ cloudlog.debug(f"Pos fix failed with median: {residual_median.round()}. All residuals: {np.round(pos_fix_residual)}")
+ return self.last_pos_fix
+
+ def is_good_report(self, gnss_msg):
+ if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom:
+ constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source)
+ # TODO support GLONASS
+ return constellation_id in [ConstellationId.GPS, ConstellationId.SBAS]
+ elif gnss_msg.which() == 'measurementReport' and not self.use_qcom:
+ return True
+ else:
+ return False
+
+ def read_report(self, gnss_msg):
+ if self.use_qcom:
+ report = gnss_msg.drMeasurementReport
+ week = report.gpsWeek
+ tow = report.gpsMilliseconds / 1000.0
+ new_meas = read_raw_qcom(report)
+ else:
+ report = gnss_msg.measurementReport
+ week = report.gpsWeek
+ tow = report.rcvTow
+ new_meas = read_raw_ublox(report)
+ return week, tow, new_meas
+
+ def is_ephemeris(self, gnss_msg):
+ if self.use_qcom:
+ return gnss_msg.which() == 'drSvPoly'
+ else:
+ return gnss_msg.which() == 'ephemeris'
+
+ def read_ephemeris(self, gnss_msg):
+ # TODO this only works on GLONASS
+ if self.use_qcom:
+ # TODO this is not robust to gps week rollover
+ if self.gps_week is None:
+ return
+ ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week)
+ else:
+ ephem = convert_ublox_ephem(gnss_msg.ephemeris)
+ self.astro_dog.add_navs({ephem.prn: [ephem]})
+ self.cache_ephemeris(t=ephem.epoch)
+
+ def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False):
+ if self.is_good_report(gnss_msg):
+ week, tow, new_meas = self.read_report(gnss_msg)
+ self.gps_week = week
+
+ t = gnss_mono_time * 1e-9
+ if week > 0:
+ self.got_first_gnss_msg = True
+ latest_msg_t = GPSTime(week, tow)
+ if self.auto_fetch_orbits:
+ self.fetch_orbits(latest_msg_t, block)
+
+ # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites
+ new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7]
+
+ processed_measurements = process_measurements(new_meas, self.astro_dog)
+ est_pos = self.get_est_pos(t, processed_measurements)
+
+ corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else []
+ if gnss_mono_time % 10 == 0:
+ cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}")
+
+ self.update_localizer(est_pos, t, corrected_measurements)
+ kf_valid = all(self.kf_valid(t))
+ ecef_pos = self.gnss_kf.x[GStates.ECEF_POS]
+ ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY]
+
+ p = self.gnss_kf.P.diagonal()
+ pos_std = np.sqrt(p[GStates.ECEF_POS])
+ vel_std = np.sqrt(p[GStates.ECEF_VELOCITY])
+
+ meas_msgs = [create_measurement_msg(m) for m in corrected_measurements]
+ dat = messaging.new_message("gnssMeasurements")
+ measurement_msg = log.LiveLocationKalman.Measurement.new_message
+ dat.gnssMeasurements = {
+ "gpsWeek": week,
+ "gpsTimeOfWeek": tow,
+ "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid),
+ "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid),
+ # TODO std is incorrectly the dimension of the measurements and not 3D
+ "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t),
+ "ubloxMonoTime": gnss_mono_time,
+ "correctedMeasurements": meas_msgs
+ }
+ return dat
+ elif self.is_ephemeris(gnss_msg):
+ self.read_ephemeris(gnss_msg)
+
+ #elif gnss_msg.which() == 'ionoData':
+ # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them.
+
+ def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]):
+ # Check time and outputs are valid
+ valid = self.kf_valid(t)
+ if not all(valid):
+ if not valid[0]: # Filter not initialized
+ pass
+ elif not valid[1]:
+ cloudlog.error("Time gap of over 10s detected, gnss kalman reset")
+ elif not valid[2]:
+ cloudlog.error("Gnss kalman filter state is nan")
+ if len(est_pos) > 0:
+ cloudlog.info(f"Reset kalman filter with {est_pos}")
+ self.init_gnss_localizer(est_pos)
+ else:
+ return
+ if len(measurements) > 0:
+ kf_add_observations(self.gnss_kf, t, measurements)
+ else:
+ # Ensure gnss filter is updated even with no new measurements
+ self.gnss_kf.predict(t)
+
+ def kf_valid(self, t: float) -> List[bool]:
+ filter_time = self.gnss_kf.filter.get_filter_time()
+ return [not math.isnan(filter_time),
+ abs(t - filter_time) < MAX_TIME_GAP,
+ all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))]
+
+ def init_gnss_localizer(self, est_pos):
+ x_initial, p_initial_diag = np.copy(GNSSKalman.x_initial), np.copy(np.diagonal(GNSSKalman.P_initial))
+ x_initial[GStates.ECEF_POS] = est_pos
+ p_initial_diag[GStates.ECEF_POS] = 1000 ** 2
+ self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag)
+
+ def fetch_orbits(self, t: GPSTime, block):
+ # Download new orbits if 1 hour of orbits data left
+ if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN):
+ astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir
+ ret = None
+
+ if block: # Used for testing purposes
+ ret = get_orbit_data(t, *astro_dog_vars)
+ elif self.orbit_fetch_future is None:
+ self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1)
+ self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars)
+ elif self.orbit_fetch_future.done():
+ ret = self.orbit_fetch_future.result()
+ self.orbit_fetch_executor = self.orbit_fetch_future = None
+
+ if ret is not None:
+ if ret[0] is None:
+ self.last_fetch_orbits_t = ret[2]
+ else:
+ self.astro_dog.orbits, self.astro_dog.orbit_fetched_times, self.last_fetch_orbits_t = ret
+ self.cache_ephemeris(t=t)
+
+
+def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir):
+ astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir)
+ cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}")
+ start_time = time.monotonic()
+ try:
+ astro_dog.get_orbit_data(t, only_predictions=True)
+ cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s")
+ cloudlog.debug(f"Downloaded orbits ({sum([len(v) for v in astro_dog.orbits])}): {list(astro_dog.orbits.keys())}" +
+ f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}")
+ return astro_dog.orbits, astro_dog.orbit_fetched_times, t
+ except (DownloadFailed, RuntimeError, ValueError, IOError) as e:
+ cloudlog.warning(f"No orbit data found or parsing failure: {e}")
+ return None, None, t
+
+
+def create_measurement_msg(meas: GNSSMeasurement):
+ c = log.GnssMeasurements.CorrectedMeasurement.new_message()
+ c.constellationId = meas.constellation_id.value
+ c.svId = meas.sv_id
+ c.glonassFrequency = meas.glonass_freq if meas.constellation_id == ConstellationId.GLONASS else 0
+ c.pseudorange = float(meas.observables_final['C1C'])
+ c.pseudorangeStd = float(meas.observables_std['C1C'])
+ c.pseudorangeRate = float(meas.observables_final['D1C'])
+ c.pseudorangeRateStd = float(meas.observables_std['D1C'])
+ c.satPos = meas.sat_pos_final.tolist()
+ c.satVel = meas.sat_vel.tolist()
+ c.satVel = meas.sat_vel.tolist()
+ ephem = meas.sat_ephemeris
+ assert ephem is not None
+ week, time_of_week = -1, -1
+ if ephem.eph_type == EphemerisType.NAV:
+ source_type = EphemerisSourceType.nav
+ elif ephem.eph_type == EphemerisType.QCOM_POLY:
+ source_type = EphemerisSourceType.qcom
+ else:
+ assert ephem.file_epoch is not None
+ week = ephem.file_epoch.week
+ time_of_week = ephem.file_epoch.tow
+ file_src = ephem.file_source
+ if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z'
+ source_type = EphemerisSourceType.nasaUltraRapid
+ elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3'
+ source_type = EphemerisSourceType.glonassIacUltraRapid
+ else:
+ raise Exception(f"Didn't expect file source {file_src}")
+
+ c.ephemerisSource.type = source_type.value
+ c.ephemerisSource.gpsWeek = week
+ c.ephemerisSource.gpsTimeOfWeek = int(time_of_week)
+ return c
+
+
+def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMeasurement]):
+ ekf_data = defaultdict(list)
+ for m in measurements:
+ m_arr = m.as_array()
+ if m.constellation_id == ConstellationId.GPS:
+ ekf_data[ObservationKind.PSEUDORANGE_GPS].append(m_arr)
+ elif m.constellation_id == ConstellationId.GLONASS:
+ ekf_data[ObservationKind.PSEUDORANGE_GLONASS].append(m_arr)
+ ekf_data[ObservationKind.PSEUDORANGE_RATE_GPS] = ekf_data[ObservationKind.PSEUDORANGE_GPS]
+ ekf_data[ObservationKind.PSEUDORANGE_RATE_GLONASS] = ekf_data[ObservationKind.PSEUDORANGE_GLONASS]
+ for kind, data in ekf_data.items():
+ if len(data) > 0:
+ gnss_kf.predict_and_observe(t, kind, data)
+
+
+class CacheSerializer(json.JSONEncoder):
+
+ def default(self, o):
+ if isinstance(o, Ephemeris):
+ return o.to_json()
+ if isinstance(o, GPSTime):
+ return o.__dict__
+ if isinstance(o, np.ndarray):
+ return o.tolist()
+ return json.JSONEncoder.default(self, o)
+
+
+def deserialize_hook(dct):
+ if 'ephemeris' in dct:
+ return Ephemeris.from_json(dct)
+ if 'week' in dct:
+ return GPSTime(dct['week'], dct['tow'])
+ return dct
+
+
+class EphemerisSourceType(IntEnum):
+ nav = 0
+ nasaUltraRapid = 1
+ glonassIacUltraRapid = 2
+ qcom = 3
+
+
+def main(sm=None, pm=None):
+ use_qcom = not Params().get_bool("UbloxAvailable", block=True)
+ if use_qcom:
+ raw_gnss_socket = "qcomGnss"
+ else:
+ raw_gnss_socket = "ubloxGnss"
+
+ if sm is None:
+ sm = messaging.SubMaster([raw_gnss_socket, 'clocks'])
+ if pm is None:
+ pm = messaging.PubMaster(['gnssMeasurements'])
+
+ replay = "REPLAY" in os.environ
+ use_internet = "LAIKAD_NO_INTERNET" not in os.environ
+ laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet, use_qcom=use_qcom)
+
+ while True:
+ sm.update()
+
+ if sm.updated[raw_gnss_socket]:
+ gnss_msg = sm[raw_gnss_socket]
+
+ # TODO: Understand and use remaining unknown constellations
+ if gnss_msg.which() == "drMeasurementReport":
+ if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']:
+ continue
+
+ if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max:
+ # gpsWeek 65535 is received rarely from quectel, this cannot be
+ # passed to GnssMeasurements's gpsWeek (Int16)
+ continue
+
+ msg = laikad.process_gnss_msg(gnss_msg, sm.logMonoTime[raw_gnss_socket], block=replay)
+ if msg is not None:
+ pm.send('gnssMeasurements', msg)
+ if not laikad.got_first_gnss_msg and sm.updated['clocks']:
+ clocks_msg = sm['clocks']
+ t = GPSTime.from_datetime(datetime.utcfromtimestamp(clocks_msg.wallTimeNanos * 1E-9))
+ if laikad.auto_fetch_orbits:
+ laikad.fetch_orbits(t, block=replay)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/selfdrive/locationd/laikad_helpers.py b/selfdrive/locationd/laikad_helpers.py
new file mode 100644
index 0000000000..f13e8e73bb
--- /dev/null
+++ b/selfdrive/locationd/laikad_helpers.py
@@ -0,0 +1,89 @@
+import numpy as np
+import sympy
+
+from laika.constants import EARTH_ROTATION_RATE, SPEED_OF_LIGHT
+from laika.helpers import ConstellationId
+
+
+def calc_pos_fix_gauss_newton(measurements, posfix_functions, x0=None, signal='C1C', min_measurements=6):
+ '''
+ Calculates gps fix using gauss newton method
+ To solve the problem a minimal of 4 measurements are required.
+ If Glonass is included 5 are required to solve for the additional free variable.
+ returns:
+ 0 -> list with positions
+ '''
+ if x0 is None:
+ x0 = [0, 0, 0, 0, 0]
+ n = len(measurements)
+ if n < min_measurements:
+ return [], []
+
+ Fx_pos = pr_residual(measurements, posfix_functions, signal=signal)
+ x = gauss_newton(Fx_pos, x0)
+ residual, _ = Fx_pos(x, weight=1.0)
+ return x.tolist(), residual.tolist()
+
+
+def pr_residual(measurements, posfix_functions, signal='C1C'):
+ def Fx_pos(inp, weight=None):
+ vals, gradients = [], []
+
+ for meas in measurements:
+ pr = meas.observables[signal]
+ pr += meas.sat_clock_err * SPEED_OF_LIGHT
+
+ w = (1 / meas.observables_std[signal]) if weight is None else weight
+
+ val, *gradient = posfix_functions[meas.constellation_id](*inp, pr, *meas.sat_pos, w)
+ vals.append(val)
+ gradients.append(gradient)
+ return np.asarray(vals), np.asarray(gradients)
+
+ return Fx_pos
+
+
+def gauss_newton(fun, b, xtol=1e-8, max_n=25):
+ for _ in range(max_n):
+ # Compute function and jacobian on current estimate
+ r, J = fun(b)
+
+ # Update estimate
+ delta = np.linalg.pinv(J) @ r
+ b -= delta
+
+ # Check step size for stopping condition
+ if np.linalg.norm(delta) < xtol:
+ break
+ return b
+
+
+def get_posfix_sympy_fun(constellation):
+ # Unknowns
+ x, y, z = sympy.Symbol('x'), sympy.Symbol('y'), sympy.Symbol('z')
+ bc = sympy.Symbol('bc')
+ bg = sympy.Symbol('bg')
+ var = [x, y, z, bc, bg]
+
+ # Knowns
+ pr = sympy.Symbol('pr')
+ sat_x, sat_y, sat_z = sympy.Symbol('sat_x'), sympy.Symbol('sat_y'), sympy.Symbol('sat_z')
+ weight = sympy.Symbol('weight')
+
+ theta = EARTH_ROTATION_RATE * (pr - bc) / SPEED_OF_LIGHT
+ val = sympy.sqrt(
+ (sat_x * sympy.cos(theta) + sat_y * sympy.sin(theta) - x) ** 2 +
+ (sat_y * sympy.cos(theta) - sat_x * sympy.sin(theta) - y) ** 2 +
+ (sat_z - z) ** 2
+ )
+
+ if constellation == ConstellationId.GLONASS:
+ res = weight * (val - (pr - bc - bg))
+ elif constellation == ConstellationId.GPS:
+ res = weight * (val - (pr - bc))
+ else:
+ raise NotImplementedError(f"Constellation {constellation} not supported")
+
+ res = [res] + [sympy.diff(res, v) for v in var]
+
+ return sympy.lambdify([x, y, z, bc, bg, pr, sat_x, sat_y, sat_z, weight], res, modules=["numpy"])
diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc
index 250d90b382..da57fb7ff4 100755
--- a/selfdrive/locationd/liblocationd.cc
+++ b/selfdrive/locationd/liblocationd.cc
@@ -7,11 +7,10 @@ extern "C" {
return new Localizer();
}
- void localizer_get_message_bytes(Localizer *localizer, uint64_t logMonoTime,
- bool inputsOK, bool sensorsOK, bool gpsOK, char *buff, size_t buff_size)
- {
+ void localizer_get_message_bytes(Localizer *localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid,
+ char *buff, size_t buff_size) {
MessageBuilder msg_builder;
- kj::ArrayPtr arr = localizer->get_message_bytes(msg_builder, logMonoTime, inputsOK, sensorsOK, gpsOK).asChars();
+ kj::ArrayPtr arr = localizer->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, msgValid).asChars();
assert(buff_size >= arr.size());
memcpy(buff, arr.begin(), arr.size());
}
@@ -27,4 +26,16 @@ extern "C" {
memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size());
}
+ bool is_gps_ok(Localizer *localizer){
+ return localizer->is_gps_ok();
+ }
+
+ bool are_inputs_ok(Localizer *localizer){
+ return localizer->are_inputs_ok();
+ }
+
+ void observation_timings_invalid_reset(Localizer *localizer){
+ localizer->observation_timings_invalid_reset();
+ }
+
}
diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc
index b723878e9d..4325900c0e 100755
--- a/selfdrive/locationd/locationd.cc
+++ b/selfdrive/locationd/locationd.cc
@@ -19,6 +19,14 @@ const double VALID_TIME_SINCE_RESET = 1.0; // s
const double VALID_POS_STD = 50.0; // m
const double MAX_RESET_TRACKER = 5.0;
const double SANE_GPS_UNCERTAINTY = 1500.0; // m
+const double INPUT_INVALID_THRESHOLD = 5.0; // same as reset tracker
+const double DECAY = 0.99995; // same as reset tracker
+const double MAX_FILTER_REWIND_TIME = 0.8; // s
+
+// TODO: GPS sensor time offsets are empirically calculated
+// They should be replaced with synced time from a real clock
+const double GPS_LOCATION_SENSOR_TIME_OFFSET = 0.630; // s
+const double GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET = 0.095; // s
static VectorXd floatlist2vector(const capnp::List