IQ.Pilot Release Commit @ 9fe0487

This commit is contained in:
IQ.Lvbs CI [bot]
2026-07-27 01:04:28 -05:00
parent 6edfe55a1e
commit 541e76a873
210 changed files with 992 additions and 504 deletions

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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)

View 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

View 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"]

View 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',
],
},
}

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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"]

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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
}],
}

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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

View 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',
],
},
}

View 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

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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

View 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}")

View 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

View 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'
],
},
}

View 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)

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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,
)
}

View 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

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View 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

View 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

View 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)

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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]),
]

View 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}")

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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)

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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})

View 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"])

View 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)

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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

View 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

View 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

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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"

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View 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()

View File

@@ -0,0 +1,6 @@
from iqdbc.car.structs import CarParams
Ecu = CarParams.Ecu
FW_VERSIONS_EXT = {
}

View 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

View 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