Merge branch 'master' into mqb-long

mqb-long
Jason Young 3 years ago
commit 11faf8696f
  1. 2
      cereal
  2. 19
      common/params.cc
  3. 2
      common/params.h
  4. 5
      common/params_pyx.pyx
  5. 8
      common/tests/test_params.py
  6. 114
      docs/CARS.md
  7. 8
      selfdrive/boardd/boardd.cc
  8. 2
      selfdrive/car/chrysler/values.py
  9. 2
      selfdrive/car/gm/values.py
  10. 2
      selfdrive/car/hyundai/values.py
  11. 3
      selfdrive/car/subaru/carstate.py
  12. 2
      selfdrive/car/tests/routes.py
  13. 2
      selfdrive/car/tests/test_models.py
  14. 2
      selfdrive/car/torque_data/override.yaml
  15. 1
      selfdrive/car/volkswagen/interface.py
  16. 111
      selfdrive/car/volkswagen/values.py
  17. 2
      selfdrive/manager/test/test_manager.py
  18. 4
      selfdrive/sensord/tests/test_sensord.py
  19. 2
      selfdrive/test/process_replay/ref_commit
  20. 2
      selfdrive/ui/.gitignore
  21. 3
      selfdrive/ui/SConscript
  22. 81
      selfdrive/ui/qt/offroad/settings.cc
  23. 13
      selfdrive/ui/qt/offroad/settings.h
  24. 156
      selfdrive/ui/qt/offroad/software_settings.cc
  25. 8
      selfdrive/ui/qt/widgets/controls.cc
  26. 14
      selfdrive/ui/qt/widgets/controls.h
  27. 2
      selfdrive/ui/qt/widgets/offroad_alerts.cc
  28. 8
      selfdrive/ui/qt/widgets/ssh_keys.cc
  29. 2
      selfdrive/ui/qt/widgets/ssh_keys.h
  30. 2
      selfdrive/ui/tests/cycle_offroad_alerts.py
  31. 91
      selfdrive/ui/translations/main_ja.ts
  32. 91
      selfdrive/ui/translations/main_ko.ts
  33. 91
      selfdrive/ui/translations/main_pt-BR.ts
  34. 91
      selfdrive/ui/translations/main_zh-CHS.ts
  35. 91
      selfdrive/ui/translations/main_zh-CHT.ts
  36. 308
      selfdrive/updated.py
  37. 61
      system/camerad/cameras/camera_qcom2.cc
  38. 4
      system/camerad/cameras/camera_qcom2.h
  39. 6
      system/camerad/cameras/sensor2_i2c.h

@ -1 +1 @@
Subproject commit bd2f7fa56706bcec3c9906bd57c2ec46f0666ac5 Subproject commit 513dfc7ee001243cd68a57a9d92fe3170fc49c7d

@ -3,6 +3,7 @@
#include <dirent.h> #include <dirent.h>
#include <sys/file.h> #include <sys/file.h>
#include <algorithm>
#include <csignal> #include <csignal>
#include <unordered_map> #include <unordered_map>
@ -154,18 +155,24 @@ std::unordered_map<std::string, uint32_t> keys = {
{"PrimeType", PERSISTENT}, {"PrimeType", PERSISTENT},
{"RecordFront", PERSISTENT}, {"RecordFront", PERSISTENT},
{"RecordFrontLock", PERSISTENT}, // for the internal fleet {"RecordFrontLock", PERSISTENT}, // for the internal fleet
{"ReleaseNotes", PERSISTENT},
{"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
{"ShouldDoUpdate", CLEAR_ON_MANAGER_START}, {"ShouldDoUpdate", CLEAR_ON_MANAGER_START},
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
{"SshEnabled", PERSISTENT}, {"SshEnabled", PERSISTENT},
{"SubscriberInfo", PERSISTENT}, {"SubscriberInfo", PERSISTENT},
{"SwitchToBranch", CLEAR_ON_MANAGER_START},
{"TermsVersion", PERSISTENT}, {"TermsVersion", PERSISTENT},
{"Timezone", PERSISTENT}, {"Timezone", PERSISTENT},
{"TrainingVersion", PERSISTENT}, {"TrainingVersion", PERSISTENT},
{"UpdateAvailable", CLEAR_ON_MANAGER_START}, {"UpdateAvailable", CLEAR_ON_MANAGER_START},
{"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START},
{"UpdaterState", CLEAR_ON_MANAGER_START},
{"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START},
{"UpdaterTargetBranch", CLEAR_ON_MANAGER_START},
{"UpdaterAvailableBranches", CLEAR_ON_MANAGER_START},
{"UpdaterCurrentDescription", CLEAR_ON_MANAGER_START},
{"UpdaterCurrentReleaseNotes", CLEAR_ON_MANAGER_START},
{"UpdaterNewDescription", CLEAR_ON_MANAGER_START},
{"UpdaterNewReleaseNotes", CLEAR_ON_MANAGER_START},
{"Version", PERSISTENT}, {"Version", PERSISTENT},
{"VisionRadarToggle", PERSISTENT}, {"VisionRadarToggle", PERSISTENT},
{"WideCameraOnly", PERSISTENT}, {"WideCameraOnly", PERSISTENT},
@ -197,6 +204,14 @@ Params::Params(const std::string &path) {
params_path = path.empty() ? default_param_path : ensure_params_path(prefix, path); params_path = path.empty() ? default_param_path : ensure_params_path(prefix, path);
} }
std::vector<std::string> Params::allKeys() const {
std::vector<std::string> ret;
for (auto &p : keys) {
ret.push_back(p.first);
}
return ret;
}
bool Params::checkKey(const std::string &key) { bool Params::checkKey(const std::string &key) {
return keys.find(key) != keys.end(); return keys.find(key) != keys.end();
} }

@ -2,6 +2,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <vector>
enum ParamKeyType { enum ParamKeyType {
PERSISTENT = 0x02, PERSISTENT = 0x02,
@ -15,6 +16,7 @@ enum ParamKeyType {
class Params { class Params {
public: public:
Params(const std::string &path = {}); Params(const std::string &path = {});
std::vector<std::string> allKeys() const;
bool checkKey(const std::string &key); bool checkKey(const std::string &key);
ParamKeyType getKeyType(const std::string &key); ParamKeyType getKeyType(const std::string &key);
inline std::string getParamPath(const std::string &key = {}) { inline std::string getParamPath(const std::string &key = {}) {

@ -2,6 +2,7 @@
# cython: language_level = 3 # cython: language_level = 3
from libcpp cimport bool from libcpp cimport bool
from libcpp.string cimport string from libcpp.string cimport string
from libcpp.vector cimport vector
import threading import threading
cdef extern from "common/params.h": cdef extern from "common/params.h":
@ -22,6 +23,7 @@ cdef extern from "common/params.h":
bool checkKey(string) nogil bool checkKey(string) nogil
string getParamPath(string) nogil string getParamPath(string) nogil
void clearAll(ParamKeyType) void clearAll(ParamKeyType)
vector[string] allKeys()
def ensure_bytes(v): def ensure_bytes(v):
@ -99,6 +101,9 @@ cdef class Params:
cdef string key_bytes = ensure_bytes(key) cdef string key_bytes = ensure_bytes(key)
return self.p.getParamPath(key_bytes).decode("utf-8") return self.p.getParamPath(key_bytes).decode("utf-8")
def all_keys(self):
return self.p.allKeys()
def put_nonblocking(key, val, d=""): def put_nonblocking(key, val, d=""):
threading.Thread(target=lambda: Params(d).put(key, val)).start() threading.Thread(target=lambda: Params(d).put(key, val)).start()

@ -98,6 +98,14 @@ class TestParams(unittest.TestCase):
assert q.get("CarParams") is None assert q.get("CarParams") is None
assert q.get("CarParams", True) == b"1" assert q.get("CarParams", True) == b"1"
def test_params_all_keys(self):
keys = Params().all_keys()
# sanity checks
assert len(keys) > 20
assert len(keys) == len(set(keys))
assert b"CarParams" in keys
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -11,27 +11,27 @@ A supported vehicle is one that just works when you install a comma three. All s
|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Acura|RDX 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Acura|RDX 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Audi|A3 2014-19|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Audi|A3 Sportback e-tron 2017-18|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Audi|Q2 2018|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Audi|Q3 2020-21|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|Q3 2020-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Audi|RS3 2018|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Audi|S3 2015-17|ACC + Lane Assist|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Chevrolet|Volt 2017-18[<sup>1</sup>](#footnotes)|Adaptive Cruise Control|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chevrolet|Volt 2017-18[<sup>1</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|
|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G70 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G70 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|GMC|Acadia 2018[<sup>1</sup>](#footnotes)|Adaptive Cruise Control|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |GMC|Acadia 2018[<sup>1</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
@ -80,8 +80,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Kia|Ceed 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|Ceed 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Kia|EV6 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| |Kia|EV6 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|
|Kia|Forte 2018|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Kia|Forte 2018|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|
@ -122,9 +122,9 @@ A supported vehicle is one that just works when you install a comma three. All s
|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Ram|1500 2019-22|Adaptive Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram| |Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|
|SEAT|Ateca 2018|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|SEAT|Leon 2014-20|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
@ -135,13 +135,13 @@ A supported vehicle is one that just works when you install a comma three. All s
|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| |Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|
|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Škoda|Kamiq 2021[<sup>5</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Kamiq 2021[<sup>5</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Karoq 2019-21|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Karoq 2019-21[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Kodiaq 2018-19|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Octavia 2015, 2018-19|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Octavia RS 2016|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Scala 2020|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Škoda|Superb 2015-18|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
@ -182,37 +182,37 @@ A supported vehicle is one that just works when you install a comma three. All s
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Sienna 2018-20|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Sienna 2018-20|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Volkswagen|Arteon 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon 2018-22[<sup>7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Arteon eHybrid 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon eHybrid 2020-22[<sup>7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Arteon R 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon R 2020-22[<sup>7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Atlas 2018-23|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Atlas 2018-23[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Atlas Cross Sport 2021-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Atlas Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|California 2021|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|California 2021[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Caravelle 2020|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Caravelle 2020[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|CC 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|CC 2018-22[<sup>7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|e-Golf 2014-20|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf 2015-20[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf 2015-20[<sup>8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf Alltrack 2015-19|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf GTD 2015-20|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf GTE 2015-20|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf GTI 2015-21|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf R 2015-19[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf R 2015-19[<sup>8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Golf SportsVan 2015-20|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
|Volkswagen|Jetta 2018-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Jetta 2018-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Jetta GLI 2021-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Jetta GLI 2021-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat 2015-22[<sup>6,7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat 2015-22[<sup>6,7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat Alltrack 2015-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat Alltrack 2015-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat GTE 2015-22[<sup>7</sup>](#footnotes)|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat GTE 2015-22[<sup>7,8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Polo 2020-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Polo 2020-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Polo GTI 2020-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Polo GTI 2020-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|T-Cross 2021|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|T-Cross 2021[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|T-Roc 2021|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|T-Roc 2021[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Taos 2022|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Taos 2022[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont 2018-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont 2018-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont Cross Sport 2021-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont X 2021-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont X 2021-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Tiguan 2019-22|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Tiguan 2019-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Touran 2017|Driver Assistance|openpilot[<sup>8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW|
<a id="footnotes"></a> <a id="footnotes"></a>
<sup>1</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br /> <sup>1</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br />
@ -221,8 +221,8 @@ A supported vehicle is one that just works when you install a comma three. All s
<sup>4</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br /> <sup>4</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>5</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br /> <sup>5</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
<sup>6</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br /> <sup>6</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
<sup>7</sup>Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.) <br /> <sup>7</sup>Model-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). In the interim, if your car has a J533 connector CAN gateway inside the dashboard, choose "VW J533 Development" from the vehicle drop-down for a suitable harness. (Some newer models are also observed to not have a J533 connector.) <br />
<sup>8</sup>Requires gateway (J533) harness. Camera harness integrations can only use stock ACC at this time. <br /> <sup>8</sup>Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.) <br />
## Community Maintained Cars ## 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/). 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/).

@ -346,14 +346,14 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
auto ps = pss[i]; auto ps = pss[i];
ps.setUptime(health.uptime_pkt); 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.setIgnitionLine(health.ignition_line_pkt);
ps.setIgnitionCan(health.ignition_can_pkt); ps.setIgnitionCan(health.ignition_can_pkt);
ps.setControlsAllowed(health.controls_allowed_pkt); ps.setControlsAllowed(health.controls_allowed_pkt);
ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt); ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt);
ps.setCanRxErrs(health.can_rx_errs_pkt); ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
ps.setCanSendErrs(health.can_send_errs_pkt); ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
ps.setCanFwdErrs(health.can_fwd_errs_pkt);
ps.setGmlanSendErrs(health.gmlan_send_errs_pkt); ps.setGmlanSendErrs(health.gmlan_send_errs_pkt);
ps.setPandaType(panda->hw_type); ps.setPandaType(panda->hw_type);
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt)); ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt));

@ -52,7 +52,7 @@ RAM_CARS = RAM_DT | RAM_HD
@dataclass @dataclass
class ChryslerCarInfo(CarInfo): class ChryslerCarInfo(CarInfo):
package: str = "Adaptive Cruise Control" package: str = "Adaptive Cruise Control (ACC)"
harness: Enum = Harness.fca harness: Enum = Harness.fca
CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {

@ -71,7 +71,7 @@ class Footnote(Enum):
@dataclass @dataclass
class GMCarInfo(CarInfo): class GMCarInfo(CarInfo):
package: str = "Adaptive Cruise Control" package: str = "Adaptive Cruise Control (ACC)"
harness: Enum = Harness.obd_ii harness: Enum = Harness.obd_ii
footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II])

@ -29,6 +29,8 @@ class CarControllerParams:
self.STEER_DRIVER_ALLOWANCE = 250 self.STEER_DRIVER_ALLOWANCE = 250
self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 250 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. # 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 the max stock LKAS request is <384, add your car to this list.

@ -32,9 +32,8 @@ class CarState(CarStateBase):
cp_wheels.vl["Wheel_Speeds"]["RR"], cp_wheels.vl["Wheel_Speeds"]["RR"],
) )
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. 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.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 # continuous blinker signals for assisted lane change
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"], ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"],

@ -194,7 +194,7 @@ routes = [
CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER),
CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA),
CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020),
CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=3), CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10),
CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3), CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3),
# Pre-global, dashcam # Pre-global, dashcam
CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL),

@ -234,7 +234,7 @@ class TestCarModelBase(unittest.TestCase):
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available
if self.CP.carName not in ("hyundai", "volkswagen", "subaru", "gm", "body"): if self.CP.carName not in ("hyundai", "volkswagen", "gm", "body"):
# TODO: fix standstill mismatches for other makes # TODO: fix standstill mismatches for other makes
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()

@ -21,7 +21,7 @@ FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
COMMA BODY: [.nan, 1000, .nan] COMMA BODY: [.nan, 1000, .nan]
# Totally new cars # Totally new cars
KIA EV6 2022: [3.5, 2.5, 0.0] KIA EV6 2022: [3.5, 3.0, 0.0]
RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
RAM HD 5TH GEN: [1.4, 1.4, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0]
SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11]

@ -90,6 +90,7 @@ class CarInterface(CarInterfaceBase):
ret.pcmCruise = not ret.openpilotLongitudinalControl ret.pcmCruise = not ret.openpilotLongitudinalControl
ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway
ret.experimentalLongitudinalAvailable &= bool(fingerprint[0]) # FIXME: hack to deal with CI/doc/harness stuff later
ret.stoppingControl = True ret.stoppingControl = True
ret.startingState = True ret.startingState = True
ret.startAccel = 1.0 ret.startAccel = 1.0

@ -150,88 +150,93 @@ class Footnote(Enum):
PASSAT = CarFootnote( PASSAT = CarFootnote(
"Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.", "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.",
Column.MODEL) Column.MODEL)
VW_HARNESS = CarFootnote(
"Model-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). In the interim, if your car has a J533 connector CAN gateway " +
"inside the dashboard, choose \"VW J533 Development\" from the vehicle drop-down for a suitable harness. " +
"(Some newer models are also observed to not have a J533 connector.)",
Column.MODEL)
VW_VARIANT = CarFootnote( VW_VARIANT = CarFootnote(
"Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)", "Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)",
Column.MODEL) Column.MODEL)
VW_LONG = CarFootnote(
"Requires gateway (J533) harness. Camera harness integrations can only use stock ACC at this time.",
Column.LONGITUDINAL)
@dataclass @dataclass
class VWCarInfo(CarInfo): class VWCarInfo(CarInfo):
package: str = "Driver Assistance" package: str = "Adaptive Cruise Control (ACC) & Lane Assist"
harness: Enum = Harness.j533 harness: Enum = Harness.vw
CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.ARTEON_MK1: [ CAR.ARTEON_MK1: [
VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT], video_link="https://youtu.be/FAomFKPFlDA"), VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"),
VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT], video_link="https://youtu.be/FAomFKPFlDA"), VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"),
VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT], video_link="https://youtu.be/FAomFKPFlDA"), VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"),
VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT], video_link="https://youtu.be/FAomFKPFlDA"), VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"),
], ],
CAR.ATLAS_MK1: [ CAR.ATLAS_MK1: [
VWCarInfo("Volkswagen Atlas 2018-23", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Atlas 2018-23", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
], ],
CAR.GOLF_MK7: [ CAR.GOLF_MK7: [
VWCarInfo("Volkswagen e-Golf 2014-20", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen e-Golf 2014-20"),
VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT]), VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf Alltrack 2015-19", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Golf Alltrack 2015-19"),
VWCarInfo("Volkswagen Golf GTD 2015-20", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Golf GTD 2015-20"),
VWCarInfo("Volkswagen Golf GTE 2015-20", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Golf GTE 2015-20"),
VWCarInfo("Volkswagen Golf GTI 2015-21", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Golf GTI 2015-21"),
VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT]), VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf SportsVan 2015-20", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Golf SportsVan 2015-20"),
], ],
CAR.JETTA_MK7: [ CAR.JETTA_MK7: [
VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
], ],
CAR.PASSAT_MK8: [ CAR.PASSAT_MK8: [
VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_LONG, Footnote.PASSAT, Footnote.VW_VARIANT]), VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.PASSAT, Footnote.VW_VARIANT], harness=Harness.j533),
VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_LONG, Footnote.VW_VARIANT]), VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
], ],
CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22", footnotes=[Footnote.VW_LONG]), CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22", harness=Harness.j533),
CAR.POLO_MK6: [ CAR.POLO_MK6: [
VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
], ],
CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_LONG]), CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_LONG]), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_LONG]), CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017", footnotes=[Footnote.VW_LONG]), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"),
CAR.TRANSPORTER_T61: [ CAR.TRANSPORTER_T61: [
VWCarInfo("Volkswagen Caravelle 2020", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen Caravelle 2020", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen California 2021", footnotes=[Footnote.VW_LONG]), VWCarInfo("Volkswagen California 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
], ],
CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_LONG]), CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.AUDI_A3_MK3: [ CAR.AUDI_A3_MK3: [
VWCarInfo("Audi A3 2014-19", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), VWCarInfo("Audi A3 2014-19"),
VWCarInfo("Audi A3 Sportback e-tron 2017-18", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), VWCarInfo("Audi A3 Sportback e-tron 2017-18"),
VWCarInfo("Audi RS3 2018", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), VWCarInfo("Audi RS3 2018"),
VWCarInfo("Audi S3 2015-17", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), VWCarInfo("Audi S3 2015-17"),
], ],
CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018"),
CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2020-21", "ACC + Lane Assist", footnotes=[Footnote.VW_LONG]), CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2020-21"),
CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018", footnotes=[Footnote.VW_LONG]), CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"),
CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20", footnotes=[Footnote.VW_LONG]), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"),
CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_LONG, Footnote.KAMIQ]), CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.KAMIQ]),
CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21", footnotes=[Footnote.VW_LONG]), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21", footnotes=[Footnote.VW_HARNESS]),
CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19", footnotes=[Footnote.VW_LONG]), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"),
CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_LONG]), CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020"),
CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18", footnotes=[Footnote.VW_LONG]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"),
CAR.SKODA_OCTAVIA_MK3: [ CAR.SKODA_OCTAVIA_MK3: [
VWCarInfo("Škoda Octavia 2015, 2018-19", footnotes=[Footnote.VW_LONG]), VWCarInfo("Škoda Octavia 2015, 2018-19"),
VWCarInfo("Škoda Octavia RS 2016", footnotes=[Footnote.VW_LONG]), VWCarInfo("Škoda Octavia RS 2016"),
], ],
} }
# All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars # 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. # with a manual trans won't return transmission firmware, but all other cars will.
# #

@ -12,7 +12,7 @@ from system.hardware import HARDWARE
os.environ['FAKEUPLOAD'] = "1" os.environ['FAKEUPLOAD'] = "1"
MAX_STARTUP_TIME = 3 MAX_STARTUP_TIME = 3
ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not DaemonProcess) and p.enabled and (p.name not in ['updated', 'pandad'])] ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not DaemonProcess) and p.enabled and (p.name not in ['pandad', ])]
class TestManager(unittest.TestCase): class TestManager(unittest.TestCase):

@ -121,6 +121,10 @@ class TestSensord(unittest.TestCase):
cls.events = read_sensor_events(5) cls.events = read_sensor_events(5)
managed_processes["sensord"].stop() managed_processes["sensord"].stop()
def tearDown(self):
# interrupt check might leave sensord running
managed_processes["sensord"].stop()
def test_sensors_present(self): def test_sensors_present(self):
# verify correct sensors configuration # verify correct sensors configuration

@ -1 +1 @@
3ad478bf44f50815d05acc5b12ff2f01a6cb42ff ef5395e5f36550d2b485216eee5406bf6062e9c9

@ -1,6 +1,8 @@
moc_* moc_*
*.moc *.moc
translations/main_test_en.*
_mui _mui
watch3 watch3
installer/installers/* installer/installers/*

@ -56,7 +56,8 @@ qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs)
# build main UI # build main UI
qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc",
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
"qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"] "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc",
"qt/offroad/driverview.cc"]
qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs)
if GetOption('test'): if GetOption('test'):
qt_src.remove("main.cc") # replaced by test_runner qt_src.remove("main.cc") # replaced by test_runner

@ -159,7 +159,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(dcamBtn); addItem(dcamBtn);
auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() { connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) {
params.remove("CalibrationParams"); params.remove("CalibrationParams");
@ -282,85 +282,6 @@ void DevicePanel::poweroff() {
} }
} }
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
gitBranchLbl = new LabelControl(tr("Git Branch"));
gitCommitLbl = new LabelControl(tr("Git Commit"));
osVersionLbl = new LabelControl(tr("OS Version"));
versionLbl = new LabelControl(tr("Version"), "", QString::fromStdString(params.get("ReleaseNotes")).trimmed());
lastUpdateLbl = new LabelControl(tr("Last Update Check"), "", tr("The last time openpilot successfully checked for an update. The updater only runs while the car is off."));
updateBtn = new ButtonControl(tr("Check for Update"), "");
connect(updateBtn, &ButtonControl::clicked, [=]() {
if (params.getBool("IsOffroad")) {
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
updateBtn->setText(tr("CHECKING"));
updateBtn->setEnabled(false);
}
std::system("pkill -1 -f selfdrive.updated");
});
connect(uiState(), &UIState::offroadTransition, updateBtn, &QPushButton::setEnabled);
branchSwitcherBtn = new ButtonControl(tr("Switch Branch"), tr("ENTER"), tr("The new branch will be pulled the next time the updater runs."));
connect(branchSwitcherBtn, &ButtonControl::clicked, [=]() {
QString branch = InputDialog::getText(tr("Enter branch name"), this, tr("The new branch will be pulled the next time the updater runs."),
false, -1, QString::fromStdString(params.get("SwitchToBranch")));
if (branch.isEmpty()) {
params.remove("SwitchToBranch");
} else {
params.put("SwitchToBranch", branch.toStdString());
}
std::system("pkill -1 -f selfdrive.updated");
});
connect(uiState(), &UIState::offroadTransition, branchSwitcherBtn, &QPushButton::setEnabled);
auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true);
}
});
connect(uiState(), &UIState::offroadTransition, uninstallBtn, &QPushButton::setEnabled);
QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, branchSwitcherBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn};
for (QWidget* w : widgets) {
if (w == branchSwitcherBtn && params.getBool("IsTestedBranch")) {
continue;
}
addItem(w);
}
fs_watch = new QFileSystemWatcher(this);
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
if (path.contains("UpdateFailedCount") && std::atoi(params.get("UpdateFailedCount").c_str()) > 0) {
lastUpdateLbl->setText(tr("failed to fetch update"));
updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true);
} else if (path.contains("LastUpdateTime")) {
updateLabels();
}
});
}
void SoftwarePanel::showEvent(QShowEvent *event) {
updateLabels();
}
void SoftwarePanel::updateLabels() {
QString lastUpdate = "";
auto tm = params.get("LastUpdateTime");
if (!tm.empty()) {
lastUpdate = timeAgo(QDateTime::fromString(QString::fromStdString(tm + "Z"), Qt::ISODate));
}
versionLbl->setText(getBrandVersion());
lastUpdateLbl->setText(lastUpdate);
updateBtn->setText(tr("CHECK"));
updateBtn->setEnabled(true);
gitBranchLbl->setText(QString::fromStdString(params.get("GitBranch")));
gitCommitLbl->setText(QString::fromStdString(params.get("GitCommit")).left(10));
osVersionLbl->setText(QString::fromStdString(Hardware::get_os_version()).trimmed());
}
void SettingsWindow::showEvent(QShowEvent *event) { void SettingsWindow::showEvent(QShowEvent *event) {
panel_widget->setCurrentIndex(0); panel_widget->setCurrentIndex(0);
nav_btns->buttons()[0]->setChecked(true); nav_btns->buttons()[0]->setChecked(true);

@ -71,14 +71,15 @@ public:
private: private:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void updateLabels(); void updateLabels();
void checkForUpdates();
LabelControl *gitBranchLbl; bool is_onroad = false;
LabelControl *gitCommitLbl;
LabelControl *osVersionLbl; QLabel *onroadLbl;
LabelControl *versionLbl; LabelControl *versionLbl;
LabelControl *lastUpdateLbl; ButtonControl *installBtn;
ButtonControl *updateBtn; ButtonControl *downloadBtn;
ButtonControl *branchSwitcherBtn; ButtonControl *targetBranchBtn;
Params params; Params params;
QFileSystemWatcher *fs_watch; QFileSystemWatcher *fs_watch;

@ -0,0 +1,156 @@
#include "selfdrive/ui/qt/offroad/settings.h"
#include <cassert>
#include <cmath>
#include <string>
#include <QDebug>
#include <QLabel>
#include "common/params.h"
#include "common/util.h"
#include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "selfdrive/ui/qt/widgets/input.h"
#include "system/hardware/hw.h"
void SoftwarePanel::checkForUpdates() {
std::system("pkill -SIGUSR1 -f selfdrive.updated");
}
SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off."));
onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;");
addItem(onroadLbl);
// current version
versionLbl = new LabelControl(tr("Current Version"), "");
addItem(versionLbl);
// download update btn
downloadBtn = new ButtonControl(tr("Download"), tr("CHECK"));
connect(downloadBtn, &ButtonControl::clicked, [=]() {
downloadBtn->setEnabled(false);
if (downloadBtn->text() == tr("CHECK")) {
checkForUpdates();
} else {
std::system("pkill -SIGHUP -f selfdrive.updated");
}
});
addItem(downloadBtn);
// install update btn
installBtn = new ButtonControl(tr("Install Update"), tr("INSTALL"));
connect(installBtn, &ButtonControl::clicked, [=]() {
installBtn->setEnabled(false);
params.putBool("DoShutdown", true);
});
addItem(installBtn);
// branch selecting
targetBranchBtn = new ButtonControl(tr("Target Branch"), tr("SELECT"));
connect(targetBranchBtn, &ButtonControl::clicked, [=]() {
auto current = params.get("GitBranch");
QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
for (QString b : {current.c_str(), "devel-staging", "devel", "master-ci", "master"}) {
auto i = branches.indexOf(b);
if (i >= 0) {
branches.removeAt(i);
branches.insert(0, b);
}
}
QString cur = QString::fromStdString(params.get("UpdaterTargetBranch"));
QString selection = MultiOptionDialog::getSelection(tr("Select a branch"), branches, cur, this);
if (!selection.isEmpty()) {
params.put("UpdaterTargetBranch", selection.toStdString());
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
checkForUpdates();
}
});
if (!params.getBool("IsTestedBranch")) {
addItem(targetBranchBtn);
}
// uninstall button
auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true);
}
});
addItem(uninstallBtn);
fs_watch = new QFileSystemWatcher(this);
QObject::connect(fs_watch, &QFileSystemWatcher::fileChanged, [=](const QString path) {
updateLabels();
});
connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
is_onroad = !offroad;
updateLabels();
});
updateLabels();
}
void SoftwarePanel::showEvent(QShowEvent *event) {
// nice for testing on PC
installBtn->setEnabled(true);
updateLabels();
}
void SoftwarePanel::updateLabels() {
// add these back in case the files got removed
fs_watch->addPath(QString::fromStdString(params.getParamPath("LastUpdateTime")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateFailedCount")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdaterState")));
fs_watch->addPath(QString::fromStdString(params.getParamPath("UpdateAvailable")));
if (!isVisible()) {
return;
}
// updater only runs offroad
onroadLbl->setVisible(is_onroad);
downloadBtn->setVisible(!is_onroad);
// download update
QString updater_state = QString::fromStdString(params.get("UpdaterState"));
bool failed = std::atoi(params.get("UpdateFailedCount").c_str()) > 0;
if (updater_state != "idle") {
downloadBtn->setEnabled(false);
downloadBtn->setValue(updater_state);
} else {
if (failed) {
downloadBtn->setText("CHECK");
downloadBtn->setValue("failed to check for update");
} else if (params.getBool("UpdaterFetchAvailable")) {
downloadBtn->setText("DOWNLOAD");
downloadBtn->setValue("update available");
} else {
QString lastUpdate = "never";
auto tm = params.get("LastUpdateTime");
if (!tm.empty()) {
lastUpdate = timeAgo(QDateTime::fromString(QString::fromStdString(tm + "Z"), Qt::ISODate));
}
downloadBtn->setText("CHECK");
downloadBtn->setValue("up to date, last checked " + lastUpdate);
}
downloadBtn->setEnabled(true);
}
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
// current + new versions
versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription")).left(40));
versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes")));
installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable"));
installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription")).left(35));
installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes")));
update();
}

@ -42,6 +42,12 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left");
hlayout->addWidget(title_label); hlayout->addWidget(title_label);
// value next to control button
value = new QLabel();
value->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
value->setStyleSheet("color: #aaaaaa");
hlayout->addWidget(value);
main_layout->addLayout(hlayout); main_layout->addLayout(hlayout);
// description // description
@ -54,7 +60,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons
connect(title_label, &QPushButton::clicked, [=]() { connect(title_label, &QPushButton::clicked, [=]() {
if (!description->isVisible()) { if (!description->isVisible()) {
emit showDescription(); emit showDescriptionEvent();
} }
if (!description->text().isEmpty()) { if (!description->text().isEmpty()) {

@ -45,8 +45,17 @@ public:
title_label->setText(title); title_label->setText(title);
} }
void setValue(const QString &val) {
value->setText(val);
}
public slots:
void showDescription() {
description->setVisible(true);
};
signals: signals:
void showDescription(); void showDescriptionEvent();
protected: protected:
AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
@ -54,6 +63,9 @@ protected:
QHBoxLayout *hlayout; QHBoxLayout *hlayout;
QPushButton *title_label; QPushButton *title_label;
private:
QLabel *value;
QLabel *description = nullptr; QLabel *description = nullptr;
}; };

@ -112,7 +112,7 @@ UpdateAlert::UpdateAlert(QWidget *parent) : AbstractAlert(true, parent) {
bool UpdateAlert::refresh() { bool UpdateAlert::refresh() {
bool updateAvailable = params.getBool("UpdateAvailable"); bool updateAvailable = params.getBool("UpdateAvailable");
if (updateAvailable) { if (updateAvailable) {
releaseNotes->setText(params.get("ReleaseNotes").c_str()); releaseNotes->setText(params.get("UpdaterNewReleaseNotes").c_str());
} }
return updateAvailable; return updateAvailable;
} }

@ -5,10 +5,6 @@
#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/input.h"
SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.")) { SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username.")) {
username_label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
username_label.setStyleSheet("color: #aaaaaa");
hlayout->insertWidget(1, &username_label);
QObject::connect(this, &ButtonControl::clicked, [=]() { QObject::connect(this, &ButtonControl::clicked, [=]() {
if (text() == tr("ADD")) { if (text() == tr("ADD")) {
QString username = InputDialog::getText(tr("Enter your GitHub username"), this); QString username = InputDialog::getText(tr("Enter your GitHub username"), this);
@ -30,10 +26,10 @@ SshControl::SshControl() : ButtonControl(tr("SSH Keys"), "", tr("Warning: This g
void SshControl::refresh() { void SshControl::refresh() {
QString param = QString::fromStdString(params.get("GithubSshKeys")); QString param = QString::fromStdString(params.get("GithubSshKeys"));
if (param.length()) { if (param.length()) {
username_label.setText(QString::fromStdString(params.get("GithubUsername"))); setValue(QString::fromStdString(params.get("GithubUsername")));
setText(tr("REMOVE")); setText(tr("REMOVE"));
} else { } else {
username_label.setText(""); setValue("");
setText(tr("ADD")); setText(tr("ADD"));
} }
setEnabled(true); setEnabled(true);

@ -27,8 +27,6 @@ public:
private: private:
Params params; Params params;
QLabel username_label;
void refresh(); void refresh();
void getUserKeys(const QString &username); void getUserKeys(const QString &username);
}; };

@ -20,7 +20,7 @@ if __name__ == "__main__":
params.put_bool("UpdateAvailable", True) params.put_bool("UpdateAvailable", True)
r = open(os.path.join(BASEDIR, "RELEASES.md")).read() r = open(os.path.join(BASEDIR, "RELEASES.md")).read()
r = r[:r.find('\n\n')] # Slice latest release notes r = r[:r.find('\n\n')] # Slice latest release notes
params.put("ReleaseNotes", r + "\n") params.put("UpdaterNewReleaseNotes", r + "\n")
time.sleep(t) time.sleep(t)
params.put_bool("UpdateAvailable", False) params.put_bool("UpdateAvailable", False)

@ -193,7 +193,7 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+3"/>
<source>Select a language</source> <source>Select a language</source>
<translation></translation> <translation></translation>
</message> </message>
@ -418,7 +418,7 @@ prime subscription. Sign up now: https://connect.comma.ai</source>
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location line="+57"/> <location line="+58"/>
<source>No home <source>No home
location set</source> location set</source>
<translation> <translation>
@ -432,7 +432,7 @@ location set</source>
</translation> </translation>
</message> </message>
<message> <message>
<location line="+113"/> <location line="+120"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation></translation> <translation></translation>
</message> </message>
@ -718,7 +718,7 @@ location set</source>
<context> <context>
<name>SettingsWindow</name> <name>SettingsWindow</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="+101"/> <location filename="../qt/offroad/settings.cc" line="+22"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
@ -983,68 +983,47 @@ location set</source>
<context> <context>
<name>SoftwarePanel</name> <name>SoftwarePanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-130"/> <location filename="../qt/offroad/software_settings.cc" line="+24"/>
<source>Git Branch</source> <source>Updates are only downloaded while the car is off.</source>
<translation>Git </translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+5"/>
<source>Git Commit</source> <source>Current Version</source>
<translation>Git </translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>OS Version</source>
<translation>OS </translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+4"/>
<source>Version</source> <source>Download</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+12"/>
<source>Last Update Check</source> <source>Install Update</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>INSTALL</source>
<translation>openpilotが最後にアップデートの確認に成功してからの時間です</translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Check for Update</source>
<translation></translation>
</message>
<message>
<location line="+5"/>
<source>CHECKING</source>
<translation></translation>
</message> </message>
<message> <message>
<location line="+7"/> <location line="+8"/>
<source>Switch Branch</source> <source>Target Branch</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>ENTER</source> <source>SELECT</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+13"/>
<location line="+2"/> <source>Select a branch</source>
<source>The new branch will be pulled the next time the updater runs.</source> <translation type="unfinished"></translation>
<translation>updater </translation>
</message>
<message>
<location line="+0"/>
<source>Enter branch name</source>
<translation></translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+12"/>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1059,13 +1038,8 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+17"/> <location line="-47"/>
<source>failed to fetch update</source> <location line="+3"/>
<translation></translation>
</message>
<message>
<location line="+1"/>
<location line="+21"/>
<source>CHECK</source> <source>CHECK</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1083,7 +1057,7 @@ location set</source>
<translation>警告: これはGitHub SSH GitHub GitHub </translation> <translation>警告: これはGitHub SSH GitHub GitHub </translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+2"/>
<location line="+24"/> <location line="+24"/>
<source>ADD</source> <source>ADD</source>
<translation></translation> <translation></translation>
@ -1153,7 +1127,7 @@ location set</source>
<context> <context>
<name>TogglesPanel</name> <name>TogglesPanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-324"/> <location filename="../qt/offroad/settings.cc" line="-303"/>
<source>Enable openpilot</source> <source>Enable openpilot</source>
<translation>openpilot </translation> <translation>openpilot </translation>
</message> </message>
@ -1290,12 +1264,11 @@ location set</source>
<name>WifiUI</name> <name>WifiUI</name>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="+113"/> <location filename="../qt/offroad/networking.cc" line="+113"/>
<location line="+53"/>
<source>Scanning for networks...</source> <source>Scanning for networks...</source>
<translation>...</translation> <translation>...</translation>
</message> </message>
<message> <message>
<location line="+26"/> <location line="+80"/>
<source>CONNECTING...</source> <source>CONNECTING...</source>
<translation>...</translation> <translation>...</translation>
</message> </message>

@ -193,7 +193,7 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+3"/>
<source>Select a language</source> <source>Select a language</source>
<translation> </translation> <translation> </translation>
</message> </message>
@ -418,7 +418,7 @@ prime subscription. Sign up now: https://connect.comma.ai</source>
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location line="+57"/> <location line="+58"/>
<source>No home <source>No home
location set</source> location set</source>
<translation> <translation>
@ -432,7 +432,7 @@ location set</source>
</translation> </translation>
</message> </message>
<message> <message>
<location line="+113"/> <location line="+120"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation> </translation> <translation> </translation>
</message> </message>
@ -718,7 +718,7 @@ location set</source>
<context> <context>
<name>SettingsWindow</name> <name>SettingsWindow</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="+101"/> <location filename="../qt/offroad/settings.cc" line="+22"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
@ -983,68 +983,47 @@ location set</source>
<context> <context>
<name>SoftwarePanel</name> <name>SoftwarePanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-130"/> <location filename="../qt/offroad/software_settings.cc" line="+24"/>
<source>Git Branch</source> <source>Updates are only downloaded while the car is off.</source>
<translation>Git </translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+5"/>
<source>Git Commit</source> <source>Current Version</source>
<translation>Git </translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>OS Version</source>
<translation>OS </translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+4"/>
<source>Version</source> <source>Download</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+12"/>
<source>Last Update Check</source> <source>Install Update</source>
<translation> </translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>INSTALL</source>
<translation> openpilot이 . .</translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Check for Update</source>
<translation> </translation>
</message>
<message>
<location line="+5"/>
<source>CHECKING</source>
<translation></translation>
</message> </message>
<message> <message>
<location line="+7"/> <location line="+8"/>
<source>Switch Branch</source> <source>Target Branch</source>
<translation> </translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>ENTER</source> <source>SELECT</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+13"/>
<location line="+2"/> <source>Select a branch</source>
<source>The new branch will be pulled the next time the updater runs.</source> <translation type="unfinished"></translation>
<translation> .</translation>
</message>
<message>
<location line="+0"/>
<source>Enter branch name</source>
<translation> </translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+12"/>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1059,13 +1038,8 @@ location set</source>
<translation>?</translation> <translation>?</translation>
</message> </message>
<message> <message>
<location line="+17"/> <location line="-47"/>
<source>failed to fetch update</source> <location line="+3"/>
<translation> </translation>
</message>
<message>
<location line="+1"/>
<location line="+21"/>
<source>CHECK</source> <source>CHECK</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1083,7 +1057,7 @@ location set</source>
<translation>경고: 허용으로 GitHub SSH . GitHub ID . comma에서는 GitHub ID를 .</translation> <translation>경고: 허용으로 GitHub SSH . GitHub ID . comma에서는 GitHub ID를 .</translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+2"/>
<location line="+24"/> <location line="+24"/>
<source>ADD</source> <source>ADD</source>
<translation></translation> <translation></translation>
@ -1153,7 +1127,7 @@ location set</source>
<context> <context>
<name>TogglesPanel</name> <name>TogglesPanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-324"/> <location filename="../qt/offroad/settings.cc" line="-303"/>
<source>Enable openpilot</source> <source>Enable openpilot</source>
<translation>openpilot </translation> <translation>openpilot </translation>
</message> </message>
@ -1290,12 +1264,11 @@ location set</source>
<name>WifiUI</name> <name>WifiUI</name>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="+113"/> <location filename="../qt/offroad/networking.cc" line="+113"/>
<location line="+53"/>
<source>Scanning for networks...</source> <source>Scanning for networks...</source>
<translation> ...</translation> <translation> ...</translation>
</message> </message>
<message> <message>
<location line="+26"/> <location line="+80"/>
<source>CONNECTING...</source> <source>CONNECTING...</source>
<translation>...</translation> <translation>...</translation>
</message> </message>

@ -193,7 +193,7 @@
<translation>ALTERAR</translation> <translation>ALTERAR</translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+3"/>
<source>Select a language</source> <source>Select a language</source>
<translation>Selecione o Idioma</translation> <translation>Selecione o Idioma</translation>
</message> </message>
@ -419,7 +419,7 @@ prime subscription. Sign up now: https://connect.comma.ai</source>
uma assinatura prime Inscreva-se agora: https://connect.comma.ai</translation> uma assinatura prime Inscreva-se agora: https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location line="+57"/> <location line="+58"/>
<source>No home <source>No home
location set</source> location set</source>
<translation>Sem local <translation>Sem local
@ -433,7 +433,7 @@ location set</source>
trabalho definido</translation> trabalho definido</translation>
</message> </message>
<message> <message>
<location line="+113"/> <location line="+120"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation>sem destinos recentes</translation> <translation>sem destinos recentes</translation>
</message> </message>
@ -722,7 +722,7 @@ trabalho definido</translation>
<context> <context>
<name>SettingsWindow</name> <name>SettingsWindow</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="+101"/> <location filename="../qt/offroad/settings.cc" line="+22"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
@ -987,68 +987,47 @@ trabalho definido</translation>
<context> <context>
<name>SoftwarePanel</name> <name>SoftwarePanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-130"/> <location filename="../qt/offroad/software_settings.cc" line="+24"/>
<source>Git Branch</source> <source>Updates are only downloaded while the car is off.</source>
<translation>Git Branch</translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Git Commit</source>
<translation>Último Commit</translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+5"/>
<source>OS Version</source> <source>Current Version</source>
<translation>Versão do Sistema</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+4"/>
<source>Version</source> <source>Download</source>
<translation>Versão</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+12"/>
<source>Last Update Check</source> <source>Install Update</source>
<translation>Verificação da última atualização</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>INSTALL</source>
<translation>A última vez que o openpilot verificou com sucesso uma atualização. O atualizador funciona com o carro desligado.</translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Check for Update</source>
<translation>Verifique atualizações</translation>
</message>
<message>
<location line="+5"/>
<source>CHECKING</source>
<translation>VERIFICANDO</translation>
</message>
<message>
<location line="+7"/>
<source>Switch Branch</source>
<translation>Alterar Branch</translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+8"/>
<source>ENTER</source> <source>Target Branch</source>
<translation>INSERIR</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<location line="+2"/> <source>SELECT</source>
<source>The new branch will be pulled the next time the updater runs.</source> <translation type="unfinished"></translation>
<translation>A nova branch será aplicada ao verificar atualizações.</translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+13"/>
<source>Enter branch name</source> <source>Select a branch</source>
<translation>Inserir o nome da branch</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+12"/>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation>DESINSTALAR</translation> <translation>DESINSTALAR</translation>
</message> </message>
@ -1063,13 +1042,8 @@ trabalho definido</translation>
<translation>Tem certeza que quer desinstalar?</translation> <translation>Tem certeza que quer desinstalar?</translation>
</message> </message>
<message> <message>
<location line="+17"/> <location line="-47"/>
<source>failed to fetch update</source> <location line="+3"/>
<translation>falha ao buscar atualização</translation>
</message>
<message>
<location line="+1"/>
<location line="+21"/>
<source>CHECK</source> <source>CHECK</source>
<translation>VERIFICAR</translation> <translation>VERIFICAR</translation>
</message> </message>
@ -1087,7 +1061,7 @@ trabalho definido</translation>
<translation>Aviso: isso concede acesso SSH a todas as chaves públicas nas configurações do GitHub. Nunca insira um nome de usuário do GitHub que não seja o seu. Um funcionário da comma NUNCA pedirá que você adicione seu nome de usuário do GitHub.</translation> <translation>Aviso: isso concede acesso SSH a todas as chaves públicas nas configurações do GitHub. Nunca insira um nome de usuário do GitHub que não seja o seu. Um funcionário da comma NUNCA pedirá que você adicione seu nome de usuário do GitHub.</translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+2"/>
<location line="+24"/> <location line="+24"/>
<source>ADD</source> <source>ADD</source>
<translation>ADICIONAR</translation> <translation>ADICIONAR</translation>
@ -1157,7 +1131,7 @@ trabalho definido</translation>
<context> <context>
<name>TogglesPanel</name> <name>TogglesPanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-324"/> <location filename="../qt/offroad/settings.cc" line="-303"/>
<source>Enable openpilot</source> <source>Enable openpilot</source>
<translation>Ativar openpilot</translation> <translation>Ativar openpilot</translation>
</message> </message>
@ -1294,12 +1268,11 @@ trabalho definido</translation>
<name>WifiUI</name> <name>WifiUI</name>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="+113"/> <location filename="../qt/offroad/networking.cc" line="+113"/>
<location line="+53"/>
<source>Scanning for networks...</source> <source>Scanning for networks...</source>
<translation>Procurando redes...</translation> <translation>Procurando redes...</translation>
</message> </message>
<message> <message>
<location line="+26"/> <location line="+80"/>
<source>CONNECTING...</source> <source>CONNECTING...</source>
<translation>CONECTANDO...</translation> <translation>CONECTANDO...</translation>
</message> </message>

@ -193,7 +193,7 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+3"/>
<source>Select a language</source> <source>Select a language</source>
<translation></translation> <translation></translation>
</message> </message>
@ -418,7 +418,7 @@ prime subscription. Sign up now: https://connect.comma.ai</source>
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location line="+57"/> <location line="+58"/>
<source>No home <source>No home
location set</source> location set</source>
<translation></translation> <translation></translation>
@ -430,7 +430,7 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+113"/> <location line="+120"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation></translation> <translation></translation>
</message> </message>
@ -716,7 +716,7 @@ location set</source>
<context> <context>
<name>SettingsWindow</name> <name>SettingsWindow</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="+101"/> <location filename="../qt/offroad/settings.cc" line="+22"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
@ -981,68 +981,47 @@ location set</source>
<context> <context>
<name>SoftwarePanel</name> <name>SoftwarePanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-130"/> <location filename="../qt/offroad/software_settings.cc" line="+24"/>
<source>Git Branch</source> <source>Updates are only downloaded while the car is off.</source>
<translation>Git Branch</translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Git Commit</source>
<translation>Git Commit</translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+5"/>
<source>OS Version</source> <source>Current Version</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+4"/>
<source>Version</source> <source>Download</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+12"/>
<source>Last Update Check</source> <source>Install Update</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>INSTALL</source>
<translation></translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Check for Update</source>
<translation></translation>
</message>
<message>
<location line="+5"/>
<source>CHECKING</source>
<translation></translation>
</message>
<message>
<location line="+7"/>
<source>Switch Branch</source>
<translation></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+8"/>
<source>ENTER</source> <source>Target Branch</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<location line="+2"/> <source>SELECT</source>
<source>The new branch will be pulled the next time the updater runs.</source> <translation type="unfinished"></translation>
<translation></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+13"/>
<source>Enter branch name</source> <source>Select a branch</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+12"/>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1057,13 +1036,8 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+17"/> <location line="-47"/>
<source>failed to fetch update</source> <location line="+3"/>
<translation></translation>
</message>
<message>
<location line="+1"/>
<location line="+21"/>
<source>CHECK</source> <source>CHECK</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1081,7 +1055,7 @@ location set</source>
<translation>SSH访问权限给您GitHub设置中的所有公钥GitHub用户名comma员工永远不会要求您添加他们的GitHub用户名</translation> <translation>SSH访问权限给您GitHub设置中的所有公钥GitHub用户名comma员工永远不会要求您添加他们的GitHub用户名</translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+2"/>
<location line="+24"/> <location line="+24"/>
<source>ADD</source> <source>ADD</source>
<translation></translation> <translation></translation>
@ -1151,7 +1125,7 @@ location set</source>
<context> <context>
<name>TogglesPanel</name> <name>TogglesPanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-324"/> <location filename="../qt/offroad/settings.cc" line="-303"/>
<source>Enable openpilot</source> <source>Enable openpilot</source>
<translation>openpilot</translation> <translation>openpilot</translation>
</message> </message>
@ -1288,12 +1262,11 @@ location set</source>
<name>WifiUI</name> <name>WifiUI</name>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="+113"/> <location filename="../qt/offroad/networking.cc" line="+113"/>
<location line="+53"/>
<source>Scanning for networks...</source> <source>Scanning for networks...</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+26"/> <location line="+80"/>
<source>CONNECTING...</source> <source>CONNECTING...</source>
<translation></translation> <translation></translation>
</message> </message>

@ -193,7 +193,7 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+3"/>
<source>Select a language</source> <source>Select a language</source>
<translation></translation> <translation></translation>
</message> </message>
@ -418,7 +418,7 @@ prime subscription. Sign up now: https://connect.comma.ai</source>
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location line="+57"/> <location line="+58"/>
<source>No home <source>No home
location set</source> location set</source>
<translation> <translation>
@ -432,7 +432,7 @@ location set</source>
</translation> </translation>
</message> </message>
<message> <message>
<location line="+113"/> <location line="+120"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation></translation> <translation></translation>
</message> </message>
@ -718,7 +718,7 @@ location set</source>
<context> <context>
<name>SettingsWindow</name> <name>SettingsWindow</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="+101"/> <location filename="../qt/offroad/settings.cc" line="+22"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
@ -983,68 +983,47 @@ location set</source>
<context> <context>
<name>SoftwarePanel</name> <name>SoftwarePanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-130"/> <location filename="../qt/offroad/software_settings.cc" line="+24"/>
<source>Git Branch</source> <source>Updates are only downloaded while the car is off.</source>
<translation>Git </translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Git Commit</source>
<translation>Git </translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+5"/>
<source>OS Version</source> <source>Current Version</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+4"/>
<source>Version</source> <source>Download</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+1"/> <location line="+12"/>
<source>Last Update Check</source> <source>Install Update</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>INSTALL</source>
<translation></translation> <translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
<source>Check for Update</source>
<translation></translation>
</message>
<message>
<location line="+5"/>
<source>CHECKING</source>
<translation></translation>
</message>
<message>
<location line="+7"/>
<source>Switch Branch</source>
<translation></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+8"/>
<source>ENTER</source> <source>Target Branch</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<location line="+2"/> <source>SELECT</source>
<source>The new branch will be pulled the next time the updater runs.</source> <translation type="unfinished"></translation>
<translation></translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+13"/>
<source>Enter branch name</source> <source>Select a branch</source>
<translation></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+12"/>
<source>UNINSTALL</source> <source>UNINSTALL</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1059,13 +1038,8 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location line="+17"/> <location line="-47"/>
<source>failed to fetch update</source> <location line="+3"/>
<translation></translation>
</message>
<message>
<location line="+1"/>
<location line="+21"/>
<source>CHECK</source> <source>CHECK</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1083,7 +1057,7 @@ location set</source>
<translation> GitHub SSH GitHub comma GitHub </translation> <translation> GitHub SSH GitHub comma GitHub </translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+2"/>
<location line="+24"/> <location line="+24"/>
<source>ADD</source> <source>ADD</source>
<translation></translation> <translation></translation>
@ -1153,7 +1127,7 @@ location set</source>
<context> <context>
<name>TogglesPanel</name> <name>TogglesPanel</name>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="-324"/> <location filename="../qt/offroad/settings.cc" line="-303"/>
<source>Enable openpilot</source> <source>Enable openpilot</source>
<translation> openpilot</translation> <translation> openpilot</translation>
</message> </message>
@ -1290,12 +1264,11 @@ location set</source>
<name>WifiUI</name> <name>WifiUI</name>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="+113"/> <location filename="../qt/offroad/networking.cc" line="+113"/>
<location line="+53"/>
<source>Scanning for networks...</source> <source>Scanning for networks...</source>
<translation>...</translation> <translation>...</translation>
</message> </message>
<message> <message>
<location line="+26"/> <location line="+80"/>
<source>CONNECTING...</source> <source>CONNECTING...</source>
<translation>...</translation> <translation>...</translation>
</message> </message>

@ -23,6 +23,7 @@
# disable this service. # disable this service.
import os import os
import re
import datetime import datetime
import subprocess import subprocess
import psutil import psutil
@ -31,8 +32,9 @@ import signal
import fcntl import fcntl
import time import time
import threading import threading
from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Optional from typing import List, Union, Optional
from markdown_it import MarkdownIt from markdown_it import MarkdownIt
from common.basedir import BASEDIR from common.basedir import BASEDIR
@ -54,38 +56,27 @@ DAYS_NO_CONNECTIVITY_MAX = 14 # do not allow to engage after this many days
DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days
class WaitTimeHelper: class WaitTimeHelper:
def __init__(self, proc): def __init__(self):
self.proc = proc
self.ready_event = threading.Event() self.ready_event = threading.Event()
self.shutdown = False self.only_check_for_update = False
signal.signal(signal.SIGTERM, self.graceful_shutdown)
signal.signal(signal.SIGINT, self.graceful_shutdown)
signal.signal(signal.SIGHUP, self.update_now) signal.signal(signal.SIGHUP, self.update_now)
signal.signal(signal.SIGUSR1, self.check_now)
def graceful_shutdown(self, signum: int, frame) -> None: def update_now(self, signum: int, frame) -> None:
# umount -f doesn't appear effective in avoiding "device busy" on NEOS, cloudlog.info("caught SIGHUP, attempting to downloading update")
# so don't actually die until the next convenient opportunity in main(). self.only_check_for_update = False
cloudlog.info("caught SIGINT/SIGTERM, dismounting overlay at next opportunity")
# forward the signal to all our child processes
child_procs = self.proc.children(recursive=True)
for p in child_procs:
p.send_signal(signum)
self.shutdown = True
self.ready_event.set() self.ready_event.set()
def update_now(self, signum: int, frame) -> None: def check_now(self, signum: int, frame) -> None:
cloudlog.info("caught SIGHUP, running update check immediately") cloudlog.info("caught SIGUSR1, checking for updates")
self.only_check_for_update = True
self.ready_event.set() self.ready_event.set()
def sleep(self, t: float) -> None: def sleep(self, t: float) -> None:
self.ready_event.wait(timeout=t) self.ready_event.wait(timeout=t)
def run(cmd: List[str], cwd: Optional[str] = None, low_priority: bool = False): def run(cmd: List[str], cwd: Optional[str] = None) -> str:
if low_priority:
cmd = ["nice", "-n", "19"] + cmd
return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8')
@ -98,59 +89,19 @@ def set_consistent_flag(consistent: bool) -> None:
consistent_file.unlink(missing_ok=True) consistent_file.unlink(missing_ok=True)
os.sync() os.sync()
def parse_release_notes(basedir: str) -> bytes:
def set_params(new_version: bool, failed_count: int, exception: Optional[str]) -> None:
params = Params()
params.put("UpdateFailedCount", str(failed_count))
last_update = datetime.datetime.utcnow()
if failed_count == 0:
t = last_update.isoformat()
params.put("LastUpdateTime", t.encode('utf8'))
else:
try: try:
t = params.get("LastUpdateTime", encoding='utf8') with open(os.path.join(basedir, "RELEASES.md"), "rb") as f:
last_update = datetime.datetime.fromisoformat(t)
except (TypeError, ValueError):
pass
if exception is None:
params.remove("LastUpdateException")
else:
params.put("LastUpdateException", exception)
# Write out release notes for new versions
if new_version:
try:
with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f:
r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes
try: try:
params.put("ReleaseNotes", MarkdownIt().render(r.decode("utf-8"))) return bytes(MarkdownIt().render(r.decode("utf-8")), encoding="utf-8")
except Exception: except Exception:
params.put("ReleaseNotes", r + b"\n") return r + b"\n"
except FileNotFoundError:
pass
except Exception: except Exception:
params.put("ReleaseNotes", "") cloudlog.exception("failed to parse release notes")
params.put_bool("UpdateAvailable", True) return b""
# Handle user prompt
for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"):
set_offroad_alert(alert, False)
now = datetime.datetime.utcnow()
dt = now - last_update
if failed_count > 15 and exception is not None:
if is_tested_branch():
extra_text = "Ensure the software is correctly installed"
else:
extra_text = exception
set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text)
elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1:
set_offroad_alert("Offroad_ConnectivityNeeded", True)
elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT:
remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1)
set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.")
def setup_git_options(cwd: str) -> None: def setup_git_options(cwd: str) -> None:
# We sync FS object atimes (which NEOS doesn't use) and mtimes, but ctimes # We sync FS object atimes (which NEOS doesn't use) and mtimes, but ctimes
@ -228,12 +179,12 @@ def init_overlay() -> None:
run(["sudo"] + mount_cmd) run(["sudo"] + mount_cmd)
run(["sudo", "chmod", "755", os.path.join(OVERLAY_METADATA, "work")]) run(["sudo", "chmod", "755", os.path.join(OVERLAY_METADATA, "work")])
git_diff = run(["git", "diff"], OVERLAY_MERGED, low_priority=True) git_diff = run(["git", "diff"], OVERLAY_MERGED)
params.put("GitDiff", git_diff) params.put("GitDiff", git_diff)
cloudlog.info(f"git diff output:\n{git_diff}") cloudlog.info(f"git diff output:\n{git_diff}")
def finalize_update(wait_helper: WaitTimeHelper) -> None: def finalize_update() -> None:
"""Take the current OverlayFS merged view and finalize a copy outside of """Take the current OverlayFS merged view and finalize a copy outside of
OverlayFS, ready to be swapped-in at BASEDIR. Copy using shutil.copytree""" OverlayFS, ready to be swapped-in at BASEDIR. Copy using shutil.copytree"""
@ -258,14 +209,11 @@ def finalize_update(wait_helper: WaitTimeHelper) -> None:
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
cloudlog.exception(f"Failed git cleanup, took {time.monotonic() - t:.3f} s") cloudlog.exception(f"Failed git cleanup, took {time.monotonic() - t:.3f} s")
if wait_helper.shutdown:
cloudlog.info("got interrupted finalizing overlay")
else:
set_consistent_flag(True) set_consistent_flag(True)
cloudlog.info("done finalizing overlay") cloudlog.info("done finalizing overlay")
def handle_agnos_update(wait_helper: WaitTimeHelper) -> None: def handle_agnos_update() -> None:
from system.hardware.tici.agnos import flash_agnos_update, get_target_slot_number from system.hardware.tici.agnos import flash_agnos_update, get_target_slot_number
cur_version = HARDWARE.get_os_version() cur_version = HARDWARE.get_os_version()
@ -288,65 +236,161 @@ def handle_agnos_update(wait_helper: WaitTimeHelper) -> None:
set_offroad_alert("Offroad_NeosUpdate", False) set_offroad_alert("Offroad_NeosUpdate", False)
def check_git_fetch_result(fetch_txt: str) -> bool:
err_msg = "Failed to add the host to the list of known hosts (/data/data/com.termux/files/home/.ssh/known_hosts).\n"
return len(fetch_txt) > 0 and (fetch_txt != err_msg)
class Updater:
def __init__(self):
self.params = Params()
self.branches = defaultdict(lambda: None)
def check_for_update() -> Tuple[bool, bool]: @property
setup_git_options(OVERLAY_MERGED) def target_branch(self) -> str:
b: Union[str, None] = self.params.get("UpdaterTargetBranch", encoding='utf-8')
if b is None:
b = self.get_branch(BASEDIR)
self.params.put("UpdaterTargetBranch", b)
return b
@property
def update_ready(self) -> bool:
consistent_file = Path(os.path.join(FINALIZED, ".overlay_consistent"))
if consistent_file.is_file():
hash_mismatch = self.get_commit_hash(BASEDIR) != self.branches[self.target_branch]
branch_mismatch = self.get_branch(BASEDIR) != self.target_branch
on_target_branch = self.get_branch(FINALIZED) == self.target_branch
return ((hash_mismatch or branch_mismatch) and on_target_branch)
return False
@property
def update_available(self) -> bool:
if os.path.isdir(OVERLAY_MERGED):
hash_mismatch = self.get_commit_hash(OVERLAY_MERGED) != self.branches[self.target_branch]
branch_mismatch = self.get_branch(OVERLAY_MERGED) != self.target_branch
return hash_mismatch or branch_mismatch
return False
def get_branch(self, path: str) -> str:
return run(["git", "rev-parse", "--abbrev-ref", "HEAD"], path).rstrip()
def get_commit_hash(self, path: str = OVERLAY_MERGED) -> str:
return run(["git", "rev-parse", "HEAD"], path).rstrip()
def set_params(self, failed_count: int, exception: Optional[str]) -> None:
self.params.put("UpdateFailedCount", str(failed_count))
self.params.put_bool("UpdaterFetchAvailable", self.update_available)
self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys()))
last_update = datetime.datetime.utcnow()
if failed_count == 0:
t = last_update.isoformat()
self.params.put("LastUpdateTime", t.encode('utf8'))
else:
try: try:
git_fetch_output = run(["git", "fetch", "--dry-run"], OVERLAY_MERGED, low_priority=True) t = self.params.get("LastUpdateTime", encoding='utf8')
return True, check_git_fetch_result(git_fetch_output) last_update = datetime.datetime.fromisoformat(t)
except subprocess.CalledProcessError: except (TypeError, ValueError):
return False, False pass
if exception is None:
self.params.remove("LastUpdateException")
else:
self.params.put("LastUpdateException", exception)
def fetch_update(wait_helper: WaitTimeHelper) -> bool: # Write out current and new version info
cloudlog.info("attempting git fetch inside staging overlay") def get_description(basedir: str) -> str:
version = ""
branch = ""
commit = ""
try:
branch = self.get_branch(basedir)
commit = self.get_commit_hash(basedir)
with open(os.path.join(basedir, "common", "version.h")) as f:
version = f.read().split('"')[1]
except Exception:
pass
return f"{version} / {branch} / {commit[:7]}"
self.params.put("UpdaterCurrentDescription", get_description(BASEDIR))
self.params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
self.params.put("UpdaterNewDescription", get_description(FINALIZED))
self.params.put("UpdaterNewReleaseNotes", parse_release_notes(FINALIZED))
self.params.put_bool("UpdateAvailable", self.update_ready)
# Handle user prompt
for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"):
set_offroad_alert(alert, False)
now = datetime.datetime.utcnow()
dt = now - last_update
if failed_count > 15 and exception is not None:
if is_tested_branch():
extra_text = "Ensure the software is correctly installed. Uninstall and re-install if this error persists."
else:
extra_text = exception
set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text)
elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1:
set_offroad_alert("Offroad_ConnectivityNeeded", True)
elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT:
remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1)
set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.")
def check_for_update(self) -> None:
cloudlog.info("checking for updates")
excluded_branches = ('release2', 'release2-staging', 'dashcam', 'dashcam-staging')
setup_git_options(OVERLAY_MERGED) setup_git_options(OVERLAY_MERGED)
output = run(["git", "ls-remote", "--heads"], OVERLAY_MERGED)
self.branches = defaultdict(lambda: None)
for line in output.split('\n'):
ls_remotes_re = r'(?P<commit_sha>\b[0-9a-f]{5,40}\b)(\s+)(refs\/heads\/)(?P<branch_name>.*$)'
x = re.fullmatch(ls_remotes_re, line.strip())
if x is not None and x.group('branch_name') not in excluded_branches:
self.branches[x.group('branch_name')] = x.group('commit_sha')
cur_branch = self.get_branch(OVERLAY_MERGED)
cur_commit = self.get_commit_hash(OVERLAY_MERGED)
new_branch = self.target_branch
new_commit = self.branches[new_branch]
if (cur_branch, cur_commit) != (new_branch, new_commit):
cloudlog.info(f"update available, {cur_branch} ({cur_commit[:7]}) -> {new_branch} ({new_commit[:7]})")
else:
cloudlog.info(f"up to date on {cur_branch} ({cur_commit[:7]})")
git_fetch_output = run(["git", "fetch"], OVERLAY_MERGED, low_priority=True) def fetch_update(self) -> None:
cloudlog.info("git fetch success: %s", git_fetch_output) cloudlog.info("attempting git fetch inside staging overlay")
cur_hash = run(["git", "rev-parse", "HEAD"], OVERLAY_MERGED).rstrip() self.params.put("UpdaterState", "downloading...")
upstream_hash = run(["git", "rev-parse", "@{u}"], OVERLAY_MERGED).rstrip()
new_version: bool = cur_hash != upstream_hash
git_fetch_result = check_git_fetch_result(git_fetch_output)
new_branch = Params().get("SwitchToBranch", encoding='utf8') # TODO: cleanly interrupt this and invalidate old update
if new_branch is not None: set_consistent_flag(False)
new_version = True self.params.put_bool("UpdateAvailable", False)
cloudlog.info(f"comparing {cur_hash} to {upstream_hash}") setup_git_options(OVERLAY_MERGED)
if new_version or git_fetch_result:
cloudlog.info("Running update") branch = self.target_branch
git_fetch_output = run(["git", "fetch", "origin", branch], OVERLAY_MERGED)
cloudlog.info("git fetch success: %s", git_fetch_output)
if new_version:
cloudlog.info("git reset in progress") cloudlog.info("git reset in progress")
cmds = [ cmds = [
["git", "reset", "--hard", "@{u}"], ["git", "checkout", "--force", "--no-recurse-submodules", branch],
["git", "reset", "--hard", f"origin/{branch}"],
["git", "clean", "-xdf"], ["git", "clean", "-xdf"],
["git", "submodule", "init"], ["git", "submodule", "init"],
["git", "submodule", "update"], ["git", "submodule", "update"],
] ]
if new_branch is not None: r = [run(cmd, OVERLAY_MERGED) for cmd in cmds]
cloudlog.info(f"switching to branch {repr(new_branch)}")
cmds.insert(0, ["git", "checkout", "-f", new_branch])
r = [run(cmd, OVERLAY_MERGED, low_priority=True) for cmd in cmds]
cloudlog.info("git reset success: %s", '\n'.join(r)) cloudlog.info("git reset success: %s", '\n'.join(r))
# TODO: show agnos download progress
if AGNOS: if AGNOS:
handle_agnos_update(wait_helper) handle_agnos_update()
# Create the finalized, ready-to-swap update # Create the finalized, ready-to-swap update
finalize_update(wait_helper) self.params.put("UpdaterState", "finalizing update...")
cloudlog.info("openpilot update successful!") finalize_update()
else: cloudlog.info("finalize success!")
cloudlog.info("nothing new from git at this time")
return new_version
def main() -> None: def main() -> None:
@ -378,29 +422,36 @@ def main() -> None:
overlay_init = Path(os.path.join(BASEDIR, ".overlay_init")) overlay_init = Path(os.path.join(BASEDIR, ".overlay_init"))
overlay_init.unlink(missing_ok=True) overlay_init.unlink(missing_ok=True)
updater = Updater()
update_failed_count = 0 # TODO: Load from param? update_failed_count = 0 # TODO: Load from param?
wait_helper = WaitTimeHelper(proc)
# no fetch on the first time
wait_helper = WaitTimeHelper()
wait_helper.only_check_for_update = True
# Run the update loop # Run the update loop
while not wait_helper.shutdown: while True:
wait_helper.ready_event.clear() wait_helper.ready_event.clear()
# Attempt an update # Attempt an update
exception = None exception = None
new_version = False
update_failed_count += 1
try: try:
# TODO: reuse overlay from previous updated instance if it looks clean
init_overlay() init_overlay()
# TODO: still needed? skip this and just fetch? # ensure we have some params written soon after startup
# Lightweight internt check updater.set_params(update_failed_count, exception)
internet_ok, update_available = check_for_update() update_failed_count += 1
if internet_ok and not update_available:
update_failed_count = 0 # check for update
params.put("UpdaterState", "checking...")
updater.check_for_update()
# Fetch update # download update
if internet_ok: if wait_helper.only_check_for_update:
new_version = fetch_update(wait_helper) cloudlog.info("skipping fetch this cycle")
else:
updater.fetch_update()
update_failed_count = 0 update_failed_count = 0
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
cloudlog.event( cloudlog.event(
@ -416,16 +467,15 @@ def main() -> None:
exception = str(e) exception = str(e)
overlay_init.unlink(missing_ok=True) overlay_init.unlink(missing_ok=True)
if not wait_helper.shutdown:
try: try:
set_params(new_version, update_failed_count, exception) params.put("UpdaterState", "idle")
updater.set_params(update_failed_count, exception)
except Exception: except Exception:
cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") cloudlog.exception("uncaught updated exception while setting params, shouldn't happen")
# infrequent attempts if we successfully updated recently # infrequent attempts if we successfully updated recently
wait_helper.sleep(5*60 if update_failed_count > 0 else 90*60) wait_helper.only_check_for_update = False
wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60)
dismount_overlay()
if __name__ == "__main__": if __name__ == "__main__":

@ -61,39 +61,48 @@ const float DC_GAIN_OX03C10 = 7.32;
const float DC_GAIN_ON_GREY_AR0231= 0.2; const float DC_GAIN_ON_GREY_AR0231= 0.2;
const float DC_GAIN_OFF_GREY_AR0231 = 0.3; const float DC_GAIN_OFF_GREY_AR0231 = 0.3;
const float DC_GAIN_ON_GREY_OX03C10= 0.3; const float DC_GAIN_ON_GREY_OX03C10= 0.25;
const float DC_GAIN_OFF_GREY_OX03C10 = 0.375; const float DC_GAIN_OFF_GREY_OX03C10 = 0.35;
const int DC_GAIN_MIN_WEIGHT = 0; const int DC_GAIN_MIN_WEIGHT_AR0231 = 0;
const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; const int DC_GAIN_MAX_WEIGHT_AR0231 = 1;
const int DC_GAIN_MIN_WEIGHT_OX03C10 = 16;
const int DC_GAIN_MAX_WEIGHT_OX03C10 = 32; const int DC_GAIN_MAX_WEIGHT_OX03C10 = 32;
const float TARGET_GREY_FACTOR_AR0231 = 1.0;
const float TARGET_GREY_FACTOR_OX03C10 = 0.02;
const float sensor_analog_gains_AR0231[] = { const float sensor_analog_gains_AR0231[] = {
1.0/8.0, 2.0/8.0, 2.0/7.0, 3.0/7.0, // 0, 1, 2, 3 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 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 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 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass
// similar gain curve to AR
const float sensor_analog_gains_OX03C10[] = { const float sensor_analog_gains_OX03C10[] = {
1.0, 1.25, 1.3125, 1.5625, 1.0, 1.125, 1.25, 1.3125, 1.5625,
1.6875, 2.0, 2.25, 2.625, 1.6875, 2.0, 2.25, 2.625, 3.125,
3.125, 3.625, 4.5, 5.0, 3.625, 4.0, 4.5, 5.0, 5.5,
7.25, 8.5, 12.0, 15.5}; 6.0, 6.5, 7.0, 7.5, 8.0,
8.5, 9.0, 9.5, 10.0, 10.5,
11.0, 11.5, 12.0, 12.5, 13.0,
13.5, 14.0, 14.5, 15.0, 15.5};
const uint32_t ox03c10_analog_gains_reg[] = { const uint32_t ox03c10_analog_gains_reg[] = {
0x100, 0x140, 0x150, 0x190, 0x100, 0x120, 0x140, 0x150, 0x190,
0x1B0, 0x200, 0x240, 0x2A0, 0x1B0, 0x200, 0x240, 0x2A0, 0x320,
0x320, 0x3A0, 0x480, 0x500, 0x3A0, 0x400, 0x480, 0x500, 0x580,
0x740, 0x880, 0xC00, 0xF80}; 0x600, 0x680, 0x700, 0x780, 0x800,
0x880, 0x900, 0x980, 0xA00, 0xA80,
0xB00, 0xB80, 0xC00, 0xC80, 0xD00,
0xD80, 0xE00, 0xE80, 0xF00, 0xF80};
const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x
const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x
const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x
const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0;
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x5; // 2x const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x6; // 2x
const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0xF; const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x22;
const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss
const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms
@ -517,6 +526,7 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) {
void CameraState::camera_set_parameters() { void CameraState::camera_set_parameters() {
if (camera_id == CAMERA_ID_AR0231) { if (camera_id == CAMERA_ID_AR0231) {
dc_gain_factor = DC_GAIN_AR0231; dc_gain_factor = DC_GAIN_AR0231;
dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231;
dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231;
dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231; dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231;
dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231; dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231;
@ -529,8 +539,10 @@ void CameraState::camera_set_parameters() {
sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; sensor_analog_gains[i] = sensor_analog_gains_AR0231[i];
} }
min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx];
target_grey_factor = TARGET_GREY_FACTOR_AR0231;
} else if (camera_id == CAMERA_ID_OX03C10) { } else if (camera_id == CAMERA_ID_OX03C10) {
dc_gain_factor = DC_GAIN_OX03C10; dc_gain_factor = DC_GAIN_OX03C10;
dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10;
dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10;
dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10; dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10;
dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10; dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10;
@ -543,6 +555,7 @@ void CameraState::camera_set_parameters() {
sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i];
} }
min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx];
target_grey_factor = TARGET_GREY_FACTOR_OX03C10;
} else { } else {
assert(false); assert(false);
} }
@ -551,7 +564,7 @@ void CameraState::camera_set_parameters() {
target_grey_fraction = 0.3; target_grey_fraction = 0.3;
dc_gain_enabled = false; dc_gain_enabled = false;
dc_gain_weight = DC_GAIN_MIN_WEIGHT; dc_gain_weight = dc_gain_min_weight;
gain_idx = analog_gain_rec_idx; gain_idx = analog_gain_rec_idx;
exposure_time = 5; exposure_time = 5;
cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time; cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time;
@ -1037,7 +1050,7 @@ void CameraState::set_camera_exposure(float grey_frac) {
const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3]; const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3];
// Scale target grey between 0.1 and 0.4 depending on lighting conditions // 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 new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4);
float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey; float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey;
float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev); float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev);
@ -1053,14 +1066,14 @@ void CameraState::set_camera_exposure(float grey_frac) {
bool enable_dc_gain = dc_gain_enabled; bool enable_dc_gain = dc_gain_enabled;
if (!enable_dc_gain && target_grey < dc_gain_on_grey) { if (!enable_dc_gain && target_grey < dc_gain_on_grey) {
enable_dc_gain = true; enable_dc_gain = true;
dc_gain_weight = DC_GAIN_MIN_WEIGHT; dc_gain_weight = dc_gain_min_weight;
} else if (enable_dc_gain && target_grey > dc_gain_off_grey) { } else if (enable_dc_gain && target_grey > dc_gain_off_grey) {
enable_dc_gain = false; enable_dc_gain = false;
dc_gain_weight = dc_gain_max_weight; dc_gain_weight = dc_gain_max_weight;
} }
if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;} if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;}
if (!enable_dc_gain && dc_gain_weight > DC_GAIN_MIN_WEIGHT) {dc_gain_weight -= 1;} if (!enable_dc_gain && dc_gain_weight > dc_gain_min_weight) {dc_gain_weight -= 1;}
std::string gain_bytes, time_bytes; std::string gain_bytes, time_bytes;
if (env_ctrl_exp_from_params) { if (env_ctrl_exp_from_params) {
@ -1145,10 +1158,12 @@ void CameraState::set_camera_exposure(float grey_frac) {
// t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD // t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD
uint32_t hcg_time = std::max((dc_gain_weight * exposure_time / dc_gain_max_weight), 0); uint32_t hcg_time = std::max((dc_gain_weight * exposure_time / dc_gain_max_weight), 0);
uint32_t lcg_time = std::max(((dc_gain_max_weight - dc_gain_weight) * exposure_time / dc_gain_max_weight), 0); uint32_t lcg_time = std::max(((dc_gain_max_weight - dc_gain_weight) * exposure_time / dc_gain_max_weight), 0);
uint32_t spd_time = std::max(hcg_time / 16, (uint32_t)exposure_time_min); // uint32_t spd_time = std::max(hcg_time / 16, (uint32_t)exposure_time_min);
uint32_t vs_time = std::min(std::max(hcg_time / 64, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 128, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10);
uint32_t spd_time = vs_time;
uint32_t real_gain = ox03c10_analog_gains_reg[new_g]; uint32_t real_gain = ox03c10_analog_gains_reg[new_g];
uint32_t min_gain = ox03c10_analog_gains_reg[0];
struct i2c_random_wr_payload exp_reg_array[] = { struct i2c_random_wr_payload exp_reg_array[] = {
{0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF},
@ -1157,9 +1172,9 @@ void CameraState::set_camera_exposure(float grey_frac) {
{0x35c1, vs_time>>8}, {0x35c2, vs_time&0xFF}, {0x35c1, vs_time>>8}, {0x35c2, vs_time&0xFF},
{0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF},
{0x3588, real_gain>>8}, {0x3589, real_gain&0xFF}, {0x3588, min_gain>>8}, {0x3589, min_gain&0xFF},
{0x3548, real_gain>>8}, {0x3549, real_gain&0xFF}, {0x3548, min_gain>>8}, {0x3549, min_gain&0xFF},
{0x35c8, real_gain>>8}, {0x35c9, real_gain&0xFF}, {0x35c8, min_gain>>8}, {0x35c9, min_gain&0xFF},
}; };
sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false);
} }

@ -31,11 +31,12 @@ public:
int exposure_time_max; int exposure_time_max;
float dc_gain_factor; float dc_gain_factor;
int dc_gain_min_weight;
int dc_gain_max_weight; int dc_gain_max_weight;
float dc_gain_on_grey; float dc_gain_on_grey;
float dc_gain_off_grey; float dc_gain_off_grey;
float sensor_analog_gains[16]; float sensor_analog_gains[35];
int analog_gain_min_idx; int analog_gain_min_idx;
int analog_gain_max_idx; int analog_gain_max_idx;
int analog_gain_rec_idx; int analog_gain_rec_idx;
@ -45,6 +46,7 @@ public:
float measured_grey_fraction; float measured_grey_fraction;
float target_grey_fraction; float target_grey_fraction;
float target_grey_factor;
unique_fd sensor_fd; unique_fd sensor_fd;
unique_fd csiphy_fd; unique_fd csiphy_fd;

@ -129,13 +129,13 @@ struct i2c_random_wr_payload init_array_ox03c10[] = {
{0x350a, 0x04}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain {0x350a, 0x04}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain
{0x3586, 0x40}, {0x3587, 0x00}, // lcg fine exposure {0x3586, 0x40}, {0x3587, 0x00}, // lcg fine exposure
{0x358a, 0x04}, {0x358b, 0x00}, {0x358c, 0x00}, // lcg digital gain {0x358a, 0x01}, {0x358b, 0x00}, {0x358c, 0x00}, // lcg digital gain
{0x3546, 0x20}, {0x3547, 0x00}, // spd fine exposure {0x3546, 0x20}, {0x3547, 0x00}, // spd fine exposure
{0x354a, 0x04}, {0x354b, 0x00}, {0x354c, 0x00}, // spd digital gain {0x354a, 0x01}, {0x354b, 0x00}, {0x354c, 0x00}, // spd digital gain
{0x35c6, 0xb0}, {0x35c7, 0x00}, // vs fine exposure {0x35c6, 0xb0}, {0x35c7, 0x00}, // vs fine exposure
{0x35ca, 0x04}, {0x35cb, 0x00}, {0x35cc, 0x00}, // vs digital gain {0x35ca, 0x01}, {0x35cb, 0x00}, {0x35cc, 0x00}, // vs digital gain
// also RSVD // also RSVD
{0x3600, 0x8f}, {0x3605, 0x16}, {0x3609, 0xf0}, {0x360a, 0x01}, {0x3600, 0x8f}, {0x3605, 0x16}, {0x3609, 0xf0}, {0x360a, 0x01},

Loading…
Cancel
Save