Merge remote-tracking branch 'upstream/master' into toyota-fuzzy-v2

pull/28641/head
Shane Smiskol 2 years ago
commit 58e4dab25f
  1. 4
      .github/workflows/selfdrive_tests.yaml
  2. 12
      selfdrive/car/hyundai/carstate.py
  3. 8
      selfdrive/car/hyundai/tests/test_hyundai.py
  4. 6
      selfdrive/car/hyundai/values.py
  5. 12
      selfdrive/car/toyota/tests/print_platform_codes.py
  6. 6
      selfdrive/car/toyota/tests/test_toyota.py
  7. 24
      selfdrive/car/toyota/values.py
  8. 4
      selfdrive/controls/controlsd.py
  9. 28
      selfdrive/test/process_replay/model_replay.py
  10. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  11. 2
      selfdrive/ui/qt/offroad/settings.cc
  12. 4
      selfdrive/ui/translations/main_ar.ts
  13. 4
      selfdrive/ui/translations/main_de.ts
  14. 4
      selfdrive/ui/translations/main_fr.ts
  15. 4
      selfdrive/ui/translations/main_ja.ts
  16. 4
      selfdrive/ui/translations/main_ko.ts
  17. 4
      selfdrive/ui/translations/main_pt-BR.ts
  18. 4
      selfdrive/ui/translations/main_th.ts
  19. 4
      selfdrive/ui/translations/main_tr.ts
  20. 4
      selfdrive/ui/translations/main_zh-CHS.ts
  21. 4
      selfdrive/ui/translations/main_zh-CHT.ts

@ -198,7 +198,7 @@ jobs:
$DOCKER_LOGIN $DOCKER_LOGIN
- uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/setup-with-retry
with: with:
git-lfs: false git_lfs: false
docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }}
- name: Build and push CL Docker image - name: Build and push CL Docker image
if: matrix.arch == 'x86_64' if: matrix.arch == 'x86_64'
@ -309,7 +309,7 @@ jobs:
id: print-diff id: print-diff
if: always() if: always()
run: cat selfdrive/test/process_replay/diff.txt run: cat selfdrive/test/process_replay/diff.txt
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v3
if: always() if: always()
continue-on-error: true continue-on-error: true
with: with:

@ -132,12 +132,12 @@ class CarState(CarStateBase):
# Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection, # Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection,
# as this seems to be standard over all cars, but is not the preferred method. # as this seems to be standard over all cars, but is not the preferred method.
if self.CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR):
gear = cp.vl["ELECT_GEAR"]["Elect_Gear_Shifter"]
elif self.CP.carFingerprint in CAN_GEARS["use_cluster_gears"]:
gear = cp.vl["CLU15"]["CF_Clu_Gear"] gear = cp.vl["CLU15"]["CF_Clu_Gear"]
elif self.CP.carFingerprint in CAN_GEARS["use_tcu_gears"]: elif self.CP.carFingerprint in CAN_GEARS["use_tcu_gears"]:
gear = cp.vl["TCU12"]["CUR_GR"] gear = cp.vl["TCU12"]["CUR_GR"]
elif self.CP.carFingerprint in CAN_GEARS["use_elect_gears"]:
gear = cp.vl["ELECT_GEAR"]["Elect_Gear_Shifter"]
else: else:
gear = cp.vl["LVR12"]["CF_Lvr_Gear"] gear = cp.vl["LVR12"]["CF_Lvr_Gear"]
@ -285,12 +285,12 @@ class CarState(CarStateBase):
("EMS16", 100), ("EMS16", 100),
] ]
if CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: if CP.carFingerprint in (HYBRID_CAR | EV_CAR):
messages.append(("ELECT_GEAR", 20))
elif CP.carFingerprint in CAN_GEARS["use_cluster_gears"]:
pass pass
elif CP.carFingerprint in CAN_GEARS["use_tcu_gears"]: elif CP.carFingerprint in CAN_GEARS["use_tcu_gears"]:
messages.append(("TCU12", 100)) messages.append(("TCU12", 100))
elif CP.carFingerprint in CAN_GEARS["use_elect_gears"]:
messages.append(("ELECT_GEAR", 20))
else: else:
messages.append(("LVR12", 100)) messages.append(("LVR12", 100))

@ -5,7 +5,7 @@ import unittest
from cereal import car from cereal import car
from openpilot.selfdrive.car.fw_versions import build_fw_dict from openpilot.selfdrive.car.fw_versions import build_fw_dict
from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \
EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \ HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \
UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, get_platform_codes UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, get_platform_codes
Ecu = car.CarParams.Ecu Ecu = car.CarParams.Ecu
@ -37,7 +37,11 @@ NO_DATES_PLATFORMS = {
class TestHyundaiFingerprint(unittest.TestCase): class TestHyundaiFingerprint(unittest.TestCase):
def test_canfd_not_in_can_features(self): def test_can_features(self):
# Test no EV/HEV in any gear lists (should all use ELECT_GEAR)
self.assertEqual(set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR), set())
# Test CAN FD car not in CAN feature lists
can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR) can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, CAMERA_SCC_CAR)
for car_model in CANFD_CAR: for car_model in CANFD_CAR:
self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list") self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list")

@ -2014,13 +2014,9 @@ CHECKSUM = {
} }
CAN_GEARS = { CAN_GEARS = {
# which message has the gear # which message has the gear. hybrid and EV use ELECT_GEAR
"use_cluster_gears": {CAR.ELANTRA, CAR.KONA}, "use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
"use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.IONIQ_EV_LTD,
CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID,
CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019,
CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020, CAR.AZERA_HEV_6TH_GEN},
} }
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN,

@ -8,7 +8,7 @@ ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
if __name__ == "__main__": if __name__ == "__main__":
parts_for_ecu: dict = defaultdict(set) parts_for_ecu: dict = defaultdict(set)
cars_for_code: dict = defaultdict(lambda: defaultdict(set))
for car_model, ecus in FW_VERSIONS.items(): for car_model, ecus in FW_VERSIONS.items():
print() print()
print(car_model) print(car_model)
@ -17,10 +17,18 @@ if __name__ == "__main__":
continue continue
platform_codes = get_platform_codes(ecus[ecu]) platform_codes = get_platform_codes(ecus[ecu])
parts_for_ecu[ecu] |= {code.split(b'-')[0] for code in platform_codes} parts_for_ecu[ecu] |= {code.split(b'-')[0] for code in platform_codes if code.count(b'-') > 1}
for code in platform_codes:
cars_for_code[ecu][b'-'.join(code.split(b'-')[:2])] |= {car_model}
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):')
print(f' Codes: {platform_codes}') print(f' Codes: {platform_codes}')
print('\nECU parts:') print('\nECU parts:')
for ecu, parts in parts_for_ecu.items(): for ecu, parts in parts_for_ecu.items():
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}): {parts}') print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}): {parts}')
print('\nCar models vs. platform codes (no major versions):')
for ecu, codes in cars_for_code.items():
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):')
for code, cars in codes.items():
print(f' {code!r}: {sorted(cars)}')

@ -63,9 +63,6 @@ class TestToyotaFingerprint(unittest.TestCase):
continue continue
if platform_code_ecu == Ecu.abs and car_model in (CAR.ALPHARD_TSS2,): if platform_code_ecu == Ecu.abs and car_model in (CAR.ALPHARD_TSS2,):
continue continue
# TODO: add DSU FW versions for Highlander Hybrid
if platform_code_ecu == Ecu.dsu and car_model in TSS2_CAR | {CAR.HIGHLANDERH}:
continue
self.assertIn(platform_code_ecu, [e[0] for e in ecus]) self.assertIn(platform_code_ecu, [e[0] for e in ecus])
def test_fw_format(self): def test_fw_format(self):
@ -118,11 +115,12 @@ class TestToyotaFingerprint(unittest.TestCase):
self.assertEqual(results, {b"58-70": {b"000"}, b"58-83": {b"000"}}) self.assertEqual(results, {b"58-70": {b"000"}, b"58-83": {b"000"}})
results = get_platform_codes([ results = get_platform_codes([
b"F152607110\x00\x00\x00\x00\x00\x00",
b"F152607140\x00\x00\x00\x00\x00\x00", b"F152607140\x00\x00\x00\x00\x00\x00",
b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00", b"\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00",
b"\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00", b"\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00",
]) ])
self.assertEqual(results, {b"F1526-07-1": {b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}}) self.assertEqual(results, {b"F1526-07-1": {b"10", b"40"}, b"8646F-41-04": {b"100"}, b"58-79": {b"000"}})
def test_fuzzy_excluded_platforms(self): def test_fuzzy_excluded_platforms(self):
# Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared. # Asserts a list of platforms that will not fuzzy fingerprint with platform codes due to them being shared.

@ -237,8 +237,8 @@ STATIC_DSU_MSGS = [
def get_platform_codes(fw_versions: List[bytes]) -> Dict[bytes, Set[bytes]]: def get_platform_codes(fw_versions: List[bytes]) -> Dict[bytes, Set[bytes]]:
# Returns minor versions in a dict so comparisons can be made within part-platform-version combos # Returns sub versions in a dict so comparisons can be made within part-platform-major_version combos
codes = defaultdict(set) # Optional[part]-platform-major_version: set of minor_version codes = defaultdict(set) # Optional[part]-platform-major_version: set of sub_version
for fw in fw_versions: for fw in fw_versions:
# FW versions returned from UDS queries can return multiple fields/chunks of data (different ECU calibrations, different data?) # FW versions returned from UDS queries can return multiple fields/chunks of data (different ECU calibrations, different data?)
# and are prefixed with a byte that describes how many chunks of data there are. # and are prefixed with a byte that describes how many chunks of data there are.
@ -330,13 +330,16 @@ def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]:
# Regex patterns for parsing more general platform-specific identifiers from FW versions. # Regex patterns for parsing more general platform-specific identifiers from FW versions.
# - Part number: Toyota part number (usually last character needs to be ignored to find a match). # - Part number: Toyota part number (usually last character needs to be ignored to find a match).
# - Platform: usually multiple codes per an openpilot platform, however this has the less variability and # Each ECU address has just one part number.
# - Platform: usually multiple codes per an openpilot platform, however this is the least variable and
# is usually shared across ECUs and model years signifying this describes something about the specific platform. # is usually shared across ECUs and model years signifying this describes something about the specific platform.
# - Major version: second least variable part of the FW version. Seen splitting cars by model year such as RAV4 2022/2023 and Prius. # This describes more generational changes (TSS-P vs TSS2), or manufacture region.
# - Major version: second least variable part of the FW version. Seen splitting cars by model year/API such as
# RAV4 2022/2023 and Avalon. Used to differentiate cars where API has changed slightly, but is not a generational change.
# It is important to note that these aren't always consecutive, for example: # It is important to note that these aren't always consecutive, for example:
# Prius TSS-P has these major versions over 16 FW: 2, 3, 4, 6, 8 while Prius TSS2 has: 5 # Avalon 2016-18's fwdCamera has these major versions: 01, 03 while 2019 has: 02
# - Sub version: exclusive to major version, but shared with other cars. Should only be used for further filtering, # - Sub version: exclusive to major version, but shared with other cars. Should only be used for further filtering.
# more exploration is needed. # Seen bumped in TSB FW updates, and describes other minor differences.
SHORT_FW_PATTERN = re.compile(b'[A-Z0-9](?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})') SHORT_FW_PATTERN = re.compile(b'[A-Z0-9](?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})')
MEDIUM_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{1})(?P<sub_version>[A-Z0-9]{2})') MEDIUM_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{1})(?P<sub_version>[A-Z0-9]{2})')
LONG_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})') LONG_FW_PATTERN = re.compile(b'(?P<part>[A-Z0-9]{5})(?P<platform>[A-Z0-9]{2})(?P<major_version>[A-Z0-9]{2})(?P<sub_version>[A-Z0-9]{3})')
@ -345,7 +348,12 @@ FW_CHUNK_LEN = 16
# List of ECUs that are most unique across openpilot platforms # List of ECUs that are most unique across openpilot platforms
# TODO: use hybrid ECU, splits similar ICE and hybrid variants # TODO: use hybrid ECU, splits similar ICE and hybrid variants
PLATFORM_CODE_ECUS = [Ecu.abs, Ecu.eps, Ecu.fwdCamera] # - fwdCamera: describes actual features related to ADAS. For example, on the Avalon it describes
# when TSS-P became standard, whether the car supports stop and go, and whether it's TSS2.
# On the RAV4, it describes the move to the radar doing ACC, and the use of LTA for lane keeping.
# - abs: differentiates hybrid/ICE on most cars (Corolla TSS2 is an exception)
# - eps: describes lateral API changes for the EPS, such as using LTA for lane keeping and rejecting LKA messages
PLATFORM_CODE_ECUS = [Ecu.fwdCamera, Ecu.abs, Ecu.eps]
# Some ECUs that use KWP2000 have their FW versions on non-standard data identifiers. # Some ECUs that use KWP2000 have their FW versions on non-standard data identifiers.

@ -629,8 +629,8 @@ class Controls:
if CC.latActive: if CC.latActive:
steer = clip(self.sm['testJoystick'].axes[1], -1, 1) steer = clip(self.sm['testJoystick'].axes[1], -1, 1)
# max angle is 45 for angle-based cars # max angle is 45 for angle-based cars, max curvature is 0.02
actuators.steer, actuators.steeringAngleDeg = steer, steer * 45. actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * 45., steer * -0.02
lac_log.active = self.active lac_log.active = self.active
lac_log.steeringAngleDeg = CS.steeringAngleDeg lac_log.steeringAngleDeg = CS.steeringAngleDeg

@ -7,7 +7,6 @@ from typing import Any
import cereal.messaging as messaging import cereal.messaging as messaging
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.spinner import Spinner
from openpilot.system.hardware import PC from openpilot.system.hardware import PC
from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.manager.process_config import managed_processes
from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url
@ -105,14 +104,6 @@ def nav_model_replay(lr):
def model_replay(lr, frs): def model_replay(lr, frs):
if not PC:
spinner = Spinner()
spinner.update("starting model replay")
else:
spinner = None
log_msgs = []
# modeld is using frame pairs # modeld is using frame pairs
modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx"}) modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx"})
dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx"}) dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx"})
@ -128,18 +119,9 @@ def model_replay(lr, frs):
modeld = get_process_config("modeld") modeld = get_process_config("modeld")
dmonitoringmodeld = get_process_config("dmonitoringmodeld") dmonitoringmodeld = get_process_config("dmonitoringmodeld")
try: modeld_msgs = replay_process(modeld, modeld_logs, frs)
if spinner: dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
spinner.update("running model replay") return modeld_msgs + dmonitoringmodeld_msgs
modeld_msgs = replay_process(modeld, modeld_logs, frs)
dmonitoringmodeld_msgs = replay_process(dmonitoringmodeld, dmodeld_logs, frs)
log_msgs.extend([m for m in modeld_msgs if m.which() == "modelV2"])
log_msgs.extend([m for m in dmonitoringmodeld_msgs if m.which() == "driverStateV2"])
finally:
if spinner:
spinner.close()
return log_msgs
if __name__ == "__main__": if __name__ == "__main__":
@ -196,8 +178,8 @@ if __name__ == "__main__":
cmp_log = [] cmp_log = []
# logs are ordered based on type: modelV2, driverStateV2, nav messages (navThumbnail, mapRenderState, navModel) # logs are ordered based on type: modelV2, driverStateV2, nav messages (navThumbnail, mapRenderState, navModel)
model_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "modelV2") model_start_index = next(i for i, m in enumerate(all_logs) if m.which() in ("modelV2", "cameraOdometry"))
cmp_log += all_logs[model_start_index:model_start_index + MAX_FRAMES] cmp_log += all_logs[model_start_index:model_start_index + MAX_FRAMES*2]
dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2") dmon_start_index = next(i for i, m in enumerate(all_logs) if m.which() == "driverStateV2")
cmp_log += all_logs[dmon_start_index:dmon_start_index + MAX_FRAMES] cmp_log += all_logs[dmon_start_index:dmon_start_index + MAX_FRAMES]
if not NO_NAV: if not NO_NAV:

@ -1 +1 @@
2af877ca0ce6996a4c1cf54cd11abf5a1f4e0576 3bb23e270a3a7219cfeedadd8d085c32fc571c0d

@ -283,7 +283,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
void DevicePanel::updateCalibDescription() { void DevicePanel::updateCalibDescription() {
QString desc = QString desc =
tr("openpilot requires the device to be mounted within 4° left or right and " tr("openpilot requires the device to be mounted within 4° left or right and "
"within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."); "within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.");
std::string calib_bytes = params.get("CalibrationParams"); std::string calib_bytes = params.get("CalibrationParams");
if (!calib_bytes.empty()) { if (!calib_bytes.empty()) {
try { try {

@ -226,8 +226,8 @@
<translation>أطفاء</translation> <translation>أطفاء</translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>يتطلب openpilot أن يتم تركيب الجهاز في حدود 4 درجات يسارًا أو يمينًا و 5 درجات لأعلى أو 8 درجات لأسفل. يقوم برنامج openpilot بالمعايرة بشكل مستمر ، ونادراً ما تكون إعادة الضبط مطلوبة.</translation> <translation>يتطلب openpilot أن يتم تركيب الجهاز في حدود 4 درجات يسارًا أو يمينًا و 5 درجات لأعلى أو 9 درجات لأسفل. يقوم برنامج openpilot بالمعايرة بشكل مستمر ، ونادراً ما تكون إعادة الضبط مطلوبة.</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation>Ausschalten</translation> <translation>Ausschalten</translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 8° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig.</translation> <translation>Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 9° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig.</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -234,8 +234,8 @@
<translation>Éteindre</translation> <translation>Éteindre</translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot nécessite que l&apos;appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 8° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire.</translation> <translation>openpilot nécessite que l&apos;appareil soit monté à 4° à gauche ou à droite et à 5° vers le haut ou 9° vers le bas. openpilot se calibre en continu, la réinitialisation est rarement nécessaire.</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilotの本体は4°5°8°</translation> <translation>openpilotの本体は4°5°9°</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot 4° , 5° 8° . openpilot은 .</translation> <translation>openpilot 4° , 5° 9° . openpilot은 .</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation>Desligar</translation> <translation>Desligar</translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário.</translation> <translation>O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 9° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário.</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot 4° 5° 8° openpilot </translation> <translation>openpilot 4° 5° 9° openpilot </translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation>Sistemi kapat</translation> <translation>Sistemi kapat</translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot, cihazın 4° sola veya 5° yukarı yada 8° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir.</translation> <translation>openpilot, cihazın 4° sola veya 5° yukarı yada 9° aşağı bakıcak şekilde monte edilmesi gerekmektedir. openpilot sürekli kendisini kalibre edilmektedir ve nadiren sıfırlama gerebilir.</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot要求设备安装的偏航角在左4°4°5°8°openpilot会持续更新校准</translation> <translation>openpilot要求设备安装的偏航角在左4°4°5°9°openpilot会持续更新校准</translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

@ -226,8 +226,8 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot 4° 5° 8° </translation> <translation>openpilot 4° 5° 9° </translation>
</message> </message>
<message> <message>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>

Loading…
Cancel
Save