IQ.Pilot Release Commit @ 9fe0487
This commit is contained in:
3
iqdbc_repo/iqdbc/lvbs/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
22
iqdbc_repo/iqdbc/lvbs/aol_base.py
Normal file
22
iqdbc_repo/iqdbc/lvbs/aol_base.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from abc import abstractmethod, ABC
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
|
||||
class AolCarStateBase(ABC):
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.lkas_button = 0
|
||||
self.prev_lkas_button = 0
|
||||
|
||||
@abstractmethod
|
||||
def update_aol(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
pass
|
||||
38
iqdbc_repo/iqdbc/lvbs/car/__init__.py
Normal file
38
iqdbc_repo/iqdbc/lvbs/car/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
|
||||
def crc8_pedal(data):
|
||||
crc = 0xFF # standard init value
|
||||
poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1
|
||||
size = len(data)
|
||||
for i in range(size - 1, -1, -1):
|
||||
crc ^= data[i]
|
||||
for _ in range(8):
|
||||
if (crc & 0x80) != 0:
|
||||
crc = ((crc << 1) ^ poly) & 0xFF
|
||||
else:
|
||||
crc <<= 1
|
||||
return crc
|
||||
|
||||
|
||||
def create_gas_interceptor_command(packer, gas_amount, idx):
|
||||
# Common gas pedal msg generator
|
||||
enable = gas_amount > 0.001
|
||||
|
||||
values = {
|
||||
"ENABLE": enable,
|
||||
"PEDAL_COUNTER": idx & 0xF,
|
||||
}
|
||||
|
||||
if enable:
|
||||
values["GAS_COMMAND"] = gas_amount * 255.
|
||||
values["GAS_COMMAND2"] = gas_amount * 255.
|
||||
|
||||
dat = packer.make_can_msg("GAS_COMMAND", 0, values)[1]
|
||||
|
||||
checksum = crc8_pedal(dat[:-1])
|
||||
values["PEDAL_CHECKSUM"] = checksum
|
||||
|
||||
return packer.make_can_msg("GAS_COMMAND", 0, values)
|
||||
4482
iqdbc_repo/iqdbc/lvbs/car/car_list.json
Normal file
4482
iqdbc_repo/iqdbc/lvbs/car/car_list.json
Normal file
File diff suppressed because it is too large
Load Diff
3
iqdbc_repo/iqdbc/lvbs/car/chrysler/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/chrysler/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
89
iqdbc_repo/iqdbc/lvbs/car/chrysler/aol.py
Normal file
89
iqdbc_repo/iqdbc/lvbs/car/chrysler/aol.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from collections import namedtuple
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.car.chrysler.values import RAM_CARS
|
||||
|
||||
from iqdbc.lvbs.aol_base import AolCarStateBase
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
AolDataIQ = namedtuple("AolDataIQ",
|
||||
["enable_aol", "paused", "lkas_disabled"])
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class AolCarController:
|
||||
def __init__(self):
|
||||
self.aol = AolDataIQ(False, False, False)
|
||||
|
||||
@staticmethod
|
||||
def create_lkas_heartbit(packer, lkas_heartbit, aol):
|
||||
# LKAS_HEARTBIT (0x2D9) LKAS heartbeat
|
||||
values = {s: lkas_heartbit[s] for s in [
|
||||
"LKAS_DISABLED",
|
||||
"AUTO_HIGH_BEAM",
|
||||
"FORWARD_1",
|
||||
"FORWARD_2",
|
||||
"FORWARD_3",
|
||||
]}
|
||||
|
||||
if aol.enable_aol:
|
||||
values["LKAS_DISABLED"] = 1 if aol.lkas_disabled else 0
|
||||
|
||||
return packer.make_can_msg("LKAS_HEARTBIT", 0, values)
|
||||
|
||||
@staticmethod
|
||||
def aol_status_update(CC: structs.CarControl, CC_IQ: structs.IQCarControl, CS) -> AolDataIQ:
|
||||
enable_aol = CC_IQ.aol.available
|
||||
paused = CC_IQ.aol.enabled and not CC.latActive
|
||||
|
||||
if any(be.type == ButtonType.lkas and be.pressed for be in CS.out.buttonEvents):
|
||||
CS.lkas_disabled = not CS.lkas_disabled
|
||||
|
||||
return AolDataIQ(enable_aol, paused, CS.lkas_disabled)
|
||||
|
||||
def update(self, CC: structs.CarControl, CC_IQ: structs.IQCarControl, CS) -> None:
|
||||
self.aol = self.aol_status_update(CC, CC_IQ, CS)
|
||||
|
||||
|
||||
class AolCarState(AolCarStateBase):
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
super().__init__(CP, CP_IQ)
|
||||
self.lkas_heartbit = 0
|
||||
|
||||
self.init_lkas_disabled = False
|
||||
self.lkas_disabled = False
|
||||
|
||||
@staticmethod
|
||||
def get_parser(CP, pt_messages, cam_messages) -> None:
|
||||
if CP.carFingerprint in RAM_CARS:
|
||||
pt_messages += [
|
||||
("Center_Stack_2", 1),
|
||||
]
|
||||
else:
|
||||
pt_messages.append(("TRACTION_BUTTON", 1))
|
||||
cam_messages.append(("LKAS_HEARTBIT", 1))
|
||||
|
||||
def get_lkas_button(self, cp, cp_cam):
|
||||
if self.CP.carFingerprint in RAM_CARS:
|
||||
lkas_button = cp.vl["Center_Stack_2"]["LKAS_Button"]
|
||||
else:
|
||||
lkas_button = cp.vl["TRACTION_BUTTON"]["TOGGLE_LKAS"]
|
||||
self.lkas_heartbit = cp_cam.vl["LKAS_HEARTBIT"]
|
||||
if not self.init_lkas_disabled:
|
||||
self.lkas_disabled = cp_cam.vl["LKAS_HEARTBIT"]["LKAS_DISABLED"]
|
||||
self.init_lkas_disabled = True
|
||||
|
||||
return lkas_button
|
||||
|
||||
def update_aol(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
self.prev_lkas_button = self.lkas_button
|
||||
self.lkas_button = self.get_lkas_button(cp, cp_cam)
|
||||
26
iqdbc_repo/iqdbc/lvbs/car/chrysler/carcontroller_ext.py
Normal file
26
iqdbc_repo/iqdbc/lvbs/car/chrysler/carcontroller_ext.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.interfaces import CarStateBase
|
||||
from iqdbc.car.chrysler.values import RAM_DT
|
||||
from iqdbc.lvbs.car.chrysler.values_ext import ChryslerFlagsIQ
|
||||
|
||||
GearShifter = structs.CarState.GearShifter
|
||||
|
||||
|
||||
class CarControllerExt:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
def get_lkas_control_bit(self, CS: CarStateBase, CC: structs.CarControl, lkas_control_bit: bool) -> bool:
|
||||
if self.CP_IQ.flags & ChryslerFlagsIQ.NO_MIN_STEERING_SPEED:
|
||||
lkas_control_bit = CC.latActive
|
||||
elif self.CP.carFingerprint in RAM_DT:
|
||||
if self.CP.minEnableSpeed <= CS.out.vEgo <= self.CP.minEnableSpeed + 0.5:
|
||||
lkas_control_bit = True
|
||||
if self.CP.minEnableSpeed >= 14.5 and CS.out.gearShifter != GearShifter.drive:
|
||||
lkas_control_bit = False
|
||||
|
||||
return lkas_control_bit
|
||||
37
iqdbc_repo/iqdbc/lvbs/car/chrysler/carstate_ext.py
Normal file
37
iqdbc_repo/iqdbc/lvbs/car/chrysler/carstate_ext.py
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car.chrysler.values import RAM_HD
|
||||
from iqdbc.lvbs.car.chrysler.values_ext import BUTTONS
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.button_events = []
|
||||
self.button_states = {button.event_type: False for button in BUTTONS}
|
||||
|
||||
def update(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser]):
|
||||
cp = can_parsers[Bus.pt]
|
||||
|
||||
button_events = []
|
||||
for button in BUTTONS:
|
||||
state = (cp.vl[button.can_addr][button.can_msg] in button.values)
|
||||
if self.button_states[button.event_type] != state:
|
||||
event = structs.CarState.ButtonEvent.new_message()
|
||||
event.type = button.event_type
|
||||
event.pressed = state
|
||||
button_events.append(event)
|
||||
self.button_states[button.event_type] = state
|
||||
self.button_events = button_events
|
||||
|
||||
if self.CP.carFingerprint in RAM_HD:
|
||||
ret.steeringAngleDeg = cp.vl["STEERING"]["STEERING_ANGLE"]
|
||||
27
iqdbc_repo/iqdbc/lvbs/car/chrysler/fingerprints_ext.py
Normal file
27
iqdbc_repo/iqdbc/lvbs/car/chrysler/fingerprints_ext.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.chrysler.values import CAR
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
|
||||
FW_VERSIONS_EXT = {
|
||||
CAR.RAM_1500_5TH_GEN: {
|
||||
(Ecu.combinationMeter, 0x742, None): [
|
||||
b'68453485AC',
|
||||
b'68510283AH',
|
||||
],
|
||||
(Ecu.eps, 0x75a, None): [
|
||||
b'68552791AA',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'05149390AA ',
|
||||
b'68378696AI ',
|
||||
b'68500631AF',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'68360085AF',
|
||||
b'68360086AL',
|
||||
b'68502996AC',
|
||||
],
|
||||
},
|
||||
}
|
||||
24
iqdbc_repo/iqdbc/lvbs/car/chrysler/values_ext.py
Normal file
24
iqdbc_repo/iqdbc/lvbs/car/chrysler/values_ext.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from enum import IntFlag
|
||||
|
||||
from iqdbc.car import structs
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
|
||||
|
||||
BUTTONS = [
|
||||
Button(ButtonType.accelCruise, "CRUISE_BUTTONS", "ACC_Accel", [1]),
|
||||
Button(ButtonType.decelCruise, "CRUISE_BUTTONS", "ACC_Decel", [1]),
|
||||
Button(ButtonType.cancel, "CRUISE_BUTTONS", "ACC_Cancel", [1]),
|
||||
Button(ButtonType.resumeCruise, "CRUISE_BUTTONS", "ACC_Resume", [1]),
|
||||
]
|
||||
|
||||
|
||||
class ChryslerFlagsIQ(IntFlag):
|
||||
NO_MIN_STEERING_SPEED = 1
|
||||
|
||||
|
||||
36
iqdbc_repo/iqdbc/lvbs/car/fingerprints_ext.py
Normal file
36
iqdbc_repo/iqdbc/lvbs/car/fingerprints_ext.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
|
||||
def extend_fw_versions(fw_versions, new_fw_versions):
|
||||
"""
|
||||
Merge firmware versions by extending lists for matching ECUs,
|
||||
adding all entries regardless of duplicates.
|
||||
"""
|
||||
for c, f in new_fw_versions.items():
|
||||
if c not in fw_versions:
|
||||
fw_versions[c] = f
|
||||
continue
|
||||
|
||||
for e, new_fw_list in f.items():
|
||||
if e not in fw_versions[c]:
|
||||
fw_versions[c][e] = new_fw_list
|
||||
else:
|
||||
fw_versions[c][e].extend(new_fw_list)
|
||||
|
||||
return fw_versions
|
||||
|
||||
|
||||
def extend_fingerprints(fingerprints, new_fingerprints):
|
||||
"""
|
||||
Merge fingerprints by extending lists for matching keys,
|
||||
adding all entries regardless of duplicates.
|
||||
"""
|
||||
for car, fp_list in new_fingerprints.items():
|
||||
if car not in fingerprints:
|
||||
fingerprints[car] = fp_list
|
||||
else:
|
||||
fingerprints[car].extend(fp_list)
|
||||
|
||||
return fingerprints
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/ford/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/ford/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
21
iqdbc_repo/iqdbc/lvbs/car/ford/aol.py
Normal file
21
iqdbc_repo/iqdbc/lvbs/car/ford/aol.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus,structs
|
||||
|
||||
from iqdbc.lvbs.aol_base import AolCarStateBase
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
|
||||
class AolCarState(AolCarStateBase):
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
super().__init__(CP, CP_IQ)
|
||||
|
||||
def update_aol(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
|
||||
self.prev_lkas_button = self.lkas_button
|
||||
self.lkas_button = cp.vl["Steering_Data_FD1"]["TjaButtnOnOffPress"]
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/gm/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/gm/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
23
iqdbc_repo/iqdbc/lvbs/car/gm/carstate_ext.py
Normal file
23
iqdbc_repo/iqdbc/lvbs/car/gm/carstate_ext.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.car.common.conversions import Conversions as CV
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.lvbs.car.gm.values_ext import GMFlagsIQ
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
def update(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
pt_cp = can_parsers[Bus.pt]
|
||||
|
||||
if self.CP_IQ.flags & GMFlagsIQ.NON_ACC:
|
||||
ret.cruiseState.enabled = pt_cp.vl["ECMCruiseControl"]["CruiseActive"] != 0
|
||||
ret.cruiseState.speed = pt_cp.vl["ECMCruiseControl"]["CruiseSetSpeed"] * CV.KPH_TO_MS
|
||||
ret.accFaulted = False
|
||||
119
iqdbc_repo/iqdbc/lvbs/car/gm/fingerprints_ext.py
Normal file
119
iqdbc_repo/iqdbc/lvbs/car/gm/fingerprints_ext.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
# ruff: noqa: E501
|
||||
from iqdbc.car.gm.values import CAR
|
||||
|
||||
FINGERPRINTS_EXT = {
|
||||
# FIXME-IQ: Need user validation
|
||||
# CAR.CHEVROLET_VOLT: [{
|
||||
# 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7
|
||||
# }],
|
||||
# FIXME-IQ: Need a message to distinguish flashed from non-flashed
|
||||
# CAR.CHEVROLET_VOLT_CC: [
|
||||
# Volt Premier w/o acc 2016
|
||||
# {
|
||||
# 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 8, 308: 4, 309: 8, 311: 8, 313: 8, 320: 8, 328: 1, 352: 5, 368: 8, 381: 6, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 4, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 8, 563: 5, 564: 5, 565: 8, 566: 5, 567: 3, 568: 1, 577: 8, 578: 8, 594: 8, 647: 3, 707: 8, 711: 6, 717: 5, 761: 7, 800: 6, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 6, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1618: 8, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 1930: 7, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2024: 8, 2025: 8, 2028: 8
|
||||
# },
|
||||
# {
|
||||
# 201: 8, 493: 8, 495: 4, 193: 8, 197: 8, 209: 7, 171: 8, 456: 8, 199: 4, 489: 8, 211: 2, 499: 3, 390: 7, 532: 6, 568: 1, 761: 7, 381: 6, 485: 8, 189: 7, 479: 3, 711: 6, 501: 8, 241: 6, 717: 5, 869: 4, 389: 2, 454: 8, 170: 8, 190: 6, 497: 8, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 500: 6, 508: 8, 528: 4, 647: 3, 1105: 6, 1005: 6, 481: 7, 844: 8, 866: 4, 564: 5, 969: 8, 388: 8, 352: 5, 562: 8, 961: 8, 386: 8, 707: 8, 977: 8, 979: 7, 298: 8, 840: 5, 842: 5, 988: 6, 1001: 8, 560: 8, 546: 7, 558: 8, 309: 8, 995: 7, 311: 8, 566: 5, 567:3, 989: 8, 384: 4, 800: 6, 1033: 7, 1034: 7, 313: 8, 554: 3, 810: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 1223: 3, 1233: 8, 1227: 4, 1417: 8, 1009: 8, 1221: 5, 1275: 3, 1225: 7, 289: 8, 550: 8, 1273: 3, 1928: 7, 1187: 4, 1265: 8, 1927: 7, 1267: 1, 1906: 7, 288: 5, 304: 1, 328: 1, 1912: 7, 320: 3, 1910: 7, 563: 5, 1249: 8, 1930: 7, 1257: 6, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 565: 5, 1280: 4, 1907: 7
|
||||
# },
|
||||
# # Volt Premier w/o ACC 2018 + Pedal
|
||||
# {
|
||||
# 189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 513: 6, 528: 4, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7, 1930: 7
|
||||
# }
|
||||
# ],
|
||||
CAR.CHEVROLET_BOLT_NON_ACC: [
|
||||
# Bolt Premier w/o ACC 2017
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 201: 6, 209: 7, 211: 2, 241: 6, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 311: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 5, 353: 3, 368: 8, 381: 6, 384: 8, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 512: 3, 513: 6, 514: 2, 516: 4, 519: 2, 521: 3, 528: 5, 530: 8, 532: 7, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 8, 566: 6, 567: 5, 568: 1, 569: 3, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 832: 8, 840: 6, 842: 6, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1601: 8, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1927: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8
|
||||
},
|
||||
# Bolt EV Premier 2017
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 201: 6, 209: 7, 211: 2, 241: 6, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 311: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 5, 353: 3, 368: 8, 381: 6, 384: 8, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 513: 6, 512: 3, 514: 2, 516: 4, 519: 2, 521: 3, 528: 5, 530: 8, 532: 7, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 8, 566: 6, 567: 5, 568: 1, 569: 3, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 832: 8, 840: 6, 842: 6, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1601: 8, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1927: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8
|
||||
},
|
||||
# Bolt EV Premier 2017 w Pedal
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 201: 6, 209: 7, 211: 2, 241: 6, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 311: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 5, 353: 3, 368: 8, 381: 6, 384: 8, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 512: 3, 512: 6, 513: 6, 514: 2, 516: 4, 519: 2, 521: 3, 528: 5, 530: 8, 532: 7, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 8, 566: 6, 567: 5, 568: 1, 569: 3, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 832: 8, 840: 6, 842: 6, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1601: 8, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1927: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8 # noqa: F601
|
||||
},
|
||||
# Bolt EV Premier 2017 2 w Pedal
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 6, 567: 5, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7
|
||||
},
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 6, 567: 5, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8
|
||||
}],
|
||||
CAR.CHEVROLET_BOLT_NON_ACC_1ST_GEN: [
|
||||
# Bolt Premier no ACC 2018 + Pedal
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 6, 567: 5, 568: 1, 573: 1, 577: 8, 592: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1601: 8, 1616: 8, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7, 2020: 8, 2023: 8, 2028: 8, 2031: 8
|
||||
},
|
||||
# Bolt Premier no ACC 2019 + Pedal
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 512: 6, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 7, 567: 5, 568: 2, 569: 3, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1037: 5, 1105: 5, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1268: 2, 1275: 3, 1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1927: 7, 2016: 8, 2024: 8
|
||||
},
|
||||
# Bolt Premier no ACC 2020
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 7, 567: 5, 568: 2, 569: 3, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1037: 5, 1105: 5, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1268: 2, 1275: 3, 1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1927: 7, 2016: 8, 2024: 8
|
||||
}],
|
||||
CAR.CHEVROLET_BOLT_NON_ACC_2ND_GEN: [
|
||||
# Bolt EV Premier no ACC 2023 w Pedal
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 308: 4, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 398: 8, 407: 7, 417: 8, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 8, 567: 5, 568: 2, 569: 3, 573: 1, 577: 8, 592: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 711: 6, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 880: 6, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1010: 8, 1013: 6, 1015: 1, 1017: 8, 1019: 2, 1020: 8, 1037: 5, 1105: 5, 1187: 5, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1279: 4, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1601: 8, 1616: 8, 1618: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7, 1930: 7, 2016: 8, 2020: 8, 2023: 8, 2024: 8, 2028: 8, 2031: 8
|
||||
},
|
||||
# Bolt EV Premier no ACC 2021
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 257: 8, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 407: 7, 417: 7, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 7, 567: 5, 568: 1, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 592: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 717: 5, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 7, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1037: 5, 1105: 5, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1243: 3, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1345: 8, 1346: 8, 1347: 8, 1513: 8, 1516: 8, 1601: 8, 1616: 8, 1904: 7, 1905: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1922: 7, 1927: 7, 2016: 8, 2017: 8, 2018: 8, 2020: 8, 2023: 8, 2024: 8, 2028: 8, 2031: 8
|
||||
},
|
||||
# shermy99's Bolt EV Premier no ACC 2023
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 308: 4, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 353: 3, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 398: 8, 407: 7, 417: 8, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 8, 567: 5, 568: 2, 569: 3, 573: 1, 577: 8, 579: 8, 592: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 711: 6, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 880: 6, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1010: 8, 1013: 6, 1015: 1, 1017: 8, 1019: 2, 1020: 8, 1037: 5, 1105: 5, 1187: 5, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1279: 4, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1345: 8, 1347: 8, 1513: 8, 1516: 8, 1601: 8, 1609: 8, 1613: 8, 1616: 8, 1618: 8, 1649: 8, 1792: 8, 1793: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1913: 7, 1920: 8, 1922: 7, 1924: 8, 1927: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1984: 8, 1988: 8, 2000: 8, 2001: 8, 2002: 8, 2017: 8, 2018: 8, 2020: 8, 2023: 8, 2025: 8, 2028: 8, 2031: 8
|
||||
},
|
||||
{
|
||||
170: 8, 188: 8, 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 353: 3, 381: 8, 384: 4, 386: 8, 388: 8, 390: 7, 398: 8, 407: 7, 417: 8, 419: 1, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 2, 508: 8, 513: 6, 528: 5, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 564: 5, 566: 8, 567: 5, 568: 2, 569: 3, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 711: 6, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 866: 4, 869: 4, 872: 1, 880: 6, 961: 8, 967: 4, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 988: 6, 989: 8, 995: 7, 1001: 8, 1009: 8, 1010: 8, 1013: 6, 1015: 1, 1017: 8, 1019: 2, 1020: 8, 1105: 5, 1187: 5, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1265: 8, 1275: 3, 1279: 4, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1913: 7, 1927: 7, 1930: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8
|
||||
}],
|
||||
CAR.CHEVROLET_EQUINOX_NON_ACC_3RD_GEN: [{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 444: 7, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1273: 3, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1611: 8, 1618: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1920: 7, 1930: 7
|
||||
},
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 384: 4, 393: 8, 398: 8, 401: 8, 413: 8, 422: 4, 431: 8, 444: 7, 453: 6, 456: 8, 479: 3, 481: 7, 485: 8, 499: 3, 500: 6, 501: 8, 567: 5, 647: 6, 800: 6, 1033: 7, 1034: 7, 1296: 4
|
||||
}],
|
||||
# Trailblazer also matches as a Silverado, so comment out to avoid conflicts.
|
||||
# TODO-IQ: split with FW versions
|
||||
# CAR.TRAILBLAZER: [
|
||||
# {
|
||||
# 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8
|
||||
# }],
|
||||
CAR.CHEVROLET_SUBURBAN_NON_ACC_11TH_GEN: [
|
||||
# Slav's 2018 Suburban, LKAS no ACC
|
||||
{
|
||||
170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 493: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1920: 7
|
||||
},
|
||||
# Qube's 2017 Suburban, LKAS no ACC
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 534: 2, 562: 8, 563: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1793: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1871: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1920: 8, 1924: 8, 1927: 8, 1937: 8, 1953: 8, 1954: 8, 1955: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1984: 8, 1988: 8, 1990: 8, 2000: 8, 2001: 8, 2004: 8, 2017: 8, 2018: 8, 2020: 8
|
||||
}],
|
||||
CAR.CADILLAC_CT6_NON_ACC_1ST_GEN: [
|
||||
# badgers4life's 2017 CT6
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 4, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 3, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 717: 5, 723: 2, 753: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 977: 8, 979: 7, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 1, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 7, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1793: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 4, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 8, 1924: 8, 1927: 8, 1928: 7, 1937: 8, 1953: 8, 1954: 8, 1955: 8, 1968: 8, 1969: 8, 1971: 8, 1975: 8, 1984: 8, 1988: 8, 2000: 8, 2001: 8, 2002: 8, 2004: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8
|
||||
}],
|
||||
CAR.CHEVROLET_TRAILBLAZER_NON_ACC_2ND_GEN: [
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 292: 2, 298: 8, 304: 3, 309: 8, 313: 8, 320: 4, 322: 7, 328: 1, 331: 3, 352: 5, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 717: 5, 723: 4, 730: 4, 761: 7, 800: 6, 840: 5, 842: 5, 844: 8, 869: 4, 961: 8, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 6, 1017: 8, 1020: 8, 1037: 5, 1105: 5, 1187: 5, 1195: 3, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1268: 2, 1271: 8, 1273: 3, 1276: 2, 1277: 7, 1278: 4, 1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7
|
||||
}],
|
||||
CAR.CHEVROLET_MALIBU_NON_ACC_9TH_GEN: [
|
||||
# Verylukyguy's Malibu
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 368: 3, 381: 8, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 409: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 717: 5, 730: 4, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 961: 8, 969: 8, 975: 2, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 6, 1017: 8, 1020: 8, 1037: 5, 1105: 5, 1187: 6, 1189: 1, 1195: 3, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1236: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1268: 2, 1271: 8, 1273: 3, 1279: 4, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7
|
||||
},
|
||||
# Tesla's Malibu
|
||||
{
|
||||
189: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 6, 390: 8, 392: 4, 393: 4, 394: 8, 395: 3, 396: 4, 397: 4, 398: 4, 399: 4, 400: 4, 401: 4, 402: 4, 403: 4, 404: 4, 405: 4, 406: 4, 407: 4, 408: 4, 409: 4, 410: 4, 411: 4, 412: 4, 413: 4, 414: 4, 415: 4, 416: 4, 417: 4, 418: 4, 419: 4, 420: 4, 421: 4, 422: 4, 423: 4, 424: 4, 425: 4, 426: 4, 427: 4, 428: 4, 429: 4, 430: 4, 431: 4, 432: 4, 433: 4, 434: 4, 435: 4, 436: 4, 437: 4, 438: 4, 439: 4, 440: 4, 441: 4, 442: 4, 443: 4, 444: 4, 445: 4, 446: 4, 447: 4, 448: 4, 449: 4, 450: 4, 451: 8, 452: 8, 453: 6, 479: 4, 481: 7, 485: 8, 489: 8, 493: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 6, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 717: 5, 761: 7, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1922: 7
|
||||
}],
|
||||
CAR.CADILLAC_XT5_NON_ACC_1ST_GEN: [
|
||||
# TRain's 2017 XT5
|
||||
{
|
||||
190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 647: 3, 707: 8, 717: 5, 723: 2, 753: 5, 761: 7, 800: 6, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 872: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1919: 7, 1920: 7
|
||||
}],
|
||||
}
|
||||
58
iqdbc_repo/iqdbc/lvbs/car/gm/interface_ext.py
Normal file
58
iqdbc_repo/iqdbc/lvbs/car/gm/interface_ext.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import os
|
||||
from math import exp
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.common.basedir import BASEDIR
|
||||
from iqdbc.car.gm.interface import CAR
|
||||
from iqdbc.lvbs.car.interfaces import LatControlInputs, NanoFFModel, TorqueFromLateralAccelCallbackTypeTorqueSpace
|
||||
|
||||
NON_LINEAR_TORQUE_PARAMS = {
|
||||
CAR.CHEVROLET_BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178],
|
||||
CAR.GMC_ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772],
|
||||
CAR.CHEVROLET_SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122]
|
||||
}
|
||||
|
||||
|
||||
class CarInterfaceExt:
|
||||
def __init__(self, CP: structs.CarParams, CI_Base):
|
||||
self.CP = CP
|
||||
self.CI_Base = CI_Base
|
||||
self.neural_ff_model = None
|
||||
|
||||
def torque_from_lateral_accel_siglin(self, latcontrol_inputs: LatControlInputs, torque_params: structs.CarParams.LateralTorqueTuning,
|
||||
gravity_adjusted: bool) -> float:
|
||||
def sig(val):
|
||||
# https://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick
|
||||
if val >= 0:
|
||||
return 1 / (1 + exp(-val)) - 0.5
|
||||
else:
|
||||
z = exp(val)
|
||||
return z / (1 + z) - 0.5
|
||||
|
||||
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
|
||||
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
|
||||
# This has big effect on the stability about 0 (noise when going straight)
|
||||
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear
|
||||
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
|
||||
assert non_linear_torque_params, "The params are not defined"
|
||||
a, b, c, _ = non_linear_torque_params
|
||||
steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c)
|
||||
return float(steer_torque)
|
||||
|
||||
def torque_from_lateral_accel_neural(self, latcontrol_inputs: LatControlInputs, orque_params: structs.CarParams.LateralTorqueTuning,
|
||||
gravity_adjusted: bool) -> float:
|
||||
inputs = list(latcontrol_inputs)
|
||||
if gravity_adjusted:
|
||||
inputs[0] += inputs[1]
|
||||
return float(self.neural_ff_model.predict(inputs))
|
||||
|
||||
def torque_from_lateral_accel_in_torque_space(self) -> TorqueFromLateralAccelCallbackTypeTorqueSpace:
|
||||
if self.CP.carFingerprint == CAR.CHEVROLET_BOLT_EUV:
|
||||
return self.torque_from_lateral_accel_neural
|
||||
elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
|
||||
return self.torque_from_lateral_accel_siglin
|
||||
else:
|
||||
return self.CI_Base.torque_from_lateral_accel_linear_in_torque_space
|
||||
15
iqdbc_repo/iqdbc/lvbs/car/gm/values_ext.py
Normal file
15
iqdbc_repo/iqdbc/lvbs/car/gm/values_ext.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class GMFlagsIQ(IntFlag):
|
||||
NON_ACC = 1
|
||||
|
||||
|
||||
class GMSafetyFlagsIQ:
|
||||
NON_ACC = 1
|
||||
|
||||
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/honda/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/honda/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
19
iqdbc_repo/iqdbc/lvbs/car/honda/aol.py
Normal file
19
iqdbc_repo/iqdbc/lvbs/car/honda/aol.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.honda.values import HONDA_BOSCH_RADARLESS
|
||||
|
||||
|
||||
class AolCarController:
|
||||
def __init__(self):
|
||||
self.dashed_lanes = False
|
||||
|
||||
def update(self, CP: structs.CarParams, CC: structs.CarControl, CC_IQ: structs.IQCarControl) -> None:
|
||||
enable_aol = CC_IQ.aol.available
|
||||
|
||||
if enable_aol:
|
||||
self.dashed_lanes = CC_IQ.aol.enabled and not CC.latActive
|
||||
else:
|
||||
self.dashed_lanes = CC.hudControl.lanesVisible if CP.carFingerprint in HONDA_BOSCH_RADARLESS else False
|
||||
30
iqdbc_repo/iqdbc/lvbs/car/honda/carstate_ext.py
Normal file
30
iqdbc_repo/iqdbc/lvbs/car/honda/carstate_ext.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.lvbs.car.honda.values_ext import HondaFlagsIQ
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
def update(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
if self.CP_IQ.flags & HondaFlagsIQ.NIDEC_HYBRID:
|
||||
ret.accFaulted = bool(cp.vl["HYBRID_BRAKE_ERROR"]["BRAKE_ERROR_1"] or cp.vl["HYBRID_BRAKE_ERROR"]["BRAKE_ERROR_2"])
|
||||
ret.stockAeb = bool(cp_cam.vl["BRAKE_COMMAND"]["AEB_REQ_1"] and cp_cam.vl["BRAKE_COMMAND"]["COMPUTER_BRAKE_HYBRID"] > 1e-5)
|
||||
|
||||
if self.CP_IQ.flags & HondaFlagsIQ.HYBRID_ALT_BRAKEHOLD:
|
||||
ret.brakeHoldActive = cp.vl["BRAKE_HOLD_HYBRID_ALT"]["BRAKE_HOLD_ACTIVE"] == 1
|
||||
|
||||
if self.CP_IQ.enableGasInterceptor and "GAS_SENSOR" in cp.vl:
|
||||
# Same threshold as panda, equivalent to 1e-5 with previous DBC scaling
|
||||
gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2
|
||||
ret.gasPressed = gas > 492
|
||||
56
iqdbc_repo/iqdbc/lvbs/car/honda/fingerprints_ext.py
Normal file
56
iqdbc_repo/iqdbc/lvbs/car/honda/fingerprints_ext.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.honda.values import CAR
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
FW_VERSIONS_EXT = {
|
||||
CAR.HONDA_ACCORD: {
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TVA,A150\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CIVIC: {
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TBA,A030\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CIVIC_BOSCH: {
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TGG,A020\x00\x00',
|
||||
b'39990-TGG,A120\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CRV_5G: {
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TLA,A040\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HONDA_CLARITY: {
|
||||
(Ecu.shiftByWire, 0x18da0bf1, None): [
|
||||
b'54008-TRW-A910\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TRW-A010\x00\x00',
|
||||
b'57114-TRW-A020\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TRW-A020\x00\x00',
|
||||
b'39990-TRW,A020\x00\x00', # modified firmware
|
||||
b'39990,TRW,A020\x00\x00', # extra modified firmware
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TRW-A210\x00\x00',
|
||||
b'77959-TRW-A220\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TRW-A010\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36161-TRW-A110\x00\x00',
|
||||
],
|
||||
},
|
||||
}
|
||||
32
iqdbc_repo/iqdbc/lvbs/car/honda/gas_interceptor.py
Normal file
32
iqdbc_repo/iqdbc/lvbs/car/honda/gas_interceptor.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.can_definitions import CanData
|
||||
from iqdbc.lvbs.car import create_gas_interceptor_command
|
||||
|
||||
|
||||
class GasInterceptorCarController:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.gas = 0.
|
||||
self.interceptor_gas_cmd = 0.
|
||||
|
||||
def update(self, CC: structs.CarControl, CS: structs.CarState, gas: float, brake: float, wind_brake: float,
|
||||
packer, frame: int) -> list[CanData]:
|
||||
can_sends = []
|
||||
|
||||
if self.CP_IQ.enableGasInterceptor:
|
||||
gas_pedal = np.interp(CS.out.vEgo, [0., 10.], [0.4, 1.0])
|
||||
if CC.longActive:
|
||||
self.gas = float(np.clip(gas_pedal * (gas - brake + wind_brake * 3 / 4), 0., 1.))
|
||||
else:
|
||||
self.gas = 0.0
|
||||
can_sends.append(create_gas_interceptor_command(packer, self.gas, frame // 2))
|
||||
|
||||
return can_sends
|
||||
26
iqdbc_repo/iqdbc/lvbs/car/honda/test_honda.py
Normal file
26
iqdbc_repo/iqdbc/lvbs/car/honda/test_honda.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from parameterized import parameterized
|
||||
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.car_helpers import interfaces
|
||||
from iqdbc.car.honda.values import CAR
|
||||
|
||||
CarFw = CarParams.CarFw
|
||||
|
||||
|
||||
class TestHondaEpsMod:
|
||||
|
||||
@parameterized.expand([(CAR.HONDA_CIVIC, b'39990-TBA,A030\x00\x00'), (CAR.HONDA_CIVIC, b'39990-TBA-A030\x00\x00'),
|
||||
(CAR.HONDA_CLARITY, b'39990-TRW-A020\x00\x00'), (CAR.HONDA_CLARITY, b'39990,TRW,A020\x00\x00')])
|
||||
def test_eps_mod_fingerprint(self, car_name, fw):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
car_fw = [CarFw(ecu="eps", fwVersion=fw)]
|
||||
|
||||
CarInterface = interfaces[car_name]
|
||||
CP = CarInterface.get_params(car_name, fingerprint, car_fw, False, False, False)
|
||||
_ = CarInterface.get_params_iq(CP, car_name, fingerprint, car_fw, False, False, False)
|
||||
|
||||
assert not CP.dashcamOnly
|
||||
18
iqdbc_repo/iqdbc/lvbs/car/honda/values_ext.py
Normal file
18
iqdbc_repo/iqdbc/lvbs/car/honda/values_ext.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class HondaFlagsIQ(IntFlag):
|
||||
NIDEC_HYBRID = 1
|
||||
EPS_MODIFIED = 2
|
||||
HYBRID_ALT_BRAKEHOLD = 4
|
||||
|
||||
|
||||
class HondaSafetyFlagsIQ:
|
||||
NIDEC_HYBRID = 1
|
||||
GAS_INTERCEPTOR = 2
|
||||
|
||||
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/hyundai/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/hyundai/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
105
iqdbc_repo/iqdbc/lvbs/car/hyundai/aol.py
Normal file
105
iqdbc_repo/iqdbc/lvbs/car/hyundai/aol.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from collections import namedtuple
|
||||
|
||||
from iqdbc.car import Bus, DT_CTRL, structs
|
||||
from iqdbc.car.hyundai.values import CAR
|
||||
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
from iqdbc.lvbs.aol_base import AolCarStateBase
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
AolDataIQ = namedtuple("AolDataIQ",
|
||||
["enable_aol", "lat_active", "disengaging", "paused"])
|
||||
|
||||
|
||||
class AolCarController:
|
||||
def __init__(self):
|
||||
self.aol = AolDataIQ(False, False, False, False)
|
||||
|
||||
self.lat_disengage_blink = 0
|
||||
self.lat_disengage_init = False
|
||||
self.prev_lat_active = False
|
||||
|
||||
self.lkas_icon = 0
|
||||
self.lfa_icon = 0
|
||||
|
||||
# display LFA "white_wheel" and LKAS "White car + lanes" when not CC.latActive
|
||||
def aol_status_update(self, CC: structs.CarControl, CC_IQ: structs.IQCarControl, frame: int) -> AolDataIQ:
|
||||
enable_aol = CC_IQ.aol.available
|
||||
|
||||
if CC.latActive:
|
||||
self.lat_disengage_init = False
|
||||
elif self.prev_lat_active:
|
||||
self.lat_disengage_init = True
|
||||
|
||||
if not self.lat_disengage_init:
|
||||
self.lat_disengage_blink = frame
|
||||
|
||||
paused = CC_IQ.aol.enabled and not CC.latActive
|
||||
disengaging = (frame - self.lat_disengage_blink) * DT_CTRL < 1.0 if self.lat_disengage_init else False
|
||||
|
||||
self.prev_lat_active = CC.latActive
|
||||
|
||||
return AolDataIQ(enable_aol, CC.latActive, disengaging, paused)
|
||||
|
||||
def create_lkas_icon(self, CP: structs.CarParams, enabled: bool) -> int:
|
||||
if self.aol.enable_aol:
|
||||
lkas_icon = 2 if self.aol.lat_active else 3 if self.aol.disengaging else 1
|
||||
else:
|
||||
lkas_icon = 2 if enabled else 1
|
||||
|
||||
# Override common signals for KIA_OPTIMA_G4 and KIA_OPTIMA_G4_FL
|
||||
if CP.carFingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.HYUNDAI_KONA_NON_SCC):
|
||||
lkas_icon = 3 if (self.aol.lat_active if self.aol.enable_aol else enabled) else 1
|
||||
|
||||
return lkas_icon
|
||||
|
||||
def create_lfa_icon(self, enabled: bool) -> int:
|
||||
if self.aol.enable_aol:
|
||||
lfa_icon = 2 if self.aol.lat_active else 3 if self.aol.disengaging else 1 if self.aol.paused else 0
|
||||
else:
|
||||
lfa_icon = 2 if enabled else 0
|
||||
|
||||
return lfa_icon
|
||||
|
||||
def update(self, CP: structs.CarParams, CC: structs.CarControl, CC_IQ: structs.IQCarControl, frame: int) -> None:
|
||||
self.aol = self.aol_status_update(CC, CC_IQ, frame)
|
||||
self.lkas_icon = self.create_lkas_icon(CP, CC.enabled)
|
||||
self.lfa_icon = self.create_lfa_icon(CC.enabled)
|
||||
|
||||
|
||||
class AolCarState(AolCarStateBase):
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.CarParams):
|
||||
super().__init__(CP, CP_IQ)
|
||||
self.main_cruise_enabled: bool = False
|
||||
|
||||
@staticmethod
|
||||
def get_parser(CP, CP_IQ, pt_messages) -> None:
|
||||
pass
|
||||
|
||||
def get_main_cruise(self, ret: structs.CarState) -> bool:
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE:
|
||||
if any(be.type == ButtonType.mainCruise and be.pressed for be in ret.buttonEvents):
|
||||
self.main_cruise_enabled = not self.main_cruise_enabled
|
||||
else:
|
||||
self.main_cruise_enabled = True
|
||||
|
||||
return self.main_cruise_enabled if ret.cruiseState.available else False
|
||||
|
||||
def update_aol(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
pass
|
||||
|
||||
def update_aol_canfd(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
|
||||
ret.cruiseState.available = cp_cruise_info.vl["SCC_CONTROL"]["MainMode_ACC"] == 1
|
||||
83
iqdbc_repo/iqdbc/lvbs/car/hyundai/carstate_ext.py
Normal file
83
iqdbc_repo/iqdbc/lvbs/car/hyundai/carstate_ext.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.aBasis = 0.0
|
||||
|
||||
def update_speed_limit(self, cp, cp_cam) -> float:
|
||||
speed_limit = 0
|
||||
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.SPEED_LIMIT_AVAILABLE:
|
||||
bus = cp if self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING else cp_cam
|
||||
speed_limit = bus.vl["FR_CMR_02_100ms"]["ISLW_SpdCluMainDis"]
|
||||
else:
|
||||
nav, cam = 0, 0
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.SPEED_LIMIT_AVAILABLE:
|
||||
nav = cp.vl["Navi_HU"]["SpeedLim_Nav_Clu"]
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.HAS_LKAS12:
|
||||
cam = cp_cam.vl["LKAS12"]["CF_Lkas_TsrSpeed_Display_Clu"]
|
||||
|
||||
speed_limit = cam if cam not in (0, 255) else nav
|
||||
|
||||
if speed_limit in (0, 255):
|
||||
speed_limit = 0
|
||||
|
||||
return speed_limit
|
||||
|
||||
def update(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser], speed_conv: float) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
self.aBasis = cp.vl["TCS13"]["aBasis"]
|
||||
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.NON_SCC:
|
||||
cruise_msg = "LABEL11" if self.CP.flags & HyundaiFlags.EV else \
|
||||
"E_CRUISE_CONTROL" if self.CP.flags & HyundaiFlags.HYBRID else \
|
||||
"EMS16"
|
||||
cruise_available_sig = "CC_React" if self.CP.flags & HyundaiFlags.EV else "CRUISE_LAMP_M"
|
||||
cruise_enabled_sig = "CC_ACT" if self.CP.flags & HyundaiFlags.EV else "CRUISE_LAMP_S"
|
||||
cruise_speed_msg = "E_EMS11" if self.CP.flags & HyundaiFlags.EV else \
|
||||
"ELECT_GEAR" if self.CP.flags & HyundaiFlags.HYBRID else \
|
||||
"LVR12"
|
||||
cruise_speed_sig = "Cruise_Limit_Target" if self.CP.flags & HyundaiFlags.EV else \
|
||||
"SLC_SET_SPEED" if self.CP.flags & HyundaiFlags.HYBRID else \
|
||||
"CF_Lvr_CruiseSet"
|
||||
ret.cruiseState.available = cp.vl[cruise_msg][cruise_available_sig] != 0
|
||||
ret.cruiseState.enabled = cp.vl[cruise_msg][cruise_enabled_sig] != 0
|
||||
ret.cruiseState.speed = cp.vl[cruise_speed_msg][cruise_speed_sig] * speed_conv
|
||||
ret.cruiseState.standstill = False
|
||||
ret.cruiseState.nonAdaptive = False
|
||||
|
||||
if not self.CP_IQ.flags & HyundaiFlagsIQ.NON_SCC_NO_FCA:
|
||||
cp_cruise = cp if self.CP_IQ.flags & HyundaiFlagsIQ.NON_SCC_RADAR_FCA else cp_cam
|
||||
|
||||
aeb_src = "FCA11"
|
||||
aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
|
||||
aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src]["FCA_CmdAct"] != 0
|
||||
ret.stockFcw = aeb_warning and not aeb_braking
|
||||
ret.stockAeb = aeb_warning and aeb_braking
|
||||
|
||||
ret_iq.speedLimit = self.update_speed_limit(cp, cp_cam) * speed_conv
|
||||
|
||||
def update_canfd_ext(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser],
|
||||
speed_factor: float) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
self.aBasis = cp.vl["TCS"]["aBasis"]
|
||||
|
||||
ret_iq.speedLimit = self.update_speed_limit(cp, cp_cam) * speed_factor
|
||||
71
iqdbc_repo/iqdbc/lvbs/car/hyundai/enable_radar_tracks.py
Normal file
71
iqdbc_repo/iqdbc/lvbs/car/hyundai/enable_radar_tracks.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from iqdbc.car import uds
|
||||
from iqdbc.car.carlog import carlog
|
||||
from iqdbc.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
|
||||
DEVELOPER_DIAGNOSTIC = 0x07
|
||||
CUSTOM_DIAGNOSTIC_REQUEST = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, DEVELOPER_DIAGNOSTIC])
|
||||
CUSTOM_DIAGNOSTIC_RESPONSE = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, DEVELOPER_DIAGNOSTIC])
|
||||
|
||||
READ_DATA_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER])
|
||||
READ_DATA_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40])
|
||||
|
||||
WRITE_DATA_REQUEST = bytes([uds.SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER])
|
||||
WRITE_DATA_RESPONSE = bytes([uds.SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER + 0x40])
|
||||
|
||||
CONFIG_DATA_ID = bytes([0x01, 0x42])
|
||||
DEFAULT_CONFIG = bytes([0x00, 0x00, 0x00, 0x01, 0x00, 0x00])
|
||||
TRACKS_ENABLED_CONFIG = bytes([0x00, 0x00, 0x00, 0x01, 0x00, 0x01])
|
||||
TRACKS_ENABLED_CONFIG_BYTES = b"\x00\x00\x01\x00\x01"
|
||||
|
||||
|
||||
def enable_radar_tracks(logcan, sendcan, bus=0, addr=0x7d0, timeout=0.1, retry=2):
|
||||
carlog.error("radar_tracks: enabling ...")
|
||||
|
||||
for i in range(retry):
|
||||
try:
|
||||
query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [CUSTOM_DIAGNOSTIC_REQUEST], [CUSTOM_DIAGNOSTIC_RESPONSE])
|
||||
|
||||
for _, _ in query.get_data(timeout).items():
|
||||
carlog.error("radar_tracks: check current config ...")
|
||||
|
||||
request = READ_DATA_REQUEST + CONFIG_DATA_ID
|
||||
query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [request], [READ_DATA_RESPONSE])
|
||||
|
||||
for _, data in query.get_data(timeout).items():
|
||||
current_config = data[3:]
|
||||
|
||||
carlog.error(f"radar_tracks: current config: {current_config.hex()}")
|
||||
|
||||
if current_config == TRACKS_ENABLED_CONFIG_BYTES:
|
||||
carlog.error("radar_tracks: already enabled, skipping ...")
|
||||
else:
|
||||
carlog.error("radar_tracks: reconfigure radar to output radar points ...")
|
||||
request = WRITE_DATA_REQUEST + CONFIG_DATA_ID + TRACKS_ENABLED_CONFIG
|
||||
query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [request], [WRITE_DATA_RESPONSE])
|
||||
query.get_data(0)
|
||||
|
||||
carlog.error("radar_tracks: successfully enabled")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
carlog.exception(f"radar_tracks exception: {e}")
|
||||
|
||||
carlog.error(f"radar_tracks retry ({i + 1}) ...")
|
||||
carlog.error("radar_tracks: failed")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
import cereal.messaging as messaging
|
||||
sendcan = messaging.pub_sock('sendcan')
|
||||
logcan = messaging.sub_sock('can')
|
||||
time.sleep(1)
|
||||
|
||||
enabled = enable_radar_tracks(logcan, sendcan, bus=0, addr=0x7d0, timeout=0.1)
|
||||
print(f"enabled: {enabled}")
|
||||
68
iqdbc_repo/iqdbc/lvbs/car/hyundai/escc.py
Normal file
68
iqdbc_repo/iqdbc/lvbs/car/hyundai/escc.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from iqdbc.car import structs
|
||||
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
|
||||
ESCC_MSG = 0x2AB
|
||||
|
||||
|
||||
class EnhancedSmartCruiseControl:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.CP_IQ.flags & HyundaiFlagsIQ.ENHANCED_SCC
|
||||
|
||||
@property
|
||||
def trigger_msg(self):
|
||||
return ESCC_MSG
|
||||
|
||||
def update_car_state(self, car_state):
|
||||
"""
|
||||
This method is invoked by the CarController to update the car state on the ESCC object.
|
||||
The updated state is then used to update SCC12 with the current car state values received through ESCC.
|
||||
:param car_state:
|
||||
:return:
|
||||
"""
|
||||
self.car_state = car_state
|
||||
|
||||
def update_scc12(self, values):
|
||||
"""
|
||||
Update SCC12 with the current car state values received through ESCC.
|
||||
These values are sourced directly from the car's SCC radar and provide a more reliable source for AEB and FCA alerts.
|
||||
:param values: SCC12 to be sent in dictionary form before being packed
|
||||
:return: Nothing. SCC12 is updated in place.
|
||||
"""
|
||||
values["AEB_CmdAct"] = self.car_state.escc_cmd_act
|
||||
values["CF_VSM_Warn"] = self.car_state.escc_aeb_warning
|
||||
values["CF_VSM_DecCmdAct"] = self.car_state.escc_aeb_dec_cmd_act
|
||||
values["CR_VSM_DecCmd"] = self.car_state.escc_aeb_dec_cmd
|
||||
# TODO-IQ: we should read it from the car's settings and use that value.
|
||||
# It may not be ideal to set this here directly.
|
||||
# Observed flickering on the dashboard settings switching between "deactivated" and "active assistance" when sending AEB_Status = 1.
|
||||
# These values could differ from the user's configuration from the car's settings.
|
||||
# This indicates that SCC12 likely displays it on the dashboard, and another FCA message may also cause it to appear.
|
||||
values["AEB_Status"] = 2 # AEB enabled
|
||||
|
||||
|
||||
class EsccCarStateBase:
|
||||
def __init__(self):
|
||||
self.escc_aeb_warning = 0
|
||||
self.escc_aeb_dec_cmd_act = 0
|
||||
self.escc_cmd_act = 0
|
||||
self.escc_aeb_dec_cmd = 0
|
||||
|
||||
|
||||
class EsccCarController:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.ESCC = EnhancedSmartCruiseControl(CP, CP_IQ)
|
||||
|
||||
def update(self, car_state):
|
||||
self.ESCC.update_car_state(car_state)
|
||||
|
||||
|
||||
class EsccRadarInterfaceBase:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.ESCC = EnhancedSmartCruiseControl(CP, CP_IQ)
|
||||
self.use_escc = False
|
||||
126
iqdbc_repo/iqdbc/lvbs/car/hyundai/fingerprints_ext.py
Normal file
126
iqdbc_repo/iqdbc/lvbs/car/hyundai/fingerprints_ext.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.hyundai.values import CAR
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
|
||||
FW_VERSIONS_EXT = {
|
||||
CAR.KIA_CEED_PHEV_2022_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00CD MDPS C 1.00 1.01 56310-XX000 4CPHC101',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00CDH LKAS AT EUR LHD 1.00 1.01 99211-CR700 931',
|
||||
],
|
||||
},
|
||||
# TODO-IQ: HYUNDAI_KONA_EV_NON_SCC has the same FW versions as HYUNDAI_KONA_EV, in the future we may
|
||||
# allow similar FW versions across different platforms
|
||||
# CAR.HYUNDAI_KONA_EV_NON_SCC: {
|
||||
# (Ecu.abs, 0x7d1, None): [
|
||||
# b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000',
|
||||
# ],
|
||||
# (Ecu.eps, 0x7d4, None): [
|
||||
# b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104',
|
||||
# ],
|
||||
# (Ecu.fwdCamera, 0x7c4, None): [
|
||||
# b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4100 W40',
|
||||
# ],
|
||||
# },
|
||||
CAR.GENESIS_G70_2021_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00IK__ SCC --CUP 1.00 1.02 96400-G9100 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00IK MFC MT USA LHD 1.00 1.01 95740-G9000 170920',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_KONA_NON_SCC: {
|
||||
# (Ecu.abs, 0x7d1, None): [
|
||||
# b'\xf1\x816V5RAJ00040.ELF\xf1\x00\x00\x00\x00\x00\x00\x00',
|
||||
# ],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105',
|
||||
b'\xf1\x00OS MDPS C 1.00 1.04 56310J9030\x00 4OSDC104',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9200 g30',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006T6J0_C2\x00\x006T6K1051\x00\x00TOS4N20NS2\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.KIA_FORTE_2019_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00BD MDPS C 1.00 1.04 56310/M6000 4BDDC104',
|
||||
b'\xf1\x00BD MDPS C 1.00 1.05 56310/M6000 4BDDC105',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.02 95740-M6000 J31',
|
||||
],
|
||||
# (Ecu.abs, 0x7d1, None): [
|
||||
# b'\xf1\x816VFRAF00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00',
|
||||
# ],
|
||||
# (Ecu.transmission, 0x7e1, None): [
|
||||
# b'\xf1\x87CXJQAM4966515JB0x\xa9\x98\x9b\x99fff\x98feg\x88\x88w\x88Ff\x8f\xff{\xff\xff\xff\xa8\xf6\xf1\x816V2C1051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C1051\x00\x00CBD0N20NS8q\xc1&\xd2', # noqa: E501
|
||||
# ],
|
||||
},
|
||||
CAR.KIA_FORTE_2021_NON_SCC: {
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00BD MDPS C 1.00 1.08 56310M6000\x00 4BDDC108',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33',
|
||||
],
|
||||
# (Ecu.abs, 0x7d1, None): [
|
||||
# b'\xf1\x816VFRAL00010.ELF\xf1\x00\x00\x00\x00\x00\x00\x00',
|
||||
# ],
|
||||
# (Ecu.transmission, 0x7e1, None): [
|
||||
# b'\xf1\x87CXLQAM0906975JB0\x89\x88\xa6\x8aVfug\xba\x87\x94yffuxgfo\xff\x8b\xff\xff\xff\x91\x82\xf1\x816V2C1051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C1051\x00\x00CBD0N20NS8q\xc1&\xd2', # noqa: E501
|
||||
# ],
|
||||
},
|
||||
CAR.KIA_SELTOS_2023_NON_SCC: {
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00SP ESC \t 101"\t\x01 58910-Q5510',
|
||||
b'\xf1\x00SP ESC \r 100"\x04\x01 58910-Q5510',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00SP2 MDPS C 1.00 1.04 56310Q5240 4SPSC104',
|
||||
b'\xf1\x00SP2 MDPS C 1.00 1.01 56300Q5920 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.03 99210-Q5500 230208',
|
||||
b'\xf1\x00SP2 MFC AT AUS RHD 1.00 1.02 99210-Q5500 220624',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006V2B0_C2\x00\x006V2D5051\x00\x00CSP2N20NL0\x00\x00\x00\x00',
|
||||
b'\xf1\x006V2B0_C2\x00\x006V2D4051\x00\x00CSP2N20KL1\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_ELANTRA_2022_NON_SCC: {
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
# b'\xf1\x8756310AA030\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA030\x00 4CNDC106',
|
||||
b'\xf1\x00CN7 MDPS R 1.00 1.04 57700-IB000 4CNNP104',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-IB000 210531',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
# b'\xf1\x8758910-AB500\xf1\x00CN ESC \t 100 \x06\x01 58910-AB500',
|
||||
b'\xf1\x00CN ESC \t 100!\x05\x01 58910-IB000',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
# b'\xf1\x87CXNQEM4091445JB3g\x98\x98\x89\x99\x87gv\x89wuwgwv\x89hD_\xffx\xff\xff\xff\x86\xeb\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', # noqa: E501
|
||||
b'\xf1\x00T02601BL T02900A1 WCN7T20XXX900NS4\xf7\xccz\xf6',
|
||||
],
|
||||
},
|
||||
CAR.HYUNDAI_BAYON_1ST_GEN_NON_SCC: {
|
||||
# TODO: Check working route for more FW
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00BC3 LKA AT EUR LHD 1.00 1.01 99211-Q0100 261'
|
||||
],
|
||||
},
|
||||
}
|
||||
114
iqdbc_repo/iqdbc/lvbs/car/hyundai/lead_data_ext.py
Normal file
114
iqdbc_repo/iqdbc/lvbs/car/hyundai/lead_data_ext.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
|
||||
|
||||
class LeadData(ABC):
|
||||
def __init__(self, object_gap: int, lead_distance: float, lead_rel_speed: float, lead_visible: bool):
|
||||
self.object_gap = object_gap
|
||||
self.lead_distance = lead_distance
|
||||
self.lead_rel_speed = lead_rel_speed
|
||||
self.lead_visible = lead_visible
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def object_rel_gap(self) -> int:
|
||||
raise NotImplementedError("Subclasses must implement this method")
|
||||
|
||||
|
||||
class CanLeadData(LeadData):
|
||||
@property
|
||||
def object_rel_gap(self) -> int:
|
||||
return 0 if self.lead_distance == 0 else 2 if self.lead_rel_speed < -0.2 else 1
|
||||
|
||||
|
||||
class CanFdLeadData(LeadData):
|
||||
@property
|
||||
def object_rel_gap(self) -> int:
|
||||
return 0 if not self.lead_visible else 2 if self.lead_rel_speed < 0 else 1
|
||||
|
||||
|
||||
def _hysteresis_update(current, new_value, counter, threshold):
|
||||
"""
|
||||
Updates a value based on a hysteresis threshold mechanism. This function
|
||||
compares a new value against the current value and uses a counter to detect
|
||||
when a transition should occur, avoiding rapid oscillations between states.
|
||||
A new value will only be adopted if it differs from the current value and
|
||||
the counter reaches the specified threshold.
|
||||
|
||||
:param current: The current value being tracked.
|
||||
:param new_value: The potential new value to compare against the current.
|
||||
:param counter: The count of consecutive different values encountered.
|
||||
:param threshold: The minimum count required before switching to the new value.
|
||||
:return: A tuple containing:
|
||||
- The updated current value, which is either the original current
|
||||
value or the new value if the hysteresis condition was met.
|
||||
- The updated counter, reset to 0 if the new value was adopted,
|
||||
or incremented by 1 otherwise.
|
||||
"""
|
||||
if new_value == current:
|
||||
return current, 0
|
||||
counter += 1
|
||||
return (new_value, 0) if counter >= threshold else (current, counter)
|
||||
|
||||
|
||||
class LeadDataCarController:
|
||||
# Hysteresis parameters
|
||||
LEAD_HYSTERESIS_FRAMES: int = 50
|
||||
|
||||
def __init__(self, CP: structs.CarParams):
|
||||
self.CP = CP
|
||||
|
||||
self.lead_one = {}
|
||||
self.lead_two = {}
|
||||
|
||||
self._lead_on_counter = 0
|
||||
self._lead_off_counter = 0
|
||||
self.lead_visible = False
|
||||
self.gap_counter = 0
|
||||
self.object_gap = 0
|
||||
self.lead_distance = 0
|
||||
self.lead_rel_speed = 0
|
||||
|
||||
def _update_object_gap(self, lead_distance: float | None):
|
||||
new_gap = 5 # Default gap value if no lead distance is provided
|
||||
if lead_distance is None or lead_distance == 0:
|
||||
new_gap = 0
|
||||
elif lead_distance < 20:
|
||||
new_gap = 2
|
||||
elif lead_distance < 25:
|
||||
new_gap = 3
|
||||
elif lead_distance < 30:
|
||||
new_gap = 4
|
||||
|
||||
self.object_gap, self.gap_counter = _hysteresis_update(self.object_gap, new_gap, self.gap_counter, self.LEAD_HYSTERESIS_FRAMES)
|
||||
|
||||
def _update_lead_visible_hysteresis(self, raw_lead_visible: bool):
|
||||
counter = self._lead_on_counter if raw_lead_visible else self._lead_off_counter
|
||||
self.lead_visible, counter = _hysteresis_update(self.lead_visible, raw_lead_visible, counter, self.LEAD_HYSTERESIS_FRAMES)
|
||||
|
||||
if raw_lead_visible:
|
||||
self._lead_on_counter = counter
|
||||
self._lead_off_counter = 0 # reset opposite counter
|
||||
else:
|
||||
self._lead_off_counter = counter
|
||||
self._lead_on_counter = 0 # reset opposite counter
|
||||
|
||||
def update(self, CC_IQ: structs.IQCarControl) -> None:
|
||||
self.lead_one = CC_IQ.leadOne
|
||||
self.lead_two = CC_IQ.leadTwo
|
||||
|
||||
self.lead_distance = self.lead_one.dRel
|
||||
self.lead_rel_speed = self.lead_one.vRel
|
||||
self._update_lead_visible_hysteresis(self.lead_one.status)
|
||||
self._update_object_gap(self.lead_distance)
|
||||
|
||||
@property
|
||||
def lead_data(self) -> CanLeadData | CanFdLeadData:
|
||||
if self.CP.flags & HyundaiFlags.CANFD:
|
||||
return CanFdLeadData(self.object_gap, self.lead_distance, self.lead_rel_speed, self.lead_visible)
|
||||
|
||||
return CanLeadData(self.object_gap, self.lead_distance, self.lead_rel_speed, self.lead_visible)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
65
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/config.py
Normal file
65
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/config.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from iqdbc.car.hyundai.values import CAR
|
||||
|
||||
|
||||
@dataclass
|
||||
class CarTuningConfig:
|
||||
v_ego_stopping: float = 0.25
|
||||
v_ego_starting: float = 0.10
|
||||
stopping_decel_rate: float = 0.40
|
||||
lookahead_jerk_bp: list[float] = field(default_factory=lambda: [5., 20.])
|
||||
lookahead_jerk_upper_v: list[float] = field(default_factory=lambda: [0.25, 0.5])
|
||||
lookahead_jerk_lower_v: list[float] = field(default_factory=lambda: [0.15, 0.3])
|
||||
longitudinal_actuator_delay: float = 0.45
|
||||
jerk_limits: float = 4.0
|
||||
|
||||
|
||||
# Default configurations for different car types
|
||||
TUNING_CONFIGS = {
|
||||
"CANFD": CarTuningConfig(
|
||||
v_ego_stopping=0.365,
|
||||
lookahead_jerk_bp=[2., 5., 20.],
|
||||
lookahead_jerk_upper_v=[0.25, 0.5, 1.0],
|
||||
lookahead_jerk_lower_v=[0.05, 0.10, 0.325],
|
||||
),
|
||||
"EV": CarTuningConfig(
|
||||
stopping_decel_rate=0.45,
|
||||
v_ego_stopping=0.35,
|
||||
lookahead_jerk_upper_v=[0.3, 0.7],
|
||||
lookahead_jerk_lower_v=[0.2, 0.4],
|
||||
),
|
||||
"HYBRID": CarTuningConfig(
|
||||
v_ego_starting=0.15,
|
||||
stopping_decel_rate=0.45,
|
||||
v_ego_stopping=0.4,
|
||||
),
|
||||
"DEFAULT": CarTuningConfig(
|
||||
lookahead_jerk_bp=[2., 5., 20.],
|
||||
lookahead_jerk_upper_v=[0.25, 0.5, 1.0],
|
||||
lookahead_jerk_lower_v=[0.05, 0.10, 0.3],
|
||||
)
|
||||
}
|
||||
|
||||
# Car-specific configs
|
||||
CAR_SPECIFIC_CONFIGS = {
|
||||
CAR.KIA_NIRO_EV: CarTuningConfig(
|
||||
stopping_decel_rate=0.3,
|
||||
lookahead_jerk_upper_v=[0.3, 1.0],
|
||||
lookahead_jerk_lower_v=[0.2, 0.4],
|
||||
jerk_limits=2.5,
|
||||
),
|
||||
CAR.KIA_NIRO_PHEV_2022: CarTuningConfig(
|
||||
stopping_decel_rate=0.3,
|
||||
lookahead_jerk_upper_v=[0.3, 1.0],
|
||||
lookahead_jerk_lower_v=[0.15, 0.3],
|
||||
jerk_limits=4.0,
|
||||
),
|
||||
CAR.HYUNDAI_IONIQ: CarTuningConfig(
|
||||
jerk_limits=4.5,
|
||||
)
|
||||
}
|
||||
292
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/controller.py
Normal file
292
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/controller.py
Normal file
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from dataclasses import dataclass
|
||||
|
||||
from iqdbc.car import structs, DT_CTRL
|
||||
from iqdbc.car.interfaces import CarStateBase
|
||||
from iqdbc.car.hyundai.values import CarControllerParams
|
||||
from iqdbc.lvbs.car.hyundai.longitudinal.helpers import get_car_config, jerk_limited_integrator, ramp_update
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
|
||||
MIN_JERK = 0.5
|
||||
COMFORT_BAND_VAL = 0.01
|
||||
|
||||
DYNAMIC_LOWER_JERK_BP = [-2.0, -1.5, -1.0, -0.25, -0.1, -0.025, -0.01, -0.005]
|
||||
DYNAMIC_LOWER_JERK_V = [3.3, 2.5, 2.0, 1.9, 1.8, 1.65, 1.15, 0.5]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LongitudinalState:
|
||||
desired_accel: float = 0.0
|
||||
actual_accel: float = 0.0
|
||||
accel_last: float = 0.0
|
||||
jerk_upper: float = 0.0
|
||||
jerk_lower: float = 0.0
|
||||
comfort_band_upper: float = 0.0
|
||||
comfort_band_lower: float = 0.0
|
||||
stopping: bool = False
|
||||
|
||||
|
||||
class LongitudinalController:
|
||||
"""Longitudinal controller which gets injected into CarControllerParams."""
|
||||
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams) -> None:
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.tuning = LongitudinalState()
|
||||
self.car_config = get_car_config(CP)
|
||||
self.long_control_state_last = LongCtrlState.off
|
||||
self.stopping_count = 0
|
||||
|
||||
self.accel_cmd = 0.0
|
||||
self.desired_accel = 0.0
|
||||
self.actual_accel = 0.0
|
||||
self.accel_last = 0.0
|
||||
self.jerk_upper = 0.0
|
||||
self.jerk_lower = 0.0
|
||||
self.comfort_band_upper = 0.0
|
||||
self.comfort_band_lower = 0.0
|
||||
self.stopping = False
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
return bool(self.CP_IQ.flags & (HyundaiFlagsIQ.LONG_TUNING_DYNAMIC | HyundaiFlagsIQ.LONG_TUNING_PREDICTIVE))
|
||||
|
||||
def get_stopping_state(self, actuators: structs.CarControl.Actuators) -> None:
|
||||
stopping = actuators.longControlState == LongCtrlState.stopping
|
||||
|
||||
# If custom tuning is not enabled, use upstream stopping logic
|
||||
if not self.enabled:
|
||||
self.stopping = stopping
|
||||
self.stopping_count = 0
|
||||
return
|
||||
|
||||
# Reset stopping state when not in stopping mode
|
||||
if not stopping:
|
||||
self.stopping = False
|
||||
self.stopping_count = 0
|
||||
return
|
||||
|
||||
# When transitioning from off state to stopping
|
||||
if self.long_control_state_last == LongCtrlState.off:
|
||||
self.stopping = True
|
||||
return
|
||||
|
||||
# Keep track of time in stopping state (in control cycles)
|
||||
if self.stopping_count > 1 / (DT_CTRL * 2):
|
||||
self.stopping = True
|
||||
|
||||
self.stopping_count += 1
|
||||
|
||||
@staticmethod
|
||||
def _calculate_speed_based_jerk_limits(velocity: float, long_control_state: LongCtrlState) -> tuple[float, float]:
|
||||
"""Calculate jerk limits based on vehicle speed according to ISO 15622:2018.
|
||||
|
||||
Args:
|
||||
velocity: Current vehicle speed (m/s)
|
||||
long_control_state: Current longitudinal control state
|
||||
|
||||
Returns:
|
||||
Tuple of (upper_limit, lower_limit) in m/s³
|
||||
"""
|
||||
|
||||
# Upper jerk limit varies based on speed and control state
|
||||
if long_control_state == LongCtrlState.pid:
|
||||
upper_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [2.0, 3.0, 1.6]))
|
||||
else:
|
||||
upper_limit = 0.5 # Default for non-PID states
|
||||
|
||||
# Lower jerk limit varies based on speed
|
||||
lower_limit = float(np.interp(velocity, [0.0, 5.0, 20.0], [5.0, 4.0, 2.5]))
|
||||
|
||||
return upper_limit, lower_limit
|
||||
|
||||
def _calculate_lookahead_jerk(self, accel_error: float, velocity: float) -> tuple[float, float]:
|
||||
"""Calculate lookahead jerk needed to reach target acceleration.
|
||||
|
||||
Args:
|
||||
accel_error: Difference between target and current acceleration (m/s²)
|
||||
velocity: Current vehicle speed (m/s)
|
||||
|
||||
Returns:
|
||||
Tuple of (upper_jerk, lower_jerk) in m/s³
|
||||
"""
|
||||
|
||||
# Time window to reach target acceleration, varies with speed
|
||||
future_t_upper = float(np.interp(velocity, self.car_config.lookahead_jerk_bp, self.car_config.lookahead_jerk_upper_v))
|
||||
future_t_lower = float(np.interp(velocity, self.car_config.lookahead_jerk_bp, self.car_config.lookahead_jerk_lower_v))
|
||||
|
||||
# Required jerk to reach target acceleration in lookahead window
|
||||
j_ego_upper = accel_error / future_t_upper
|
||||
j_ego_lower = accel_error / future_t_lower
|
||||
|
||||
return j_ego_upper, j_ego_lower
|
||||
|
||||
def _calculate_dynamic_lower_jerk(self, accel_error: float, velocity: float) -> float:
|
||||
"""Calculate dynamic jerk for braking based on acceleration error.
|
||||
|
||||
Used for the dynamic tuning approach (non-predictive).
|
||||
|
||||
Args:
|
||||
accel_error: Difference between actual and previous acceleration (m/s²)
|
||||
velocity: Current vehicle speed (m/s)
|
||||
|
||||
Returns:
|
||||
Dynamic lower jerk limit (m/s³)
|
||||
"""
|
||||
|
||||
if self.CP.radarUnavailable:
|
||||
return 5.0
|
||||
|
||||
if accel_error < 0:
|
||||
# Scale the brake jerk values based on car config
|
||||
lower_max = self.car_config.jerk_limits
|
||||
original_values = np.array(DYNAMIC_LOWER_JERK_V)
|
||||
scaled_values = original_values * (lower_max / original_values[0])
|
||||
|
||||
# Interpolate based on acceleration error
|
||||
dynamic_lower_jerk = float(np.interp(accel_error, DYNAMIC_LOWER_JERK_BP, scaled_values))
|
||||
else:
|
||||
dynamic_lower_jerk = 0.5
|
||||
|
||||
return dynamic_lower_jerk
|
||||
|
||||
def calculate_jerk(self, CC: structs.CarControl, CS: CarStateBase, long_control_state: LongCtrlState) -> None:
|
||||
"""Calculate appropriate jerk limits for smooth acceleration/deceleration.
|
||||
|
||||
Args:
|
||||
CC: Car control signals
|
||||
CS: Car state
|
||||
long_control_state: Current longitudinal control state
|
||||
"""
|
||||
|
||||
# If custom tuning is disabled, use upstream fixed values
|
||||
if not self.enabled:
|
||||
jerk_limit = 3.0 if long_control_state == LongCtrlState.pid else 1.0
|
||||
self.jerk_upper = jerk_limit
|
||||
self.jerk_lower = 5.0
|
||||
return
|
||||
|
||||
velocity = CS.out.vEgo
|
||||
accel_error = self.accel_cmd - self.accel_last
|
||||
|
||||
# Calculate jerk limits based on speed
|
||||
upper_speed_factor, lower_speed_factor = self._calculate_speed_based_jerk_limits(velocity, long_control_state)
|
||||
|
||||
# Calculate lookahead jerk
|
||||
j_ego_upper, j_ego_lower = self._calculate_lookahead_jerk(accel_error, velocity)
|
||||
|
||||
# Calculate lower jerk limit
|
||||
lower_jerk = max(-j_ego_lower, MIN_JERK)
|
||||
if self.CP.radarUnavailable:
|
||||
lower_jerk = 5.0
|
||||
|
||||
# Final jerk limits with thresholds
|
||||
desired_jerk_upper = min(max(j_ego_upper, MIN_JERK), upper_speed_factor)
|
||||
desired_jerk_lower = min(lower_jerk, lower_speed_factor)
|
||||
|
||||
# Calculate dynamic lower jerk for non-predictive tuning
|
||||
a_ego_blended = float(np.interp(velocity, [1.0, 2.0], [CS.aBasis, CS.out.aEgo]))
|
||||
dynamic_accel_error = a_ego_blended - self.accel_last
|
||||
dynamic_lower_jerk = self._calculate_dynamic_lower_jerk(dynamic_accel_error, velocity)
|
||||
dynamic_desired_lower_jerk = min(dynamic_lower_jerk, lower_speed_factor)
|
||||
|
||||
# Apply jerk limits based on tuning approach
|
||||
self.jerk_upper = ramp_update(self.jerk_upper, desired_jerk_upper)
|
||||
|
||||
# Predictive tuning uses calculated desired jerk directly
|
||||
# Dynamic tuning applies a ramped approach for smoother transitions
|
||||
if self.CP_IQ.flags & HyundaiFlagsIQ.LONG_TUNING_PREDICTIVE:
|
||||
self.jerk_lower = desired_jerk_lower
|
||||
else:
|
||||
self.jerk_lower = ramp_update(self.jerk_lower, dynamic_desired_lower_jerk)
|
||||
|
||||
# Disable jerk when longitudinal control is inactive
|
||||
if not CC.longActive:
|
||||
self.jerk_upper = 0.0
|
||||
self.jerk_lower = 0.0
|
||||
|
||||
def calculate_accel(self, CC: structs.CarControl) -> None:
|
||||
"""Calculate commanded acceleration using jerk-limited approach.
|
||||
|
||||
Args:
|
||||
CC: Car control signals
|
||||
"""
|
||||
|
||||
# Skip custom processing if tuning is disabled or radar unavailable
|
||||
if not self.enabled or self.CP.radarUnavailable:
|
||||
self.desired_accel = self.accel_cmd
|
||||
self.actual_accel = self.accel_cmd
|
||||
return
|
||||
|
||||
# Reset acceleration when control is inactive
|
||||
if not CC.longActive:
|
||||
self.desired_accel = 0.0
|
||||
self.actual_accel = 0.0
|
||||
self.accel_last = 0.0
|
||||
return
|
||||
|
||||
# Force zero acceleration during stopping
|
||||
if self.stopping:
|
||||
self.desired_accel = 0.0
|
||||
else:
|
||||
self.desired_accel = float(np.clip(self.accel_cmd, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
|
||||
|
||||
# Apply jerk-limited integration to get smooth acceleration
|
||||
self.actual_accel = jerk_limited_integrator(self.desired_accel, self.accel_last, self.jerk_upper, self.jerk_lower)
|
||||
|
||||
self.accel_last = self.actual_accel
|
||||
|
||||
def calculate_comfort_band(self, CC: structs.CarControl) -> None:
|
||||
if not self.enabled or self.CP.radarUnavailable or not CC.longActive:
|
||||
self.comfort_band_upper = 0.0
|
||||
self.comfort_band_lower = 0.0
|
||||
return
|
||||
|
||||
self.comfort_band_upper = COMFORT_BAND_VAL
|
||||
self.comfort_band_lower = COMFORT_BAND_VAL
|
||||
|
||||
def get_tuning_state(self) -> None:
|
||||
"""Update the tuning state object with current control values.
|
||||
|
||||
External components depend on this state for longitudinal control.
|
||||
"""
|
||||
|
||||
self.tuning = LongitudinalState(
|
||||
desired_accel=self.desired_accel,
|
||||
actual_accel=self.actual_accel,
|
||||
accel_last=self.accel_last,
|
||||
jerk_upper=self.jerk_upper,
|
||||
jerk_lower=self.jerk_lower,
|
||||
comfort_band_upper=self.comfort_band_upper,
|
||||
comfort_band_lower=self.comfort_band_lower,
|
||||
stopping=self.stopping,
|
||||
)
|
||||
|
||||
def update(self, CC: structs.CarControl, CS: CarStateBase) -> None:
|
||||
"""Update longitudinal control calculations.
|
||||
|
||||
This is the main entry point called externally.
|
||||
|
||||
Args:
|
||||
CC: Car control signals including actuators
|
||||
CS: Car state information
|
||||
"""
|
||||
|
||||
actuators = CC.actuators
|
||||
long_control_state = actuators.longControlState
|
||||
self.accel_cmd = CC.actuators.accel
|
||||
|
||||
self.get_stopping_state(actuators)
|
||||
self.calculate_jerk(CC, CS, long_control_state)
|
||||
self.calculate_accel(CC)
|
||||
self.calculate_comfort_band(CC)
|
||||
self.get_tuning_state()
|
||||
|
||||
self.long_control_state_last = long_control_state
|
||||
60
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/helpers.py
Normal file
60
iqdbc_repo/iqdbc/lvbs/car/hyundai/longitudinal/helpers.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car import structs, DT_CTRL, rate_limit
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
from iqdbc.lvbs.car.hyundai.longitudinal.config import CarTuningConfig, TUNING_CONFIGS, CAR_SPECIFIC_CONFIGS
|
||||
|
||||
JERK_THRESHOLD = 0.1
|
||||
JERK_STEP = 0.1
|
||||
|
||||
|
||||
class LongitudinalTuningType:
|
||||
OFF = 0
|
||||
DYNAMIC = 1
|
||||
PREDICTIVE = 2
|
||||
|
||||
|
||||
def get_car_config(CP: structs.CarParams) -> CarTuningConfig:
|
||||
# Get car type flags from specific configs or determine from car flags
|
||||
car_config = CAR_SPECIFIC_CONFIGS.get(CP.carFingerprint)
|
||||
# If car is not in specific configs, determine from flags
|
||||
if car_config is None:
|
||||
if CP.flags & HyundaiFlags.CANFD:
|
||||
car_config = TUNING_CONFIGS["CANFD"]
|
||||
elif CP.flags & HyundaiFlags.EV:
|
||||
car_config = TUNING_CONFIGS["EV"]
|
||||
elif CP.flags & HyundaiFlags.HYBRID:
|
||||
car_config = TUNING_CONFIGS["HYBRID"]
|
||||
else:
|
||||
car_config = TUNING_CONFIGS["DEFAULT"]
|
||||
|
||||
return car_config
|
||||
|
||||
|
||||
def get_longitudinal_tune(CP: structs.CarParams) -> None:
|
||||
config = get_car_config(CP)
|
||||
CP.vEgoStopping = config.v_ego_stopping
|
||||
CP.vEgoStarting = config.v_ego_starting
|
||||
CP.stoppingDecelRate = config.stopping_decel_rate
|
||||
CP.startingState = False
|
||||
CP.longitudinalActuatorDelay = config.longitudinal_actuator_delay
|
||||
|
||||
|
||||
def jerk_limited_integrator(desired_accel, last_accel, jerk_upper, jerk_lower) -> float:
|
||||
if desired_accel >= last_accel:
|
||||
val = jerk_upper * DT_CTRL * 2
|
||||
else:
|
||||
val = jerk_lower * DT_CTRL * 2
|
||||
|
||||
return rate_limit(desired_accel, last_accel, -val, val)
|
||||
|
||||
|
||||
def ramp_update(current, target):
|
||||
error = target - current
|
||||
if abs(error) > JERK_THRESHOLD:
|
||||
return current + float(np.clip(error, -JERK_STEP, JERK_STEP))
|
||||
return target
|
||||
85
iqdbc_repo/iqdbc/lvbs/car/hyundai/radar_interface_ext.py
Normal file
85
iqdbc_repo/iqdbc/lvbs/car/hyundai/radar_interface_ext.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car import structs, Bus
|
||||
from iqdbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from iqdbc.car.hyundai.values import DBC, HyundaiFlags
|
||||
|
||||
from iqdbc.lvbs.car.hyundai.escc import EsccRadarInterfaceBase
|
||||
|
||||
|
||||
class RadarInterfaceExt(EsccRadarInterfaceBase):
|
||||
msg_src: str
|
||||
trigger_msg: int
|
||||
rcp: CANParser
|
||||
pts: dict[int, structs.RadarData.RadarPoint]
|
||||
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
EsccRadarInterfaceBase.__init__(self, CP, CP_IQ)
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.track_id = 0
|
||||
|
||||
@property
|
||||
def use_radar_interface_ext(self) -> bool:
|
||||
return self.use_escc or self.CP.flags & (HyundaiFlags.CAMERA_SCC | HyundaiFlags.CANFD_CAMERA_SCC)
|
||||
|
||||
def get_msg_src(self) -> str | None:
|
||||
if self.use_escc:
|
||||
return "ESCC"
|
||||
if self.CP.flags & (HyundaiFlags.CAMERA_SCC | HyundaiFlags.CANFD_CAMERA_SCC):
|
||||
return "SCC_CONTROL" if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else "SCC11"
|
||||
|
||||
def get_radar_ext_can_parser(self) -> CANParser:
|
||||
if self.ESCC.enabled:
|
||||
lead_src, bus = "ESCC", 0
|
||||
elif self.CP.flags & (HyundaiFlags.CAMERA_SCC | HyundaiFlags.CANFD_CAMERA_SCC):
|
||||
lead_src = "SCC_CONTROL" if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else "SCC11"
|
||||
bus = CanBus(self.CP).CAM if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else 2
|
||||
else:
|
||||
return None
|
||||
|
||||
messages = [(lead_src, 50)]
|
||||
return CANParser(DBC[self.CP.carFingerprint][Bus.pt], messages, bus)
|
||||
|
||||
def get_trigger_msg(self, default_trigger_msg) -> int:
|
||||
if self.ESCC.enabled:
|
||||
return self.ESCC.trigger_msg
|
||||
if self.CP.flags & (HyundaiFlags.CAMERA_SCC | HyundaiFlags.CANFD_CAMERA_SCC):
|
||||
return 0x1A0 if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else 0x420
|
||||
return default_trigger_msg
|
||||
|
||||
def initialize_radar_ext(self, default_trigger_msg) -> None:
|
||||
if self.ESCC.enabled:
|
||||
self.use_escc = True
|
||||
|
||||
self.rcp = self.get_radar_ext_can_parser()
|
||||
self.trigger_msg = self.get_trigger_msg(default_trigger_msg)
|
||||
|
||||
def update_ext(self, ret: structs.RadarData) -> structs.RadarData:
|
||||
if not self.rcp.can_valid:
|
||||
ret.errors.canError = True
|
||||
return ret
|
||||
|
||||
for ii in range(1):
|
||||
msg_src = self.get_msg_src()
|
||||
msg = self.rcp.vl[msg_src]
|
||||
|
||||
if ii not in self.pts:
|
||||
self.pts[ii] = structs.RadarData.RadarPoint()
|
||||
self.pts[ii].trackId = self.track_id
|
||||
self.track_id += 1
|
||||
|
||||
valid = msg['ACC_ObjDist'] < 204.6 if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else msg['ACC_ObjStatus']
|
||||
if valid:
|
||||
self.pts[ii].measured = True
|
||||
self.pts[ii].dRel = msg['ACC_ObjDist']
|
||||
self.pts[ii].yRel = float('nan') # FIXME-IQ: Only some cars have lateral position from SCC
|
||||
self.pts[ii].vRel = msg['ACC_ObjRelSpd']
|
||||
self.pts[ii].aRel = float('nan') # TODO-IQ: calculate from ACC_ObjRelSpd and with timestep 50Hz (needs to modify in interfaces.py)
|
||||
self.pts[ii].yvRel = float('nan')
|
||||
|
||||
else:
|
||||
del self.pts[ii]
|
||||
|
||||
ret.points = list(self.pts.values())
|
||||
return ret
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/hyundai/tests/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/hyundai/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
61
iqdbc_repo/iqdbc/lvbs/car/hyundai/tests/test_escc_base.py
Normal file
61
iqdbc_repo/iqdbc/lvbs/car/hyundai/tests/test_escc_base.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import pytest
|
||||
from hypothesis import given, strategies as st, settings, HealthCheck
|
||||
from iqdbc.lvbs.car.hyundai.escc import EnhancedSmartCruiseControl, ESCC_MSG
|
||||
from iqdbc.car.hyundai.carstate import CarState
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def car_params():
|
||||
params = structs.CarParams()
|
||||
params.carFingerprint = "HYUNDAI_SONATA"
|
||||
return params
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def car_params_iq():
|
||||
params = structs.IQCarParams()
|
||||
params.flags = HyundaiFlagsIQ.ENHANCED_SCC
|
||||
return params
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def escc(car_params, car_params_iq):
|
||||
return EnhancedSmartCruiseControl(car_params, car_params_iq)
|
||||
|
||||
|
||||
class TestEscc:
|
||||
def test_escc_msg_id(self, escc):
|
||||
assert escc.trigger_msg == ESCC_MSG
|
||||
|
||||
@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
@given(st.integers(min_value=0, max_value=255))
|
||||
def test_enabled_flag(self, car_params, car_params_iq, value):
|
||||
car_params_iq.flags = value
|
||||
escc = EnhancedSmartCruiseControl(car_params, car_params_iq)
|
||||
assert escc.enabled == (value & HyundaiFlagsIQ.ENHANCED_SCC)
|
||||
|
||||
def test_update_car_state(self, escc, car_params, car_params_iq):
|
||||
car_state = CarState(car_params, car_params_iq)
|
||||
car_state.escc_cmd_act = 1
|
||||
car_state.escc_aeb_warning = 1
|
||||
car_state.escc_aeb_dec_cmd_act = 1
|
||||
car_state.escc_aeb_dec_cmd = 1
|
||||
escc.update_car_state(car_state)
|
||||
assert escc.car_state == car_state
|
||||
|
||||
def test_update_scc12(self, escc, car_params, car_params_iq):
|
||||
car_state = CarState(car_params, car_params_iq)
|
||||
car_state.escc_cmd_act = 1
|
||||
car_state.escc_aeb_warning = 1
|
||||
car_state.escc_aeb_dec_cmd_act = 1
|
||||
car_state.escc_aeb_dec_cmd = 1
|
||||
escc.update_car_state(car_state)
|
||||
scc12_message = {}
|
||||
escc.update_scc12(scc12_message)
|
||||
assert scc12_message["AEB_CmdAct"] == 1
|
||||
assert scc12_message["CF_VSM_Warn"] == 1
|
||||
assert scc12_message["CF_VSM_DecCmdAct"] == 1
|
||||
assert scc12_message["CR_VSM_DecCmd"] == 1
|
||||
assert scc12_message["AEB_Status"] == 2
|
||||
@@ -0,0 +1,77 @@
|
||||
from enum import IntFlag
|
||||
|
||||
from iqdbc.lvbs.car.hyundai.lead_data_ext import LeadDataCarController, CanLeadData, CanFdLeadData
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
|
||||
|
||||
def make_carparams(flags: IntFlag = HyundaiFlags.LEGACY):
|
||||
cp = structs.CarParams()
|
||||
cp.carFingerprint = "HYUNDAI_SONATA"
|
||||
cp.flags = flags.value
|
||||
return cp
|
||||
|
||||
|
||||
def make_iq_carcontrol(leadDistance=10.0, leadRelSpeed=0.0, leadVisible=True):
|
||||
c = structs.IQCarControl()
|
||||
c.leadOne.dRel = leadDistance
|
||||
c.leadOne.vRel = leadRelSpeed
|
||||
c.leadOne.status = leadVisible
|
||||
return c
|
||||
|
||||
|
||||
class TestLeadDataCarController:
|
||||
def test_update_object_gap(self):
|
||||
ctrl = LeadDataCarController(make_carparams())
|
||||
# Initial value should be 0
|
||||
assert ctrl.object_gap == 0
|
||||
|
||||
# Set to 15 (should become 2 after hysteresis)
|
||||
for _ in range(ctrl.LEAD_HYSTERESIS_FRAMES):
|
||||
ctrl._update_object_gap(15)
|
||||
assert ctrl.object_gap == 2
|
||||
|
||||
# Set to 22 (should become 3 after hysteresis)
|
||||
for _ in range(ctrl.LEAD_HYSTERESIS_FRAMES):
|
||||
ctrl._update_object_gap(22)
|
||||
assert ctrl.object_gap == 3
|
||||
|
||||
# Set to 0 (should become 0 after hysteresis)
|
||||
for _ in range(ctrl.LEAD_HYSTERESIS_FRAMES):
|
||||
ctrl._update_object_gap(0)
|
||||
assert ctrl.object_gap == 0
|
||||
|
||||
def test_update_lead_visible_hysteresis(self):
|
||||
ctrl = LeadDataCarController(make_carparams())
|
||||
ctrl._update_lead_visible_hysteresis(True)
|
||||
assert isinstance(ctrl.lead_visible, bool)
|
||||
ctrl._update_lead_visible_hysteresis(False)
|
||||
assert isinstance(ctrl.lead_visible, bool)
|
||||
|
||||
def test_update(self):
|
||||
ctrl = LeadDataCarController(make_carparams())
|
||||
iq_control = make_iq_carcontrol(leadDistance=25, leadRelSpeed=-0.5, leadVisible=True)
|
||||
ctrl.update(iq_control)
|
||||
assert ctrl.lead_distance == 25
|
||||
assert ctrl.lead_rel_speed == -0.5
|
||||
assert isinstance(ctrl.lead_visible, bool)
|
||||
|
||||
def test_lead_data_can(self):
|
||||
ctrl = LeadDataCarController(make_carparams())
|
||||
ctrl.object_gap = 1
|
||||
ctrl.lead_distance = 10
|
||||
ctrl.lead_rel_speed = -0.3
|
||||
ctrl.lead_visible = True
|
||||
ld = ctrl.lead_data
|
||||
assert isinstance(ld, CanLeadData)
|
||||
assert ld.object_rel_gap == 2
|
||||
|
||||
def test_lead_data_canfd(self):
|
||||
ctrl = LeadDataCarController(make_carparams(HyundaiFlags.CANFD))
|
||||
ctrl.object_gap = 1
|
||||
ctrl.lead_distance = 10
|
||||
ctrl.lead_rel_speed = 1.0
|
||||
ctrl.lead_visible = True
|
||||
ld = ctrl.lead_data
|
||||
assert isinstance(ld, CanFdLeadData)
|
||||
assert ld.object_rel_gap == 1
|
||||
@@ -0,0 +1,119 @@
|
||||
from parameterized import parameterized
|
||||
|
||||
from iqdbc.car import CanData
|
||||
from iqdbc.car.car_helpers import interfaces
|
||||
from iqdbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from iqdbc.lvbs.car.hyundai.escc import ESCC_MSG
|
||||
|
||||
ESCC_CARS = [
|
||||
(CAR.HYUNDAI_ELANTRA_2021, ESCC_MSG),
|
||||
]
|
||||
|
||||
CAMERA_SCC_CARS = [
|
||||
(CAR.HYUNDAI_KONA_EV_2022, 0, 0x420, "SCC11"),
|
||||
(CAR.HYUNDAI_IONIQ_5, HyundaiFlags.CANFD_CAMERA_SCC.value, 0x1A0, "SCC_CONTROL"),
|
||||
]
|
||||
|
||||
STANDARD_RADAR_CARS = [
|
||||
(CAR.HYUNDAI_ELANTRA_2021, 0),
|
||||
(CAR.HYUNDAI_SANTA_FE, 0),
|
||||
]
|
||||
|
||||
|
||||
class TestRadarInterfaceExt:
|
||||
|
||||
@staticmethod
|
||||
def _setup_platform(car_name, additional_flags=0, escc_msg=None):
|
||||
"""Set up the platform with specific parameters"""
|
||||
CarInterface = interfaces[car_name]
|
||||
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
CP.flags |= additional_flags
|
||||
|
||||
CP_IQ = CarInterface.get_non_essential_params_iq(CP, car_name)
|
||||
|
||||
CI = CarInterface(CP, CP_IQ)
|
||||
|
||||
RD = CI.RadarInterface(CP, CP_IQ)
|
||||
|
||||
if escc_msg is not None and hasattr(RD, 'use_escc'):
|
||||
try:
|
||||
RD.use_escc = True
|
||||
except AttributeError:
|
||||
object.__setattr__(RD, 'use_escc', True)
|
||||
|
||||
return RD, CP, CP_IQ
|
||||
|
||||
@parameterized.expand(ESCC_CARS)
|
||||
def test_escc_radar_interface(self, car_name, escc_msg):
|
||||
"""Test radar interface for ESCC-enabled cars"""
|
||||
RD, CP, CP_IQ = self._setup_platform(car_name, escc_msg=escc_msg)
|
||||
|
||||
# Assert that ESCC features are present
|
||||
if hasattr(RD, 'use_escc'):
|
||||
assert RD.use_escc, "ESCC car should have use_escc=True"
|
||||
if hasattr(RD, 'use_radar_interface_ext'):
|
||||
assert RD.use_radar_interface_ext, "ESCC car should use radar interface ext"
|
||||
|
||||
# Run radar interface once
|
||||
RD.update([])
|
||||
|
||||
# Test radar fault
|
||||
if not CP.radarUnavailable and RD.rcp is not None:
|
||||
cans = [(0, [CanData(0, b'', 0) for _ in range(5)])]
|
||||
rr = RD.update(cans)
|
||||
assert rr is None or len(rr.errors) > 0
|
||||
|
||||
@parameterized.expand(CAMERA_SCC_CARS)
|
||||
def test_camera_scc_radar_interface(self, car_name, flags, expected_trigger, msg_src):
|
||||
"""Test radar interface for Camera SCC cars"""
|
||||
RD, CP, CP_IQ = self._setup_platform(car_name, additional_flags=flags)
|
||||
|
||||
# Assert Camera SCC flag is set appropriately
|
||||
if flags & HyundaiFlags.CAMERA_SCC:
|
||||
assert CP.flags & HyundaiFlags.CAMERA_SCC, "Car should have CAMERA_SCC flag"
|
||||
if flags & HyundaiFlags.CANFD_CAMERA_SCC:
|
||||
assert CP.flags & HyundaiFlags.CANFD_CAMERA_SCC, "Car should have CANFD_CAMERA_SCC flag"
|
||||
|
||||
# Check if using radar interface ext
|
||||
if hasattr(RD, 'use_radar_interface_ext'):
|
||||
assert RD.use_radar_interface_ext, "Camera SCC car should use radar interface ext"
|
||||
|
||||
# Verify trigger message
|
||||
if hasattr(RD, 'trigger_msg'):
|
||||
assert RD.trigger_msg == expected_trigger, f"Expected trigger_msg {expected_trigger}, got {RD.trigger_msg}"
|
||||
|
||||
# Run radar interface once
|
||||
RD.update([])
|
||||
|
||||
# Test radar fault
|
||||
if not CP.radarUnavailable and RD.rcp is not None:
|
||||
cans = [(0, [CanData(0, b'', 0) for _ in range(5)])]
|
||||
rr = RD.update(cans)
|
||||
assert rr is None or len(rr.errors) > 0
|
||||
|
||||
@parameterized.expand(STANDARD_RADAR_CARS)
|
||||
def test_standard_radar_interface(self, car_name, flags):
|
||||
"""Test radar interface for standard radar cars"""
|
||||
RD, CP, CP_IQ = self._setup_platform(car_name, additional_flags=flags)
|
||||
|
||||
# Standard cars should not use radar interface ext
|
||||
if hasattr(RD, 'use_radar_interface_ext'):
|
||||
assert not RD.use_radar_interface_ext, "Standard car should not use radar interface ext"
|
||||
|
||||
# Run radar interface once
|
||||
RD.update([])
|
||||
|
||||
# For standard radar, test the _update method directly if available
|
||||
if not CP.radarUnavailable and RD.rcp is not None and \
|
||||
hasattr(RD, '_update') and hasattr(RD, 'trigger_msg'):
|
||||
# Setup for _update test if needed
|
||||
if hasattr(RD, 'updated_messages'):
|
||||
RD.updated_messages = {RD.trigger_msg}
|
||||
RD._update(RD.updated_messages)
|
||||
|
||||
# Test radar fault
|
||||
if not CP.radarUnavailable and RD.rcp is not None:
|
||||
cans = [(0, [CanData(0, b'', 0) for _ in range(5)])]
|
||||
rr = RD.update(cans)
|
||||
assert rr is None or len(rr.errors) > 0
|
||||
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import numpy as np
|
||||
from unittest.mock import Mock
|
||||
|
||||
from iqdbc.lvbs.car.hyundai.longitudinal.controller import LongitudinalController, LongitudinalState
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
from iqdbc.car import DT_CTRL, structs
|
||||
from iqdbc.car.interfaces import CarStateBase
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
|
||||
|
||||
class TestLongitudinalTuningController(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.mock_CP = Mock(carFingerprint="KIA_NIRO_EV", flags=0)
|
||||
self.mock_CP.radarUnavailable = False # ensure tuning branch
|
||||
self.mock_CP_IQ = Mock(flags=0)
|
||||
self.controller = LongitudinalController(self.mock_CP, self.mock_CP_IQ)
|
||||
|
||||
def test_init(self):
|
||||
"""Test controller initialization"""
|
||||
self.assertIsInstance(self.controller.tuning, LongitudinalState)
|
||||
self.assertEqual(self.controller.desired_accel, 0.0)
|
||||
self.assertEqual(self.controller.actual_accel, 0.0)
|
||||
self.assertEqual(self.controller.jerk_upper, 0.0)
|
||||
self.assertEqual(self.controller.jerk_lower, 0.0)
|
||||
self.assertEqual(self.controller.comfort_band_upper, 0.0)
|
||||
self.assertEqual(self.controller.comfort_band_lower, 0.0)
|
||||
|
||||
def test_make_jerk_flag_off(self):
|
||||
"""Test when LONG_TUNING_DYNAMIC flag is off"""
|
||||
mock_CC, mock_CS = Mock(spec=structs.CarControl), Mock(spec=CarStateBase)
|
||||
mock_CS.out = Mock()
|
||||
mock_CS.out.vEgo = 0.0
|
||||
mock_CS.out.aEgo = 0.0
|
||||
mock_CS.aBasis = 0.0
|
||||
|
||||
# Test with PID state
|
||||
self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid)
|
||||
print(f"[PID state] jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}")
|
||||
self.assertEqual(self.controller.jerk_upper, 3.0)
|
||||
self.assertEqual(self.controller.jerk_lower, 5.0)
|
||||
|
||||
# Test with non-PID state
|
||||
self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.stopping)
|
||||
print(f"[Non-PID state] jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}")
|
||||
self.assertEqual(self.controller.jerk_upper, 1.0)
|
||||
self.assertEqual(self.controller.jerk_lower, 5.0)
|
||||
|
||||
def test_make_jerk_flag_on(self):
|
||||
"""Only verify that limits update when flags are on."""
|
||||
self.controller.CP_IQ.flags = HyundaiFlagsIQ.LONG_TUNING_DYNAMIC
|
||||
self.controller.CP.flags = HyundaiFlags.CANFD
|
||||
mock_CC = Mock()
|
||||
mock_CC.actuators = Mock(accel=1.0)
|
||||
mock_CC.longActive = True
|
||||
self.controller.stopping = False
|
||||
mock_CS = Mock()
|
||||
mock_CS.out = Mock(aEgo=0.8, vEgo=3.0)
|
||||
mock_CS.aBasis = 0.8
|
||||
|
||||
self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid)
|
||||
print(f"[FlagOn] jerk_upper={self.controller.jerk_upper:.3f}, jerk_lower={self.controller.jerk_lower:.3f}")
|
||||
self.assertGreater(self.controller.jerk_upper, 0.0)
|
||||
self.assertGreater(self.controller.jerk_lower, 0.0)
|
||||
|
||||
def test_a_value_jerk_scaling(self):
|
||||
"""Test a_value jerk scaling under tuning branch."""
|
||||
self.controller.CP_IQ.flags = HyundaiFlagsIQ.LONG_TUNING_DYNAMIC
|
||||
self.controller.CP.radarUnavailable = False
|
||||
mock_CC = Mock()
|
||||
mock_CC.actuators = Mock(accel=1.0)
|
||||
mock_CC.longActive = True
|
||||
print("[a_value] starting accel_last:", self.controller.tuning.accel_last)
|
||||
# first pass: limit to jerk_upper * DT_CTRL * 2 = 0.1
|
||||
self.controller.jerk_upper = 0.1 / (DT_CTRL * 2)
|
||||
self.controller.accel_cmd = 1.0 # ensure accel_cmd is set
|
||||
self.controller.calculate_accel(mock_CC)
|
||||
print(f"[a_value] pass1 actual_accel={self.controller.actual_accel:.5f}")
|
||||
self.assertAlmostEqual(self.controller.actual_accel, 0.1, places=5)
|
||||
|
||||
# second pass: limit increment by new jerk_upper
|
||||
mock_CC.actuators.accel = 0.7
|
||||
self.controller.jerk_upper = 0.2 / (DT_CTRL * 2)
|
||||
self.controller.accel_cmd = 0.7 # update accel_cmd
|
||||
self.controller.calculate_accel(mock_CC)
|
||||
print(f"[a_value] pass2 actual_accel={self.controller.actual_accel:.5f}")
|
||||
self.assertAlmostEqual(self.controller.actual_accel, 0.3, places=5)
|
||||
|
||||
def test_make_jerk_realistic_profile(self):
|
||||
"""Test make_jerk with realistic velocity and acceleration profile"""
|
||||
np.random.seed(42)
|
||||
num_points = 30
|
||||
segments = [
|
||||
np.random.uniform(0.3, 0.8, num_points//4),
|
||||
np.random.uniform(0.8, 1.6, num_points//4),
|
||||
np.random.uniform(-0.2, 0.2, num_points//4),
|
||||
np.random.uniform(-1.2, -0.5, num_points//8),
|
||||
np.random.uniform(-2.2, -1.2, num_points//8)
|
||||
]
|
||||
accels = np.concatenate(segments)[:num_points]
|
||||
vels = np.zeros_like(accels)
|
||||
vels[0] = 5.0
|
||||
for i in range(1, len(accels)):
|
||||
vels[i] = max(0.0, min(30.0, vels[i-1] + accels[i-1] * (DT_CTRL*2)))
|
||||
mock_CC, mock_CS = Mock(), Mock()
|
||||
mock_CC.actuators, mock_CS.out = Mock(), Mock()
|
||||
mock_CC.longActive = True
|
||||
self.controller.stopping = False
|
||||
|
||||
# Test with LONG_TUNING_DYNAMIC only
|
||||
self.controller.CP_IQ.flags = HyundaiFlagsIQ.LONG_TUNING_DYNAMIC
|
||||
for v, a in zip(vels, accels, strict=True):
|
||||
mock_CS.out.vEgo = float(v)
|
||||
mock_CS.out.aEgo = float(a)
|
||||
mock_CS.aBasis = float(a)
|
||||
mock_CC.actuators.accel = float(a)
|
||||
self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid)
|
||||
print(f"[realistic][LONG_TUNING_DYNAMIC] v={v:.2f}, a={a:.2f}, jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}")
|
||||
self.assertGreater(self.controller.jerk_upper, 0.0)
|
||||
|
||||
# Reset controller before next test
|
||||
self.controller.tuning = LongitudinalState()
|
||||
self.controller.jerk_upper = 0.5
|
||||
self.controller.jerk_lower = 0.5
|
||||
|
||||
# Test with LONG_TUNING_DYNAMIC and LONG_TUNING_PREDICTIVE
|
||||
self.controller.CP_IQ.flags = HyundaiFlagsIQ.LONG_TUNING_DYNAMIC | HyundaiFlagsIQ.LONG_TUNING_PREDICTIVE
|
||||
for v, a in zip(vels, accels, strict=True):
|
||||
mock_CS.out.vEgo = float(v)
|
||||
mock_CS.out.aEgo = float(a)
|
||||
mock_CS.aBasis = float(a)
|
||||
mock_CC.actuators.accel = float(a)
|
||||
self.controller.calculate_jerk(mock_CC, mock_CS, LongCtrlState.pid)
|
||||
print(f"[realistic][LONG_TUNING_PREDICTIVE] v={v:.2f}, a={a:.2f}, " +
|
||||
f"jerk_upper={self.controller.jerk_upper:.2f}, jerk_lower={self.controller.jerk_lower:.2f}")
|
||||
self.assertGreater(self.controller.jerk_upper, 0.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
32
iqdbc_repo/iqdbc/lvbs/car/hyundai/values.py
Normal file
32
iqdbc_repo/iqdbc/lvbs/car/hyundai/values.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class HyundaiSafetyFlagsIQ:
|
||||
DEFAULT = 0
|
||||
ESCC = 1
|
||||
LONG_MAIN_CRUISE_TOGGLEABLE = 2
|
||||
HAS_LDA_BUTTON = 4
|
||||
NON_SCC = 8
|
||||
|
||||
|
||||
class HyundaiFlagsIQ(IntFlag):
|
||||
"""
|
||||
Flags for Hyundai specific quirks within iqpilot.
|
||||
"""
|
||||
ENHANCED_SCC = 1
|
||||
HAS_LFA_BUTTON = 2 # Deprecated in favor of HyundaiFlags.HAS_LDA_BUTTON
|
||||
LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE = 2 ** 2
|
||||
ENABLE_RADAR_TRACKS_DEPRECATED = 2 ** 3
|
||||
LONG_TUNING_DYNAMIC = 2 ** 4
|
||||
LONG_TUNING_PREDICTIVE = 2 ** 5
|
||||
NON_SCC = 2 ** 6
|
||||
NON_SCC_RADAR_FCA = 2 ** 7 # most with FCA come from the camera
|
||||
NON_SCC_NO_FCA = 2 ** 8 # not all have FCA
|
||||
SPEED_LIMIT_AVAILABLE = 2 ** 9 # platforms with speed limit data available
|
||||
HAS_LKAS12 = 2 ** 10
|
||||
|
||||
|
||||
143
iqdbc_repo/iqdbc/lvbs/car/interfaces.py
Normal file
143
iqdbc_repo/iqdbc/lvbs/car/interfaces.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import json
|
||||
import numpy as np
|
||||
from typing import NamedTuple
|
||||
from collections.abc import Callable
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.can_definitions import CanRecvCallable, CanSendCallable
|
||||
from iqdbc.car.hyundai.values import HyundaiFlags
|
||||
from iqdbc.car.subaru.values import SubaruFlags
|
||||
from iqdbc.lvbs.car.hyundai.enable_radar_tracks import enable_radar_tracks as hyundai_enable_radar_tracks
|
||||
from iqdbc.lvbs.car.hyundai.longitudinal.helpers import LongitudinalTuningType
|
||||
from iqdbc.lvbs.car.hyundai.values import HyundaiFlagsIQ
|
||||
from iqdbc.lvbs.car.subaru.values_ext import SubaruFlagsIQ, SubaruSafetyFlagsIQ
|
||||
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ
|
||||
from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ
|
||||
|
||||
|
||||
class LatControlInputs(NamedTuple):
|
||||
lateral_acceleration: float
|
||||
roll_compensation: float
|
||||
vego: float
|
||||
aego: float
|
||||
|
||||
|
||||
TorqueFromLateralAccelCallbackTypeTorqueSpace = Callable[[LatControlInputs, structs.CarParams.LateralTorqueTuning, bool], float]
|
||||
|
||||
|
||||
class CarInterfaceBaseIQ:
|
||||
@staticmethod
|
||||
def torque_from_lateral_accel_linear_in_torque_space(latcontrol_inputs: LatControlInputs, torque_params: structs.CarParams.LateralTorqueTuning,
|
||||
gravity_adjusted: bool) -> float:
|
||||
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
|
||||
return latcontrol_inputs.lateral_acceleration / float(torque_params.latAccelFactor)
|
||||
|
||||
def torque_from_lateral_accel_in_torque_space(self) -> TorqueFromLateralAccelCallbackTypeTorqueSpace:
|
||||
return self.torque_from_lateral_accel_linear_in_torque_space
|
||||
|
||||
|
||||
class NanoFFModel:
|
||||
def __init__(self, weights_loc: str, platform: str):
|
||||
self.weights_loc = weights_loc
|
||||
self.platform = platform
|
||||
self.load_weights(platform)
|
||||
|
||||
def load_weights(self, platform: str):
|
||||
with open(self.weights_loc) as fob:
|
||||
self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()}
|
||||
|
||||
def relu(self, x: np.ndarray):
|
||||
return np.maximum(0.0, x)
|
||||
|
||||
def forward(self, x: np.ndarray):
|
||||
assert x.ndim == 1
|
||||
x = (x - self.weights['input_norm_mat'][:, 0]) / (self.weights['input_norm_mat'][:, 1] - self.weights['input_norm_mat'][:, 0])
|
||||
x = self.relu(np.dot(x, self.weights['w_1']) + self.weights['b_1'])
|
||||
x = self.relu(np.dot(x, self.weights['w_2']) + self.weights['b_2'])
|
||||
x = self.relu(np.dot(x, self.weights['w_3']) + self.weights['b_3'])
|
||||
x = np.dot(x, self.weights['w_4']) + self.weights['b_4']
|
||||
return x
|
||||
|
||||
def predict(self, x: list[float], do_sample: bool = False):
|
||||
x = self.forward(np.array(x))
|
||||
if do_sample:
|
||||
pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature'])
|
||||
else:
|
||||
pred = x[0]
|
||||
pred = pred * (self.weights['output_norm_mat'][1] - self.weights['output_norm_mat'][0]) + self.weights['output_norm_mat'][0]
|
||||
return pred
|
||||
|
||||
|
||||
def apply_iq_car_config(CI, CP: structs.CarParams, CP_IQ: structs.IQCarParams,
|
||||
params_list: list[dict[str, str]] | None = None,
|
||||
can_recv: CanRecvCallable | None = None, can_send: CanSendCallable | None = None) -> None:
|
||||
if params_list is None:
|
||||
params_list = []
|
||||
|
||||
params_dict = {k: v for param in params_list for k, v in param.items()}
|
||||
|
||||
_initialize_custom_longitudinal_tuning(CI, CP, CP_IQ, params_dict)
|
||||
_initialize_coop_steering(CP, CP_IQ, params_dict)
|
||||
_initialize_radar_tracks(CP, CP_IQ, can_recv, can_send)
|
||||
_initialize_stop_and_go(CP, CP_IQ, params_dict)
|
||||
_initialize_toyota(CP, CP_IQ, params_dict)
|
||||
|
||||
|
||||
def _initialize_custom_longitudinal_tuning(CI, CP: structs.CarParams, CP_IQ: structs.IQCarParams,
|
||||
params_dict: dict[str, str]) -> None:
|
||||
|
||||
# Hyundai Custom Longitudinal Tuning
|
||||
if CP.brand == 'hyundai':
|
||||
hyundai_longitudinal_tuning = int(params_dict.get("HyundaiLongitudinalTuning", 0))
|
||||
if hyundai_longitudinal_tuning == LongitudinalTuningType.DYNAMIC:
|
||||
CP_IQ.flags |= HyundaiFlagsIQ.LONG_TUNING_DYNAMIC.value
|
||||
if hyundai_longitudinal_tuning == LongitudinalTuningType.PREDICTIVE:
|
||||
CP_IQ.flags |= HyundaiFlagsIQ.LONG_TUNING_PREDICTIVE.value
|
||||
|
||||
_ = CI.get_longitudinal_tuning_iq(CP, CP_IQ)
|
||||
|
||||
|
||||
def _initialize_coop_steering(CP: structs.CarParams, CP_IQ: structs.IQCarParams,
|
||||
params_dict: dict[str, str]) -> None:
|
||||
if CP.brand == 'tesla':
|
||||
coop_steering = int(params_dict.get("TeslaCoopSteering", 0)) == 1
|
||||
if coop_steering:
|
||||
CP_IQ.flags |= TeslaFlagsIQ.COOP_STEERING.value
|
||||
|
||||
|
||||
def _initialize_radar_tracks(CP: structs.CarParams, CP_IQ: structs.IQCarParams,
|
||||
can_recv: CanRecvCallable | None = None, can_send: CanSendCallable | None = None) -> None:
|
||||
if CP.brand == 'hyundai':
|
||||
if CP.flags & HyundaiFlags.MANDO_RADAR and (CP.radarUnavailable or CP_IQ.flags & HyundaiFlagsIQ.ENHANCED_SCC):
|
||||
tracks_enabled = hyundai_enable_radar_tracks(can_recv, can_send, bus=0, addr=0x7d0)
|
||||
CP.radarUnavailable = not tracks_enabled
|
||||
|
||||
|
||||
def _initialize_stop_and_go(CP: structs.CarParams, CP_IQ: structs.IQCarParams, params_dict: dict[str, str]) -> None:
|
||||
if CP.brand == 'subaru' and not CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID):
|
||||
stop_and_go = int(params_dict.get("SubaruStopAndGo", 0)) == 1
|
||||
stop_and_go_manual_parking_brake = int(params_dict.get("SubaruStopAndGoManualParkingBrake", 0)) == 1
|
||||
|
||||
if stop_and_go:
|
||||
CP_IQ.flags |= SubaruFlagsIQ.STOP_AND_GO.value
|
||||
if stop_and_go_manual_parking_brake:
|
||||
CP_IQ.flags |= SubaruFlagsIQ.STOP_AND_GO_MANUAL_PARKING_BRAKE.value
|
||||
if stop_and_go or stop_and_go_manual_parking_brake:
|
||||
CP_IQ.safetyParam |= SubaruSafetyFlagsIQ.STOP_AND_GO
|
||||
|
||||
|
||||
def _initialize_toyota(CP: structs.CarParams, CP_IQ: structs.IQCarParams, params_dict: dict[str, str]) -> None:
|
||||
if CP.brand == 'toyota':
|
||||
toyota_stock_long = int(params_dict.get("ToyotaEnforceStockLongitudinal", 0)) == 1
|
||||
toyota_sng_hack = int(params_dict.get("ToyotaSnGHack", 0)) == 1
|
||||
|
||||
if toyota_stock_long:
|
||||
CP_IQ.flags |= ToyotaFlagsIQ.STOCK_LONGITUDINAL.value
|
||||
|
||||
if toyota_sng_hack:
|
||||
CP_IQ.flags |= ToyotaFlagsIQ.STOP_AND_GO_HACK.value
|
||||
CP.minEnableSpeed = -1.
|
||||
CP.autoResumeSng = CP.openpilotLongitudinalControl
|
||||
18
iqdbc_repo/iqdbc/lvbs/car/iq_lateral.py
Normal file
18
iqdbc_repo/iqdbc/lvbs/car/iq_lateral.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.lateral import apply_center_deadzone
|
||||
|
||||
|
||||
def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float,
|
||||
torque_params: structs.CarParams.LateralTorqueTuning) -> float:
|
||||
# TODO torque params' friction should be in lataxel space, not torque space
|
||||
friction_interp = np.interp(
|
||||
apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone),
|
||||
[-friction_threshold, friction_threshold],
|
||||
[-torque_params.friction, torque_params.friction]
|
||||
)
|
||||
return float(friction_interp)
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/mazda/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/mazda/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/nissan/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/nissan/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
32
iqdbc_repo/iqdbc/lvbs/car/nissan/carstate_ext.py
Normal file
32
iqdbc_repo/iqdbc/lvbs/car/nissan/carstate_ext.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.lvbs.car.nissan.values import BUTTONS
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.button_events = []
|
||||
self.button_states = {button.event_type: False for button in BUTTONS}
|
||||
|
||||
def update(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser]):
|
||||
cp = can_parsers[Bus.pt]
|
||||
|
||||
button_events = []
|
||||
for button in BUTTONS:
|
||||
state = (cp.vl[button.can_addr][button.can_msg] in button.values)
|
||||
if self.button_states[button.event_type] != state:
|
||||
event = structs.CarState.ButtonEvent.new_message()
|
||||
event.type = button.event_type
|
||||
event.pressed = state
|
||||
button_events.append(event)
|
||||
self.button_states[button.event_type] = state
|
||||
self.button_events = button_events
|
||||
23
iqdbc_repo/iqdbc/lvbs/car/nissan/values.py
Normal file
23
iqdbc_repo/iqdbc/lvbs/car/nissan/values.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from iqdbc.car import structs
|
||||
|
||||
|
||||
class NissanSafetyFlagsIQ:
|
||||
DEFAULT = 0
|
||||
LEAF = 1
|
||||
|
||||
|
||||
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
|
||||
|
||||
|
||||
BUTTONS = [
|
||||
Button(ButtonType.accelCruise, "CRUISE_THROTTLE", "RES_BUTTON", [1]),
|
||||
Button(ButtonType.decelCruise, "CRUISE_THROTTLE", "SET_BUTTON", [1]),
|
||||
]
|
||||
69
iqdbc_repo/iqdbc/lvbs/car/platform_list.py
Normal file
69
iqdbc_repo/iqdbc/lvbs/car/platform_list.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import unicodedata
|
||||
|
||||
from iqdbc.car.common.basedir import BASEDIR
|
||||
from iqdbc.car.docs import get_all_footnotes, get_params_for_docs
|
||||
from iqdbc.car.values import PLATFORMS
|
||||
|
||||
CAR_LIST_JSON_OUT = os.path.join(BASEDIR, "../", "iqpilot", "car", "car_list.json")
|
||||
|
||||
|
||||
def get_car_list() -> dict[str, dict[str, list[str] | str]]:
|
||||
collected_footnote = get_all_footnotes()
|
||||
sorted_list: dict[str, dict[str, list[str] | str]] = collect_car_docs(PLATFORMS, collected_footnote)
|
||||
return sorted_list
|
||||
|
||||
|
||||
def _natural_sort_key(s):
|
||||
# NFKD normalization ensures accented characters sort with their base letter (e.g., Š sorts with S)
|
||||
normalized = unicodedata.normalize('NFKD', s)
|
||||
return [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', normalized) if t]
|
||||
|
||||
|
||||
def collect_car_docs(platforms, footnotes) -> dict[str, dict[str, list[str] | str]]:
|
||||
cars: dict[str, dict[str, list[str] | str]] = {}
|
||||
for model, platform in platforms.items():
|
||||
car_docs = platform.config.get_all_docs()
|
||||
CP, CP_IQ = get_params_for_docs(platform)
|
||||
|
||||
if CP.dashcamOnly or not len(car_docs):
|
||||
continue
|
||||
|
||||
# A platform can include multiple car models
|
||||
for _car_docs in car_docs:
|
||||
if not hasattr(_car_docs, "row"):
|
||||
_car_docs.init_make(CP)
|
||||
_car_docs.init(CP, footnotes)
|
||||
cars[_car_docs.name] = model
|
||||
|
||||
_platform = model
|
||||
_name = _car_docs.name
|
||||
_make = _car_docs.make
|
||||
_brand = _car_docs.brand
|
||||
_model = _car_docs.model
|
||||
_years = _car_docs.year_list
|
||||
_package = _car_docs.package if _car_docs.package else []
|
||||
|
||||
cars[_name] = {
|
||||
"platform": _platform,
|
||||
"make": _make,
|
||||
"brand": _brand,
|
||||
"model": _model,
|
||||
"year": _years if _years else [],
|
||||
"package": _package,
|
||||
}
|
||||
|
||||
# Sort cars by make and model + year
|
||||
sorted_cars = sorted(cars.keys(), key=lambda car: _natural_sort_key(car))
|
||||
sorted_car_list = {car: cars[car] for car in sorted_cars}
|
||||
return sorted_car_list
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
platform_list = get_car_list()
|
||||
|
||||
with open(CAR_LIST_JSON_OUT, "w") as json_file:
|
||||
json.dump(platform_list, json_file, indent=2, ensure_ascii=False)
|
||||
print(f"Generated and written to {CAR_LIST_JSON_OUT}")
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/rivian/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/rivian/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
33
iqdbc_repo/iqdbc/lvbs/car/rivian/aol.py
Normal file
33
iqdbc_repo/iqdbc/lvbs/car/rivian/aol.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from collections import namedtuple
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.interfaces import CarStateBase
|
||||
|
||||
MAX_STEERING_ANGLE = 90.0
|
||||
|
||||
AolDataIQ = namedtuple("AolDataIQ",
|
||||
["lka_icon_states", "lat_active"])
|
||||
|
||||
|
||||
class AolCarController:
|
||||
def __init__(self):
|
||||
self.aol = AolDataIQ(False, False)
|
||||
|
||||
self.lka_icon_states = False
|
||||
self.lat_active = False
|
||||
|
||||
def aol_status_update(self, CC: structs.CarControl, CC_IQ: structs.IQCarControl, CS: CarStateBase) -> AolDataIQ:
|
||||
if CC_IQ.aol.available:
|
||||
self.lka_icon_states = self.lat_active
|
||||
self.lat_active = CC.latActive and abs(CS.out.steeringAngleDeg) < MAX_STEERING_ANGLE
|
||||
else:
|
||||
self.lka_icon_states = CC.enabled
|
||||
self.lat_active = CC.latActive
|
||||
|
||||
return AolDataIQ(self.lka_icon_states, self.lat_active)
|
||||
|
||||
def update(self, CC: structs.CarControl, CC_IQ: structs.IQCarControl, CS: CarStateBase) -> None:
|
||||
self.aol = self.aol_status_update(CC, CC_IQ, CS)
|
||||
100
iqdbc_repo/iqdbc/lvbs/car/rivian/carstate_ext.py
Normal file
100
iqdbc_repo/iqdbc/lvbs/car/rivian/carstate_ext.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import math
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car.common.conversions import Conversions as CV
|
||||
from iqdbc.car.rivian.values import DBC
|
||||
from iqdbc.lvbs.car.rivian.values import RivianFlagsIQ
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
MAX_SET_SPEED = 85 * CV.MPH_TO_MS
|
||||
MIN_SET_SPEED = 20 * CV.MPH_TO_MS
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.set_speed = 10
|
||||
self.increase_button = False
|
||||
self.decrease_button = False
|
||||
self.distance_button = 0
|
||||
self.increase_counter = 0
|
||||
self.decrease_counter = 0
|
||||
self.stalk_down_counter = 0
|
||||
|
||||
def update_longitudinal_upgrade(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp_park = can_parsers[Bus.alt]
|
||||
cp_adas = can_parsers[Bus.adas]
|
||||
cp = can_parsers[Bus.pt]
|
||||
|
||||
prev_increase_button = self.increase_button
|
||||
prev_decrease_button = self.decrease_button
|
||||
|
||||
if self.CP.openpilotLongitudinalControl:
|
||||
# distance scroll wheel
|
||||
right_scroll = cp_park.vl["WheelButtons"]["RightButton_Scroll"]
|
||||
if right_scroll != 255:
|
||||
if self.distance_button != right_scroll:
|
||||
ret.buttonEvents = [structs.CarState.ButtonEvent(pressed=False, type=ButtonType.gapAdjustCruise)]
|
||||
self.distance_button = right_scroll
|
||||
|
||||
# button logic for set-speed
|
||||
self.increase_button = cp_park.vl["WheelButtons"]["RightButton_RightClick"] == 2
|
||||
self.decrease_button = cp_park.vl["WheelButtons"]["RightButton_LeftClick"] == 2
|
||||
|
||||
self.increase_counter = self.increase_counter + 1 if self.increase_button else 0
|
||||
self.decrease_counter = self.decrease_counter + 1 if self.decrease_button else 0
|
||||
|
||||
metric = cp_adas.vl["Cluster"]["Cluster_Unit"] == 0
|
||||
conversion = CV.KPH_TO_MS if metric else CV.MPH_TO_MS
|
||||
long_press_step = 10.0 if metric else 5.0
|
||||
set_speed_converted = self.set_speed * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)
|
||||
|
||||
if self.increase_button:
|
||||
if self.increase_counter % 66 == 0:
|
||||
self.set_speed = (int(math.ceil((set_speed_converted + 1) / long_press_step)) * long_press_step) * conversion
|
||||
elif not prev_increase_button:
|
||||
self.set_speed += conversion
|
||||
|
||||
if self.decrease_button:
|
||||
if self.decrease_counter % 66 == 0:
|
||||
self.set_speed = (int(math.floor((set_speed_converted - 1) / long_press_step)) * long_press_step) * conversion
|
||||
elif not prev_decrease_button:
|
||||
self.set_speed -= conversion
|
||||
|
||||
if not ret.cruiseState.enabled:
|
||||
self.set_speed = ret.vEgoCluster
|
||||
|
||||
# VDM_UserAdasRequest: 0=IDLE, 1=UP_1, 2=UP_2, 3=DOWN_1, 4=DOWN_2
|
||||
stalk_down = int(cp.vl["VDM_AdasSts"]["VDM_UserAdasRequest"]) in (3, 4)
|
||||
self.stalk_down_counter = self.stalk_down_counter + 1 if stalk_down else 0
|
||||
if self.stalk_down_counter == 50:
|
||||
# Mimic Rivian ACC: holding stalk 0.5s sets speed to current speed (never decreases)
|
||||
self.set_speed = max(self.set_speed, ret.vEgoCluster)
|
||||
|
||||
self.set_speed = max(MIN_SET_SPEED, min(self.set_speed, MAX_SET_SPEED))
|
||||
ret.cruiseState.speed = self.set_speed
|
||||
|
||||
if self.CP.enableBsm:
|
||||
ret.leftBlindspot = cp_park.vl["BSM_BlindSpotIndicator"]["BSM_BlindSpotIndicator_Left"] != 0
|
||||
ret.rightBlindspot = cp_park.vl["BSM_BlindSpotIndicator"]["BSM_BlindSpotIndicator_Right"] != 0
|
||||
|
||||
def update(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
if self.CP_IQ.flags & RivianFlagsIQ.LONGITUDINAL_HARNESS_UPGRADE:
|
||||
self.update_longitudinal_upgrade(ret, can_parsers)
|
||||
|
||||
@staticmethod
|
||||
def get_parser(CP, CP_IQ) -> dict[StrEnum, CANParser]:
|
||||
messages = {}
|
||||
|
||||
if CP_IQ.flags & RivianFlagsIQ.LONGITUDINAL_HARNESS_UPGRADE:
|
||||
messages[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.alt], [], 5)
|
||||
|
||||
return messages
|
||||
10
iqdbc_repo/iqdbc/lvbs/car/rivian/values.py
Normal file
10
iqdbc_repo/iqdbc/lvbs/car/rivian/values.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class RivianFlagsIQ(IntFlag):
|
||||
LONGITUDINAL_HARNESS_UPGRADE = 1
|
||||
|
||||
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/subaru/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/subaru/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
45
iqdbc_repo/iqdbc/lvbs/car/subaru/aol.py
Normal file
45
iqdbc_repo/iqdbc/lvbs/car/subaru/aol.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
from iqdbc.car import Bus, structs
|
||||
|
||||
from iqdbc.car.subaru.values import SubaruFlags
|
||||
from iqdbc.lvbs.aol_base import AolCarStateBase
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class AolCarState(AolCarStateBase):
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
super().__init__(CP, CP_IQ)
|
||||
|
||||
@staticmethod
|
||||
def create_lkas_button_events(cur_btn: int, prev_btn: int,
|
||||
buttons_dict: dict[int, structs.CarState.ButtonEvent.Type]) -> list[structs.CarState.ButtonEvent]:
|
||||
events: list[structs.CarState.ButtonEvent] = []
|
||||
|
||||
if cur_btn == prev_btn:
|
||||
return events
|
||||
|
||||
state_changes = [
|
||||
{"pressed": prev_btn != cur_btn and cur_btn != 2 and not (prev_btn == 2 and cur_btn == 1)},
|
||||
{"pressed": prev_btn != cur_btn and cur_btn == 2 and cur_btn != 1},
|
||||
]
|
||||
|
||||
for change in state_changes:
|
||||
if change["pressed"]:
|
||||
events.append(structs.CarState.ButtonEvent(pressed=change["pressed"],
|
||||
type=buttons_dict.get(cur_btn, ButtonType.unknown)))
|
||||
return events
|
||||
|
||||
def update_aol(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
self.prev_lkas_button = self.lkas_button
|
||||
if not self.CP.flags & SubaruFlags.PREGLOBAL:
|
||||
self.lkas_button = cp_cam.vl["ES_LKAS_State"]["LKAS_Dash_State"]
|
||||
|
||||
ret.buttonEvents = self.create_lkas_button_events(self.lkas_button, self.prev_lkas_button, {1: ButtonType.lkas})
|
||||
121
iqdbc_repo/iqdbc/lvbs/car/subaru/stop_and_go.py
Normal file
121
iqdbc_repo/iqdbc/lvbs/car/subaru/stop_and_go.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import copy
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs, DT_CTRL
|
||||
from iqdbc.car.can_definitions import CanData
|
||||
from iqdbc.car.interfaces import CarStateBase
|
||||
from iqdbc.car.subaru.values import SubaruFlags
|
||||
|
||||
from iqdbc.lvbs.car.subaru import subarucan_ext
|
||||
from iqdbc.lvbs.car.subaru.values_ext import SubaruFlagsIQ
|
||||
from iqdbc.can.parser import CANParser
|
||||
|
||||
_SNG_ACC_MIN_DIST = 3
|
||||
_SNG_ACC_MAX_DIST = 4.5
|
||||
|
||||
|
||||
class IQStopAndGoController:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
self.enabled = CP_IQ.flags & (SubaruFlagsIQ.STOP_AND_GO | SubaruFlagsIQ.STOP_AND_GO_MANUAL_PARKING_BRAKE)
|
||||
self.manual_parking_brake = CP_IQ.flags & SubaruFlagsIQ.STOP_AND_GO_MANUAL_PARKING_BRAKE
|
||||
|
||||
self.last_standstill_frame = 0
|
||||
self.epb_resume_frames_remaining = -1
|
||||
self.prev_close_distance = 0.0
|
||||
|
||||
def update_epb_resume_sequence(self, should_resume: bool) -> bool:
|
||||
if self.manual_parking_brake:
|
||||
return False
|
||||
|
||||
if should_resume:
|
||||
self.epb_resume_frames_remaining = 15
|
||||
|
||||
send_resume = self.epb_resume_frames_remaining > 0
|
||||
if self.epb_resume_frames_remaining > 0:
|
||||
self.epb_resume_frames_remaining -= 1
|
||||
|
||||
return send_resume
|
||||
|
||||
def update_stop_and_go(self, CC: structs.CarControl, CS: CarStateBase, frame: int) -> bool:
|
||||
"""
|
||||
Manages stop-and-go functionality for adaptive cruise control (ACC).
|
||||
|
||||
Args:
|
||||
CC: Car control data
|
||||
CS: Car state data
|
||||
frame: Current frame number
|
||||
|
||||
Returns:
|
||||
bool: True if resume command should be sent, False otherwise
|
||||
"""
|
||||
|
||||
if not CC.enabled or not CC.hudControl.leadVisible:
|
||||
return False
|
||||
|
||||
close_distance = CS.es_distance_msg["Close_Distance"]
|
||||
in_standstill = CS.out.standstill
|
||||
|
||||
if not in_standstill:
|
||||
self.last_standstill_frame = frame
|
||||
|
||||
# Check if we've been in standstill long enough
|
||||
mpb_standstill_timers = (0.75, 0.8) if self.CP.flags & SubaruFlags.PREGLOBAL else (0.5, 0.55)
|
||||
standstill_duration = (frame - self.last_standstill_frame) * DT_CTRL
|
||||
in_standstill_hold = standstill_duration > mpb_standstill_timers[0]
|
||||
if (frame - self.last_standstill_frame) * DT_CTRL >= mpb_standstill_timers[1]:
|
||||
self.last_standstill_frame = frame
|
||||
|
||||
# Car state distance-based conditions (EPB only)
|
||||
in_resume_distance = _SNG_ACC_MIN_DIST < close_distance < _SNG_ACC_MAX_DIST
|
||||
distance_increasing = close_distance > self.prev_close_distance
|
||||
distance_resume_allowed = in_resume_distance and distance_increasing
|
||||
|
||||
if self.manual_parking_brake:
|
||||
# Manual parking brake: Direct resume when the standstill hold threshold is reached to prevent ACC fault
|
||||
send_resume = in_standstill_hold
|
||||
else:
|
||||
# EPB: Resume sequence with trigger on distance with lead car increasing
|
||||
should_resume = CS.out.standstill and distance_resume_allowed
|
||||
send_resume = self.update_epb_resume_sequence(should_resume)
|
||||
|
||||
self.prev_close_distance = close_distance
|
||||
|
||||
return send_resume
|
||||
|
||||
def create_stop_and_go(self, packer, CC: structs.CarControl, CS: CarStateBase, frame: int) -> list[CanData]:
|
||||
can_sends = []
|
||||
|
||||
if not self.enabled:
|
||||
return can_sends
|
||||
|
||||
send_resume = self.update_stop_and_go(CC, CS, frame)
|
||||
|
||||
can_sends.append(subarucan_ext.create_throttle(packer, self.CP, CS.throttle_msg, send_resume and not self.manual_parking_brake))
|
||||
|
||||
if frame % 2 == 0:
|
||||
can_sends.append(subarucan_ext.create_brake_pedal(packer, self.CP, CS.brake_pedal_msg, send_resume and self.manual_parking_brake))
|
||||
|
||||
return can_sends
|
||||
|
||||
|
||||
class IQStopAndGoState:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.brake_pedal_msg: dict[str, float] = {}
|
||||
self.throttle_msg: dict[str, float] = {}
|
||||
|
||||
def update(self, ret: structs.CarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
|
||||
self.brake_pedal_msg = copy.copy(cp.vl["Brake_Pedal"])
|
||||
|
||||
if not self.CP.flags & SubaruFlags.HYBRID:
|
||||
self.throttle_msg = copy.copy(cp.vl["Throttle"])
|
||||
68
iqdbc_repo/iqdbc/lvbs/car/subaru/subarucan_ext.py
Normal file
68
iqdbc_repo/iqdbc/lvbs/car/subaru/subarucan_ext.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from iqdbc.car.subaru.values import CanBus, SubaruFlags
|
||||
|
||||
|
||||
def create_counter(msg):
|
||||
return (msg["COUNTER"] + 1) % 0x10
|
||||
|
||||
|
||||
def create_throttle(packer, CP, throttle_msg, send_resume):
|
||||
if CP.flags & SubaruFlags.PREGLOBAL:
|
||||
values = {s: throttle_msg[s] for s in [
|
||||
"Throttle_Pedal",
|
||||
"Signal1",
|
||||
"Not_Full_Throttle",
|
||||
"Signal2",
|
||||
"Engine_RPM",
|
||||
"Off_Throttle",
|
||||
"Signal3",
|
||||
"Throttle_Cruise",
|
||||
"Throttle_Combo",
|
||||
"Throttle_Body",
|
||||
"Off_Throttle_2",
|
||||
"Signal4",
|
||||
]}
|
||||
else:
|
||||
values = {s: throttle_msg[s] for s in [
|
||||
"CHECKSUM",
|
||||
"Signal1",
|
||||
"Engine_RPM",
|
||||
"Neutral",
|
||||
"Throttle_Pedal",
|
||||
"Throttle_Cruise",
|
||||
"Throttle_Combo",
|
||||
"Signal3",
|
||||
"Off_Accel",
|
||||
]}
|
||||
|
||||
values["COUNTER"] = create_counter(throttle_msg)
|
||||
|
||||
if send_resume:
|
||||
values["Throttle_Pedal"] = 5
|
||||
|
||||
return packer.make_can_msg("Throttle", CanBus.camera, values)
|
||||
|
||||
|
||||
def create_brake_pedal(packer, CP, brake_pedal_msg, send_resume):
|
||||
if CP.flags & SubaruFlags.PREGLOBAL:
|
||||
values = {s: brake_pedal_msg[s] for s in [
|
||||
"Speed",
|
||||
"Brake_Pedal",
|
||||
"Signal1",
|
||||
]}
|
||||
else:
|
||||
values = {s: brake_pedal_msg[s] for s in [
|
||||
"CHECKSUM",
|
||||
"Signal1",
|
||||
"Speed",
|
||||
"Signal2",
|
||||
"Brake_Lights",
|
||||
"Signal3",
|
||||
"Brake_Pedal",
|
||||
"Signal4",
|
||||
]}
|
||||
values["COUNTER"] = create_counter(brake_pedal_msg)
|
||||
|
||||
if send_resume:
|
||||
values["Speed"] = 1 if CP.flags & SubaruFlags.PREGLOBAL else 3
|
||||
|
||||
return packer.make_can_msg("Brake_Pedal", CanBus.camera, values)
|
||||
14
iqdbc_repo/iqdbc/lvbs/car/subaru/values_ext.py
Normal file
14
iqdbc_repo/iqdbc/lvbs/car/subaru/values_ext.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class SubaruSafetyFlagsIQ:
|
||||
STOP_AND_GO = 1
|
||||
|
||||
|
||||
class SubaruFlagsIQ(IntFlag):
|
||||
STOP_AND_GO = 1
|
||||
STOP_AND_GO_MANUAL_PARKING_BRAKE = 2
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/tesla/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/tesla/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
72
iqdbc_repo/iqdbc/lvbs/car/tesla/carstate_ext.py
Normal file
72
iqdbc_repo/iqdbc/lvbs/car/tesla/carstate_ext.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, create_button_events, structs
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car.common.conversions import Conversions as CV
|
||||
from iqdbc.car.tesla.values import DBC, CANBUS
|
||||
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP: structs.CarParams, CP_IQ: structs.IQCarParams):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.infotainment_3_finger_press = 0
|
||||
|
||||
def update(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
if self.CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS:
|
||||
cp_adas = can_parsers[Bus.adas]
|
||||
|
||||
prev_infotainment_3_finger_press = self.infotainment_3_finger_press
|
||||
self.infotainment_3_finger_press = int(cp_adas.vl["UI_status2"]["UI_activeTouchPoints"])
|
||||
|
||||
ret.buttonEvents = [*create_button_events(self.infotainment_3_finger_press, prev_infotainment_3_finger_press,
|
||||
{3: ButtonType.lkas})]
|
||||
|
||||
bms_soc_ui = float(cp_adas.vl["ID292BMS_SOC"].get("SOCUI292", 0.0))
|
||||
ui_range_mi = float(cp_adas.vl["ID33AUI_rangeSOC"].get("UI_Range", 0.0))
|
||||
hv_batt_voltage_v = float(cp_adas.vl["ID132HVBattAmpVolt"].get("BattVoltage132", 0.0))
|
||||
battery_details = None
|
||||
try:
|
||||
battery_details = ret.batteryDetails
|
||||
except Exception:
|
||||
battery_details = None
|
||||
|
||||
soc_ui = bms_soc_ui if 0.0 <= bms_soc_ui <= 102.3 else None
|
||||
if soc_ui is not None:
|
||||
ret.fuelGauge = min(100.0, soc_ui) / 100.0
|
||||
if battery_details is not None:
|
||||
battery_details.soc = soc_ui
|
||||
battery_details.charge = soc_ui
|
||||
if 0.0 <= ui_range_mi <= 1023.0 and battery_details is not None:
|
||||
battery_details.capacity = ui_range_mi
|
||||
if 0.0 < hv_batt_voltage_v <= 800.0 and battery_details is not None:
|
||||
battery_details.voltage = hv_batt_voltage_v
|
||||
|
||||
cp_party = can_parsers[Bus.party]
|
||||
cp_ap_party = can_parsers[Bus.ap_party]
|
||||
|
||||
speed_units = self.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp_party.vl["DI_state"]["DI_speedUnits"]), None)
|
||||
speed_limit = cp_ap_party.vl["DAS_status"]["DAS_fusedSpeedLimit"]
|
||||
if self.can_define.dv["DAS_status"]["DAS_fusedSpeedLimit"].get(int(speed_limit), None) in ["NONE", "UNKNOWN_SNA"]:
|
||||
ret_iq.speedLimit = 0
|
||||
else:
|
||||
if speed_units == "KPH":
|
||||
ret_iq.speedLimit = speed_limit * CV.KPH_TO_MS
|
||||
elif speed_units == "MPH":
|
||||
ret_iq.speedLimit = speed_limit * CV.MPH_TO_MS
|
||||
|
||||
@staticmethod
|
||||
def get_parser(CP: structs.CarParams, CP_IQ: structs.IQCarParams) -> dict[StrEnum, CANParser]:
|
||||
messages = {}
|
||||
|
||||
if CP_IQ.flags & TeslaFlagsIQ.HAS_VEHICLE_BUS:
|
||||
messages[Bus.adas] = CANParser(DBC[CP.carFingerprint][Bus.adas], [], CANBUS.vehicle)
|
||||
|
||||
return messages
|
||||
312
iqdbc_repo/iqdbc/lvbs/car/tesla/coop_steering.py
Normal file
312
iqdbc_repo/iqdbc/lvbs/car/tesla/coop_steering.py
Normal file
@@ -0,0 +1,312 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import math
|
||||
import numpy as np
|
||||
from collections import namedtuple
|
||||
from dataclasses import replace
|
||||
|
||||
from iqdbc.car import structs, rate_limit, DT_CTRL
|
||||
from iqdbc.car.vehicle_model import VehicleModel
|
||||
from iqdbc.car.lateral import apply_steer_angle_limits_vm
|
||||
from iqdbc.car.tesla.values import CarControllerParams
|
||||
from iqdbc.lvbs.car.tesla.values import TeslaFlagsIQ
|
||||
|
||||
|
||||
DT_LAT_CTRL = DT_CTRL * CarControllerParams.STEER_STEP
|
||||
|
||||
class CoopSteeringCarControllerParams(CarControllerParams):
|
||||
ANGLE_LIMITS = replace(CarControllerParams.ANGLE_LIMITS, MAX_ANGLE_RATE=5)
|
||||
|
||||
STEERING_DEG_PHASE_LEAD_COEFF = 8.0
|
||||
|
||||
# angle override # todo implement steering torque inertia compensation to increase gains
|
||||
STEER_OVERRIDE_MIN_TORQUE = 0.5 # Nm - based on typical steering bias + noise
|
||||
STEER_OVERRIDE_MAX_TORQUE = 2.5 # Nm max torque before EPS disengages, LKAS takes over at 1.8Nm
|
||||
STEER_OVERRIDE_MAX_LAT_ACCEL = 1.5 # m/s^2 - determines angle rate - speed dependent - similar to Tesla comfort steering mode
|
||||
STEER_OVERRIDE_LAT_ACCEL_GAIN_LIMIT = 10 # deg/Nm stability and smoothness for angle control # todo this could be increased after solving feedback stability
|
||||
|
||||
# angle ramping
|
||||
STEER_OVERRIDE_MAX_LAT_JERK = 2.0 # m/s^3 - determines angle ramping rate - speed dependent
|
||||
STEER_OVERRIDE_MAX_LAT_JERK_CENTERING = CoopSteeringCarControllerParams.ANGLE_LIMITS.MAX_LATERAL_JERK # m/s^3 - for low speed angle ramp down
|
||||
# stability and smoothness for angle ramp control - at very low speeds this takes precedence over jerk settings
|
||||
STEER_OVERRIDE_LAT_JERK_GAIN_LIMIT = 100 # deg/s/Nm - should be less than CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE / DT_CTRL / STEER_OVERRIDE_TORQUE_RANGE
|
||||
STEER_OVERRIDE_TORQUE_RANGE = STEER_OVERRIDE_MAX_TORQUE - STEER_OVERRIDE_MIN_TORQUE
|
||||
|
||||
# model fighting mitigation
|
||||
STEER_DESIRED_LIMITER_ALLOW_SPEED = 6.0 # m/s - below this speed the desired angle limiter is active
|
||||
STEER_DESIRED_LIMITER_ACCEL = 100 # deg/s^2 when override angle ramp is active
|
||||
STEER_DESIRED_LIMITER_OVERRIDE_ACTIVE_COUNTER = 0.7 # second
|
||||
|
||||
# limit model acceleration when engaging
|
||||
STEER_RESUME_RATE_LIMIT_RAMP_RATE = 500 # deg/s^2 - controls rate of rise of angle rate limit, not angle directly
|
||||
|
||||
|
||||
CoopSteeringDataIQ = namedtuple("CoopSteeringDataIQ",
|
||||
["steeringAngleDeg", "lat_active", "control_type"])
|
||||
|
||||
def get_steer_from_lat_accel(lat_accel, v_ego: float, VM: VehicleModel):
|
||||
"""Calculate the maximum steering angle based on lateral acceleration."""
|
||||
curvature = lat_accel / (max(1, v_ego) ** 2) # 1/m
|
||||
return math.degrees(VM.get_steer_from_curvature(curvature, v_ego, 0)) # deg
|
||||
|
||||
|
||||
def apply_bounds(signal: float, limit: float) -> float:
|
||||
"""Limit input to a range."""
|
||||
return float(np.clip(signal, -limit, limit))
|
||||
|
||||
|
||||
def apply_deadzone(signal: float, deadzone: float) -> float:
|
||||
"""Apply deadzone to input."""
|
||||
return signal - apply_bounds(signal, deadzone)
|
||||
|
||||
|
||||
def calc_override_angle_limited(torque: float, vEgo: float, VM: VehicleModel, lat_accel) -> float:
|
||||
"""
|
||||
Map driver torque to lateral acceleration and convert to steering angle.
|
||||
Limit gain for stability with EPS and torque sensor interaction.
|
||||
"""
|
||||
|
||||
# lateral accel is linear in respect to angle so it's fine to interpolate it with torque
|
||||
torque_to_angle = get_steer_from_lat_accel(lat_accel, vEgo, VM) / STEER_OVERRIDE_TORQUE_RANGE
|
||||
|
||||
# limit the gain to prevent jerkiness and instability
|
||||
gain_limit = STEER_OVERRIDE_LAT_ACCEL_GAIN_LIMIT
|
||||
override_angle_target = torque * min(torque_to_angle, gain_limit)
|
||||
|
||||
return override_angle_target
|
||||
|
||||
|
||||
def calc_override_angle_delta_limited(torque: float, vEgo: float, VM: VehicleModel, lat_jerk) -> float:
|
||||
"""
|
||||
Map driver torque to lateral jerk and convert to steering speed.
|
||||
Limit gain for stability with EPS and torque sensor interaction.
|
||||
"""
|
||||
|
||||
# prevents windup in carcontroller rate limiter
|
||||
lat_jerk = min(lat_jerk, CoopSteeringCarControllerParams.ANGLE_LIMITS.MAX_LATERAL_JERK)
|
||||
|
||||
# lateral accel is linear in respect to angle so it's fine to interpolate it with torque
|
||||
torque_to_angle = get_steer_from_lat_accel(lat_jerk, vEgo, VM) / STEER_OVERRIDE_TORQUE_RANGE
|
||||
# limit the gain to prevent jerkiness and instability
|
||||
gain_limit = min(STEER_OVERRIDE_LAT_JERK_GAIN_LIMIT, CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE / DT_CTRL / STEER_OVERRIDE_TORQUE_RANGE)
|
||||
override_angle_rate = torque * min(torque_to_angle, gain_limit)
|
||||
|
||||
# prevent windup in angle rate limiter
|
||||
return apply_bounds(override_angle_rate * DT_LAT_CTRL, CoopSteeringCarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE)
|
||||
|
||||
|
||||
class SteerRateLimiter:
|
||||
"""Handles rate limiting of steering angle changes with a configurable rate."""
|
||||
def __init__(self):
|
||||
self._last = 0.0
|
||||
|
||||
def reset(self, angle: float) -> None:
|
||||
"""Reset the rate limiter state with the given angle."""
|
||||
self._last = angle
|
||||
|
||||
def update(self, angle: float, angle_delta_lim: float) -> float:
|
||||
angle_lim = rate_limit(angle, self._last, -angle_delta_lim, angle_delta_lim)
|
||||
self._last = angle_lim
|
||||
return angle_lim
|
||||
|
||||
|
||||
class SteerAccelLimiter:
|
||||
"""
|
||||
Second-order limiter for steering angle:
|
||||
- Limits angular acceleration (change in allowed angular rate).
|
||||
- Enforces a hard max angular rate.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.delta_rl = SteerRateLimiter()
|
||||
self.angle_cmd = 0.0
|
||||
|
||||
def reset(self, angle: float) -> None:
|
||||
self.delta_rl.reset(0)
|
||||
self.angle_cmd = angle
|
||||
|
||||
def update(self, angle_target: float, max_rate: float, accel: float, decel: float, dt: float) -> float:
|
||||
if dt <= 0.0:
|
||||
return self.angle_cmd
|
||||
|
||||
# acceleration limits per update step
|
||||
accel_delta = max(0.0, accel) * (dt * dt)
|
||||
decel_delta = max(0.0, decel) * (dt * dt)
|
||||
|
||||
err = angle_target - self.angle_cmd
|
||||
err = apply_bounds(err, max(0.0, max_rate) * dt)
|
||||
|
||||
# acceleration (towards target) or deceleration (away from target)
|
||||
if err * self.delta_rl._last < 0:
|
||||
delta = decel_delta
|
||||
else:
|
||||
delta = accel_delta
|
||||
|
||||
# Handle large decel (enabled with inf value)
|
||||
if decel == np.inf and err * self.delta_rl._last < 0:
|
||||
# if output crosses the target or target crosses the output
|
||||
self.delta_rl._last = 0
|
||||
angle_out = self.angle_cmd
|
||||
else:
|
||||
self.delta_rl._last = self.delta_rl.update(err, delta)
|
||||
if decel == np.inf:
|
||||
# if we are close to target, snap to it before we cross it
|
||||
self.delta_rl._last = apply_bounds(self.delta_rl._last, abs(err))
|
||||
angle_out = self.angle_cmd + self.delta_rl._last
|
||||
|
||||
# Integrate
|
||||
self.angle_cmd = angle_out
|
||||
|
||||
return angle_out
|
||||
|
||||
|
||||
class CoopSteeringCarController:
|
||||
def __init__(self):
|
||||
self.coop_apply_angle_last = 0
|
||||
self.coop_apply_angle_last_sat = 0
|
||||
self.override_angle_accu = 0
|
||||
self.override_active_counter = 0 # Counter for how many cycles torque is below threshold
|
||||
self.resume_rate_limiter_delta = SteerRateLimiter()
|
||||
self.resume_rate_limiter = SteerRateLimiter()
|
||||
self.override_accel_rate_limiter = SteerAccelLimiter()
|
||||
self.debug_angle_desired_limited = 0
|
||||
|
||||
def apply_override_angle_direct(self, lat_active: bool, driverTorque: float, vEgo: float, VM: VehicleModel) -> float:
|
||||
"""
|
||||
Emulates steering springiness based on lateral acceleration exerted on the steering rack.
|
||||
We rely on apply_override_angle_ramp to reach the max angle at low speeds.
|
||||
At low speed lateral acceleration approaches infinity and it is not good proxy
|
||||
for the torque to target angle conversion and needs to be limited
|
||||
|
||||
"""
|
||||
if not lat_active:
|
||||
return 0.0
|
||||
|
||||
## torque to position
|
||||
# ignore torque sensor offset and disturbances
|
||||
steering_torque_with_deadzone = apply_deadzone(driverTorque, STEER_OVERRIDE_MIN_TORQUE)
|
||||
angle_override = calc_override_angle_limited(steering_torque_with_deadzone, vEgo, VM, STEER_OVERRIDE_MAX_LAT_ACCEL)
|
||||
return angle_override
|
||||
|
||||
def apply_override_angle_relative(self, lat_active: bool, driverTorque: float, vEgo: float,
|
||||
VM: VehicleModel, unwind_weight: float = 1.0) -> float:
|
||||
"""
|
||||
Converts steering torque to steering rotation rate.
|
||||
Physically angle rate is related to viscous damping of tires rotating on the ground.
|
||||
Here, however, the angle rate target is obtained from lateral jerk limit
|
||||
as a reasonable safe rate which decays quadratically with vehicle speed.
|
||||
"""
|
||||
if not lat_active:
|
||||
self.override_angle_accu = 0
|
||||
return 0
|
||||
|
||||
# unwind accumulator toward zero if the previous loop saturated (apply_steer_angle_limits_vm)
|
||||
unwind = (self.coop_apply_angle_last - self.coop_apply_angle_last_sat) * unwind_weight
|
||||
if self.override_angle_accu * unwind > 0:
|
||||
unwind = apply_bounds(unwind, abs(self.override_angle_accu))
|
||||
self.override_angle_accu -= unwind
|
||||
|
||||
# torque biasing emulates the steering centering when released:
|
||||
if self.override_angle_accu > 0 and abs(vEgo) > 0.1:
|
||||
torque_biased = driverTorque - STEER_OVERRIDE_MIN_TORQUE
|
||||
elif self.override_angle_accu < 0 and abs(vEgo) > 0.1:
|
||||
torque_biased = driverTorque + STEER_OVERRIDE_MIN_TORQUE
|
||||
else:
|
||||
# when override_angle_accu is reset this turns off everything
|
||||
torque_biased = apply_deadzone(driverTorque, STEER_OVERRIDE_MIN_TORQUE)
|
||||
|
||||
# higher rate when centering
|
||||
angle_override_delta = calc_override_angle_delta_limited(torque_biased, vEgo, VM,
|
||||
STEER_OVERRIDE_MAX_LAT_JERK if (torque_biased * self.override_angle_accu) > 0
|
||||
else STEER_OVERRIDE_MAX_LAT_JERK_CENTERING)
|
||||
|
||||
# ramp the angle
|
||||
new_override_angle_accu = self.override_angle_accu + angle_override_delta
|
||||
# snap to 0 if sign changes and driver torque is steering centering zone
|
||||
if (new_override_angle_accu * self.override_angle_accu) < 0 and abs(driverTorque) < STEER_OVERRIDE_MIN_TORQUE:
|
||||
new_override_angle_accu = 0
|
||||
|
||||
self.override_angle_accu = new_override_angle_accu
|
||||
|
||||
return self.override_angle_accu
|
||||
|
||||
def apply_override_angle_combined(self, lat_active: bool, driverTorque: float, vEgo: float, VM: VehicleModel) -> float:
|
||||
"""
|
||||
Combines direct and relative override angles based on direct angle override limitations (stability and practical range depending on vehicle speed).
|
||||
Effectively vehicle-speed based transition.
|
||||
"""
|
||||
if not lat_active:
|
||||
return 0
|
||||
|
||||
# calculate capability of direct angle override (fully active above ~36kph)
|
||||
direct_override_capability = (calc_override_angle_limited(STEER_OVERRIDE_TORQUE_RANGE, vEgo, VM, STEER_OVERRIDE_MAX_LAT_ACCEL) /
|
||||
get_steer_from_lat_accel(STEER_OVERRIDE_MAX_LAT_ACCEL, vEgo, VM))
|
||||
|
||||
angle_override_direct = self.apply_override_angle_direct(lat_active, driverTorque, vEgo, VM)
|
||||
relative_weight = 1.0 - direct_override_capability
|
||||
angle_override_relative = self.apply_override_angle_relative(lat_active, driverTorque, vEgo, VM,
|
||||
unwind_weight=relative_weight)
|
||||
|
||||
return angle_override_direct * direct_override_capability + angle_override_relative * relative_weight
|
||||
|
||||
def overriding_steer_desired_accel_limit(self, lat_active: bool, apply_angle: float, vEgo: float, steeringTorque: float) -> float:
|
||||
"""
|
||||
Acceleration rate limiter - limits acceleration but allows for quick deceleration (no overshoot)
|
||||
"""
|
||||
if not lat_active:
|
||||
self.override_accel_rate_limiter.reset(apply_angle)
|
||||
return apply_angle
|
||||
|
||||
if abs(steeringTorque) >= STEER_OVERRIDE_MIN_TORQUE:
|
||||
self.override_active_counter = 0
|
||||
else:
|
||||
self.override_active_counter += DT_LAT_CTRL
|
||||
self.override_active_counter = min(self.override_active_counter, STEER_DESIRED_LIMITER_OVERRIDE_ACTIVE_COUNTER)
|
||||
|
||||
max_angle_rate = CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE / DT_LAT_CTRL # MAX_ANGLE_RATE is per frame units so convert to real rate
|
||||
# this ensures no acceleration limit when override is disabled:
|
||||
max_angle_accel = max_angle_rate / DT_LAT_CTRL # ensures max deceleration
|
||||
if vEgo < STEER_DESIRED_LIMITER_ALLOW_SPEED:
|
||||
# Interpolate between STEER_DESIRED_LIMITER_ACCEL and max_angle_accel based on counter progress
|
||||
max_angle_accel = np.interp(
|
||||
self.override_active_counter,
|
||||
[0, STEER_DESIRED_LIMITER_OVERRIDE_ACTIVE_COUNTER],
|
||||
[STEER_DESIRED_LIMITER_ACCEL, max_angle_accel]
|
||||
)
|
||||
# max_angle_rate / DT_LAT_CTRL ensures max deceleration
|
||||
return self.override_accel_rate_limiter.update(apply_angle, max_angle_rate, max_angle_accel, np.inf, DT_LAT_CTRL)
|
||||
|
||||
def resume_steer_desired_rate_limit(self, lat_active: bool, apply_angle: float, steering_angle: float) -> float:
|
||||
"""Limits steering wheel acceleration when resuming steering"""
|
||||
if not lat_active:
|
||||
# reset and bypass
|
||||
self.resume_rate_limiter_delta.reset(0)
|
||||
self.resume_rate_limiter.reset(steering_angle)
|
||||
return steering_angle
|
||||
|
||||
angle_rate_delta_lim = self.resume_rate_limiter_delta.update(CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE,
|
||||
STEER_RESUME_RATE_LIMIT_RAMP_RATE * DT_LAT_CTRL**2)
|
||||
apply_angle_lim = self.resume_rate_limiter.update(apply_angle, angle_rate_delta_lim)
|
||||
return apply_angle_lim
|
||||
|
||||
def update(self, apply_angle, lat_active, CP_IQ: structs.IQCarParams, CS: structs.CarState, VM: VehicleModel) -> CoopSteeringDataIQ:
|
||||
# estimate real steering angle by adding rate to the tesla filtered angle
|
||||
steeringAngleDegPhaseLead = CS.out.steeringAngleDeg + CS.out.steeringRateDeg / STEERING_DEG_PHASE_LEAD_COEFF
|
||||
|
||||
angle_coop_enabled = CP_IQ.flags & TeslaFlagsIQ.COOP_STEERING.value
|
||||
|
||||
# avoid sudden rotation on engagement
|
||||
apply_angle = self.resume_steer_desired_rate_limit(lat_active, apply_angle, steeringAngleDegPhaseLead)
|
||||
|
||||
if angle_coop_enabled:
|
||||
# apply_angle = self.overriding_steer_desired_accel_limit(lat_active, apply_angle, CS.out.vEgo, CS.out.steeringTorque)
|
||||
self.debug_angle_desired_limited = apply_angle #! debug
|
||||
|
||||
apply_angle += self.apply_override_angle_combined(lat_active, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
|
||||
# final rate limit - matching panda safety
|
||||
self.coop_apply_angle_last = apply_angle
|
||||
self.coop_apply_angle_last_sat = apply_steer_angle_limits_vm(apply_angle, self.coop_apply_angle_last_sat, CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg, lat_active, CoopSteeringCarControllerParams, VM)
|
||||
|
||||
return CoopSteeringDataIQ(self.coop_apply_angle_last_sat, lat_active, 1) # 1 = angle control
|
||||
15
iqdbc_repo/iqdbc/lvbs/car/tesla/values.py
Normal file
15
iqdbc_repo/iqdbc/lvbs/car/tesla/values.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class TeslaFlagsIQ(IntFlag):
|
||||
HAS_VEHICLE_BUS = 1 # 3-finger infotainment press signal is present on the VEHICLE bus with the deprecated Tesla harness installed
|
||||
COOP_STEERING = 2 # virtual torque blending
|
||||
|
||||
|
||||
class TeslaSafetyFlagsIQ:
|
||||
HAS_VEHICLE_BUS = 1
|
||||
|
||||
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/tests/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
12
iqdbc_repo/iqdbc/lvbs/car/tests/test_car_list.py
Normal file
12
iqdbc_repo/iqdbc/lvbs/car/tests/test_car_list.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import json
|
||||
|
||||
from iqdbc.lvbs.car.platform_list import get_car_list, CAR_LIST_JSON_OUT
|
||||
|
||||
|
||||
class TestCarList:
|
||||
def test_generator(self):
|
||||
generated_car_list = json.dumps(get_car_list(), indent=2, ensure_ascii=False)
|
||||
with open(CAR_LIST_JSON_OUT) as f:
|
||||
current_car_list = f.read()
|
||||
|
||||
assert generated_car_list == current_car_list, "Run iqdbc/iqpilot/car/platform_list.py to update the car list"
|
||||
3
iqdbc_repo/iqdbc/lvbs/car/toyota/__init__.py
Normal file
3
iqdbc_repo/iqdbc/lvbs/car/toyota/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
167
iqdbc_repo/iqdbc/lvbs/car/toyota/carstate_ext.py
Normal file
167
iqdbc_repo/iqdbc/lvbs/car/toyota/carstate_ext.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from iqdbc.car import Bus, structs
|
||||
from iqdbc.car.carlog import carlog
|
||||
from iqdbc.can.parser import CANParser
|
||||
from iqdbc.car.common.conversions import Conversions as CV
|
||||
from iqdbc.lvbs.car.toyota.values import ToyotaFlagsIQ
|
||||
|
||||
TRAFFIC_SIGNAL_MAP = {
|
||||
1: "kph",
|
||||
36: "mph",
|
||||
65: "No overtake",
|
||||
66: "No overtake"
|
||||
}
|
||||
|
||||
ZSS_DIFF_THRESHOLD = 4
|
||||
ZSS_MAX_THRESHOLD = 10
|
||||
|
||||
|
||||
class CarStateExt:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.acc_type = 1
|
||||
self.zss_compute = False
|
||||
self.zss_cruise_active_last = False
|
||||
self.zss_angle_offset = 0.
|
||||
self.zss_threshold_count = 0
|
||||
|
||||
"""Initialize traffic signal variables"""
|
||||
self._tsgn1 = None
|
||||
self._spdval1 = None
|
||||
self._splsgn1 = None
|
||||
self._tsgn2 = None
|
||||
self._splsgn2 = None
|
||||
self._tsgn3 = None
|
||||
self._splsgn3 = None
|
||||
self._tsgn4 = None
|
||||
self._splsgn4 = None
|
||||
|
||||
@staticmethod
|
||||
def traffic_signal_description(tsgn):
|
||||
"""Get description for traffic signal code"""
|
||||
desc = TRAFFIC_SIGNAL_MAP.get(int(tsgn))
|
||||
return f'{tsgn}: {desc}' if desc is not None else f'{tsgn}'
|
||||
|
||||
def update_traffic_signals(self, cp_cam):
|
||||
"""Update traffic signals with error handling"""
|
||||
try:
|
||||
# Add error handling for missing RSA messages
|
||||
tsgn1 = cp_cam.vl.get("RSA1", {}).get('TSGN1', 0)
|
||||
spdval1 = cp_cam.vl.get("RSA1", {}).get('SPDVAL1', 0)
|
||||
splsgn1 = cp_cam.vl.get("RSA1", {}).get('SPLSGN1', 0)
|
||||
tsgn2 = cp_cam.vl.get("RSA1", {}).get('TSGN2', 0)
|
||||
splsgn2 = cp_cam.vl.get("RSA1", {}).get('SPLSGN2', 0)
|
||||
tsgn3 = cp_cam.vl.get("RSA2", {}).get('TSGN3', 0)
|
||||
splsgn3 = cp_cam.vl.get("RSA2", {}).get('SPLSGN3', 0)
|
||||
tsgn4 = cp_cam.vl.get("RSA2", {}).get('TSGN4', 0)
|
||||
splsgn4 = cp_cam.vl.get("RSA2", {}).get('SPLSGN4', 0)
|
||||
except (KeyError, AttributeError) as e:
|
||||
# Handle case where RSA messages are not available
|
||||
carlog.debug(f"RSA messages not available: {e}")
|
||||
return
|
||||
|
||||
has_changed = tsgn1 != self._tsgn1 \
|
||||
or spdval1 != self._spdval1 \
|
||||
or splsgn1 != self._splsgn1 \
|
||||
or tsgn2 != self._tsgn2 \
|
||||
or splsgn2 != self._splsgn2 \
|
||||
or tsgn3 != self._tsgn3 \
|
||||
or splsgn3 != self._splsgn3 \
|
||||
or tsgn4 != self._tsgn4 \
|
||||
or splsgn4 != self._splsgn4
|
||||
|
||||
self._tsgn1 = tsgn1
|
||||
self._spdval1 = spdval1
|
||||
self._splsgn1 = splsgn1
|
||||
self._tsgn2 = tsgn2
|
||||
self._splsgn2 = splsgn2
|
||||
self._tsgn3 = tsgn3
|
||||
self._splsgn3 = splsgn3
|
||||
self._tsgn4 = tsgn4
|
||||
self._splsgn4 = splsgn4
|
||||
|
||||
if not has_changed:
|
||||
return
|
||||
|
||||
carlog.debug('---- TRAFFIC SIGNAL UPDATE -----')
|
||||
if tsgn1 is not None and tsgn1 != 0:
|
||||
carlog.debug(f'TSGN1: {self.traffic_signal_description(tsgn1)}')
|
||||
if spdval1 is not None and spdval1 != 0:
|
||||
carlog.debug(f'SPDVAL1: {spdval1}')
|
||||
if splsgn1 is not None and splsgn1 != 0:
|
||||
carlog.debug(f'SPLSGN1: {splsgn1}')
|
||||
if tsgn2 is not None and tsgn2 != 0:
|
||||
carlog.debug(f'TSGN2: {self.traffic_signal_description(tsgn2)}')
|
||||
if splsgn2 is not None and splsgn2 != 0:
|
||||
carlog.debug(f'SPLSGN2: {splsgn2}')
|
||||
if tsgn3 is not None and tsgn3 != 0:
|
||||
carlog.debug(f'TSGN3: {self.traffic_signal_description(tsgn3)}')
|
||||
if splsgn3 is not None and splsgn3 != 0:
|
||||
carlog.debug(f'SPLSGN3: {splsgn3}')
|
||||
if tsgn4 is not None and tsgn4 != 0:
|
||||
carlog.debug(f'TSGN4: {self.traffic_signal_description(tsgn4)}')
|
||||
if splsgn4 is not None and splsgn4 != 0:
|
||||
carlog.debug(f'SPLSGN4: {splsgn4}')
|
||||
carlog.debug('------------------------')
|
||||
|
||||
def calculate_speed_limit(self):
|
||||
"""Calculate speed limit from traffic signals with validation"""
|
||||
# Check all traffic sign slots for speed limits, not just tsgn1
|
||||
for tsgn, spdval in [(self._tsgn1, self._spdval1),
|
||||
(self._tsgn2, None),
|
||||
(self._tsgn3, None),
|
||||
(self._tsgn4, None)]:
|
||||
|
||||
if tsgn == 1 and spdval is not None and 0 < spdval <= 200: # Reasonable speed range
|
||||
return spdval * CV.KPH_TO_MS
|
||||
|
||||
if tsgn == 36 and spdval is not None and 0 < spdval <= 120: # Reasonable MPH range
|
||||
return spdval * CV.MPH_TO_MS
|
||||
|
||||
return 0
|
||||
|
||||
def update(self, ret: structs.CarState, ret_iq: structs.IQCarState, can_parsers: dict[StrEnum, CANParser]) -> None:
|
||||
cp = can_parsers[Bus.pt]
|
||||
cp_cam = can_parsers[Bus.cam]
|
||||
|
||||
if self.CP_IQ.flags & ToyotaFlagsIQ.SMART_DSU or self.CP_IQ.flags & ToyotaFlagsIQ.STOP_AND_GO_HACK:
|
||||
self.acc_type = 1
|
||||
|
||||
if self.CP_IQ.enableGasInterceptor:
|
||||
gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) // 2
|
||||
ret.gasPressed = gas > 805
|
||||
|
||||
# ZSS support thanks to zorrobyte, ErichMoraga, and dragonpilot
|
||||
if self.CP_IQ.flags & ToyotaFlagsIQ.ZSS:
|
||||
zorro_steer = cp.vl["SECONDARY_STEER_ANGLE"]["ZORRO_STEER"]
|
||||
control_available = ret.cruiseState.available
|
||||
|
||||
# Only compute ZSS offset when control is available
|
||||
if control_available and not self.zss_cruise_active_last:
|
||||
self.zss_threshold_count = 0
|
||||
self.zss_compute = True # Control was just activated, so allow offset to be recomputed
|
||||
self.zss_cruise_active_last = control_available
|
||||
|
||||
# Compute ZSS offset once we have meaningful angles
|
||||
if self.zss_compute and abs(ret.steeringAngleDeg) > 1e-3 and abs(zorro_steer) > 1e-3:
|
||||
self.zss_compute = False
|
||||
self.zss_angle_offset = zorro_steer - ret.steeringAngleDeg
|
||||
|
||||
# Sanity checks
|
||||
steering_angle_deg = zorro_steer - self.zss_angle_offset
|
||||
if self.zss_threshold_count <= ZSS_MAX_THRESHOLD:
|
||||
if abs(ret.steeringAngleDeg - steering_angle_deg) > ZSS_DIFF_THRESHOLD:
|
||||
self.zss_threshold_count += 1
|
||||
else:
|
||||
ret.steeringAngleDeg = steering_angle_deg
|
||||
|
||||
# Update traffic signals and speed limit
|
||||
self.update_traffic_signals(cp_cam)
|
||||
ret_iq.speedLimit = self.calculate_speed_limit()
|
||||
6
iqdbc_repo/iqdbc/lvbs/car/toyota/fingerprints_ext.py
Normal file
6
iqdbc_repo/iqdbc/lvbs/car/toyota/fingerprints_ext.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from iqdbc.car.structs import CarParams
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
FW_VERSIONS_EXT = {
|
||||
}
|
||||
47
iqdbc_repo/iqdbc/lvbs/car/toyota/gas_interceptor.py
Normal file
47
iqdbc_repo/iqdbc/lvbs/car/toyota/gas_interceptor.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from iqdbc.car import structs
|
||||
from iqdbc.car.can_definitions import CanData
|
||||
from iqdbc.car.toyota.values import CAR, MIN_ACC_SPEED, PEDAL_TRANSITION
|
||||
from iqdbc.lvbs.car import create_gas_interceptor_command
|
||||
|
||||
|
||||
class GasInterceptorCarController:
|
||||
def __init__(self, CP, CP_IQ):
|
||||
self.CP = CP
|
||||
self.CP_IQ = CP_IQ
|
||||
|
||||
self.gas = 0.
|
||||
self.interceptor_gas_cmd = 0.
|
||||
|
||||
def create_gas_command(self, CC: structs.CarControl, CS: structs.CarState, actuators: structs.CarControl.Actuators,
|
||||
packer, frame: int) -> list[CanData]:
|
||||
can_sends = []
|
||||
|
||||
if self.CP_IQ.enableGasInterceptor and CC.longActive:
|
||||
MAX_INTERCEPTOR_GAS = 0.3
|
||||
# RAV4 has very sensitive gas pedal
|
||||
if self.CP.carFingerprint in (CAR.TOYOTA_RAV4, CAR.TOYOTA_RAV4H, CAR.TOYOTA_HIGHLANDER):
|
||||
PEDAL_SCALE = np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])
|
||||
elif self.CP.carFingerprint in (CAR.TOYOTA_COROLLA,):
|
||||
PEDAL_SCALE = np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])
|
||||
else:
|
||||
PEDAL_SCALE = np.interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0])
|
||||
# offset for creep and windbrake
|
||||
pedal_offset = np.interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2])
|
||||
pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset)
|
||||
self.interceptor_gas_cmd = float(np.clip(pedal_command, 0., MAX_INTERCEPTOR_GAS))
|
||||
else:
|
||||
self.interceptor_gas_cmd = 0.
|
||||
|
||||
if frame % 2 == 0 and self.CP_IQ.enableGasInterceptor and self.CP.openpilotLongitudinalControl:
|
||||
# send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd.
|
||||
# This prevents unexpected pedal range rescaling
|
||||
can_sends.append(create_gas_interceptor_command(packer, self.interceptor_gas_cmd, frame // 2))
|
||||
self.gas = self.interceptor_gas_cmd
|
||||
|
||||
return can_sends
|
||||
21
iqdbc_repo/iqdbc/lvbs/car/toyota/values.py
Normal file
21
iqdbc_repo/iqdbc/lvbs/car/toyota/values.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
class ToyotaFlagsIQ(IntFlag):
|
||||
SMART_DSU = 1
|
||||
RADAR_CAN_FILTER = 2
|
||||
ZSS = 4
|
||||
STOCK_LONGITUDINAL = 8
|
||||
STOP_AND_GO_HACK = 16
|
||||
|
||||
|
||||
class ToyotaSafetyFlagsIQ:
|
||||
DEFAULT = 0
|
||||
UNSUPPORTED_DSU = 1
|
||||
GAS_INTERCEPTOR = 2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user