1
0
forked from IQ.Lvbs/IQ.Pilot

IQ.Pilot Prebuilt Release @ ab07000

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:42 -05:00
commit 9f9c9a70cc
3729 changed files with 778697 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from bisect import insort
from collections.abc import Callable, Iterable
from dataclasses import dataclass, field
from enum import IntEnum
import cereal.messaging as messaging
from cereal import car, log
from openpilot.common.realtime import DT_CTRL
from openpilot.system.hardware import HARDWARE
AlertSize = log.SelfdriveState.AlertSize
AlertStatus = log.SelfdriveState.AlertStatus
VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
def _frames_for(seconds: float) -> int:
return int(seconds / DT_CTRL)
class Tier(IntEnum):
LOWEST = 0
LOWER = 1
LOW = 2
MID = 3
HIGH = 4
HIGHEST = 5
class Tags:
ENABLE = "enable"
PRE_ENABLE = "preEnable"
OVERRIDE_LATERAL = "overrideLateral"
OVERRIDE_LONGITUDINAL = "overrideLongitudinal"
NO_ENTRY = "noEntry"
WARNING = "warning"
USER_DISABLE = "userDisable"
SOFT_DISABLE = "softDisable"
IMMEDIATE_DISABLE = "immediateDisable"
PERMANENT = "permanent"
@dataclass(slots=True)
class AlertCard:
alert_text_1: str
alert_text_2: str
alert_status: log.SelfdriveState.AlertStatus
alert_size: log.SelfdriveState.AlertSize
priority: Tier
visual_alert: car.CarControl.HUDControl.VisualAlert
audible_alert: car.CarControl.HUDControl.AudibleAlert
duration: int
creation_delay: float = 0.0
alert_type: str = field(default="", init=False)
event_type: str | None = field(default=None, init=False)
def __init__(self,
alert_text_1: str,
alert_text_2: str,
alert_status: log.SelfdriveState.AlertStatus,
alert_size: log.SelfdriveState.AlertSize,
priority: Tier,
visual_alert: car.CarControl.HUDControl.VisualAlert,
audible_alert: car.CarControl.HUDControl.AudibleAlert,
duration: float,
creation_delay: float = 0.0):
self.alert_text_1 = alert_text_1
self.alert_text_2 = alert_text_2
self.alert_status = alert_status
self.alert_size = alert_size
self.priority = priority
self.visual_alert = visual_alert
self.audible_alert = audible_alert
self.duration = _frames_for(duration)
self.creation_delay = creation_delay
self.alert_type = ""
self.event_type = None
def __str__(self) -> str:
return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}"
AlertFactory = Callable[[car.CarParams, car.CarState, messaging.SubMaster, bool, int, log.ControlsState], AlertCard]
def car_mode_entry_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> AlertCard:
del CS, sm, metric, soft_disable_time, personality
headline = "Enable Adaptive Cruise to Engage"
if CP.brand == "honda":
headline = "Enable Main Switch to Engage"
return NoEntryCard(headline)
class EventBook(ABC):
def __init__(self):
self._live_names: list[int] = []
self._latched_names: list[int] = []
self.event_counters: dict[int, int] = {}
@property
def events(self) -> list[int]:
return self._live_names
@events.setter
def events(self, values: list[int]) -> None:
self._live_names = values
@property
def static_events(self) -> list[int]:
return self._latched_names
@static_events.setter
def static_events(self, values: list[int]) -> None:
self._latched_names = values
@property
def names(self) -> list[int]:
return list(self._live_names)
def __len__(self) -> int:
return len(self._live_names)
def add(self, event_name: int, static: bool = False) -> None:
if static:
insort(self._latched_names, event_name)
insort(self._live_names, event_name)
def clear(self) -> None:
refreshed: dict[int, int] = {}
for event_name, frames_seen in self.event_counters.items():
refreshed[event_name] = frames_seen + 1 if event_name in self._live_names else 0
self.event_counters = refreshed
self._live_names = list(self._latched_names)
def contains(self, event_type: str) -> bool:
board = self.get_events_mapping()
return any(event_type in board.get(event_name, {}) for event_name in self._live_names)
def has(self, event_name: int) -> bool:
return event_name in self._live_names
def contains_in_list(self, events_list: list[int]) -> bool:
return any(event_name in self._live_names for event_name in events_list)
def remove(self, event_name: int, static: bool = False) -> None:
if static and event_name in self._latched_names:
self._latched_names.remove(event_name)
if event_name in self._live_names:
self.event_counters[event_name] = self.event_counters.get(event_name, 0) + 1
self._live_names.remove(event_name)
def add_from_msg(self, events: Iterable) -> None:
for event in events:
insort(self._live_names, event.name.raw)
def to_msg(self):
board = self.get_events_mapping()
outbound = []
for event_name in self._live_names:
msg = self.get_event_msg_type().new_message()
msg.name = event_name
for event_kind in board.get(event_name, {}):
setattr(msg, event_kind, True)
outbound.append(msg)
return outbound
def create_alerts(self, event_types: list[str], callback_args=None):
callback_args = [] if callback_args is None else callback_args
board = self.get_events_mapping()
spawned: list[AlertCard] = []
for event_name in self._live_names:
variants = board.get(event_name, {})
for event_type in event_types:
chosen = variants.get(event_type)
if chosen is None:
continue
alert = self._realize(chosen, callback_args)
age_frames = self.event_counters.get(event_name, 0) + 1
if age_frames * DT_CTRL < alert.creation_delay:
continue
alert.alert_type = f"{self.get_event_name(event_name)}/{event_type}"
alert.event_type = event_type
spawned.append(alert)
return spawned
@staticmethod
def _realize(candidate: AlertCard | AlertFactory, callback_args: list) -> AlertCard:
return candidate if isinstance(candidate, AlertCard) else candidate(*callback_args)
@abstractmethod
def get_events_mapping(self) -> dict[int, dict[str, AlertCard | AlertFactory]]:
raise NotImplementedError
@abstractmethod
def get_event_name(self, event: int) -> str:
raise NotImplementedError
@abstractmethod
def get_event_msg_type(self):
raise NotImplementedError
def _mici_reframe(primary: str, secondary: str) -> tuple[str, str, log.SelfdriveState.AlertSize]:
if HARDWARE.get_device_type() == "mici":
return secondary, primary, AlertSize.small
return primary, secondary, AlertSize.mid
class NoEntryCard(AlertCard):
def __init__(self,
alert_text_2: str,
alert_text_1: str = "openpilot Unavailable",
visual_alert: car.CarControl.HUDControl.VisualAlert = VisualAlert.none,
priority: Tier = Tier.LOW):
primary, secondary, size = _mici_reframe(alert_text_1, alert_text_2)
super().__init__(primary, secondary, AlertStatus.normal, size, priority, visual_alert, AudibleAlert.refuse, 3.0)
class GentleDisableCard(AlertCard):
def __init__(self, alert_text_2: str):
super().__init__(
"TAKE CONTROL IMMEDIATELY",
alert_text_2,
AlertStatus.userPrompt,
AlertSize.full,
Tier.MID,
VisualAlert.steerRequired,
AudibleAlert.warningSoft,
2.0,
)
class PendingDisableCard(GentleDisableCard):
def __init__(self, alert_text_2: str):
super().__init__(alert_text_2)
self.alert_text_1 = "openpilot will disengage"
class HardDisableCard(AlertCard):
def __init__(self, alert_text_2: str):
super().__init__(
"TAKE CONTROL IMMEDIATELY",
alert_text_2,
AlertStatus.critical,
AlertSize.full,
Tier.HIGHEST,
VisualAlert.steerRequired,
AudibleAlert.warningImmediate,
4.0,
)
class ChimeCard(AlertCard):
def __init__(self, audible_alert: car.CarControl.HUDControl.AudibleAlert):
super().__init__("", "", AlertStatus.normal, AlertSize.none, Tier.MID, VisualAlert.none, audible_alert, 0.2)
class BannerCard(AlertCard):
def __init__(self, alert_text_1: str, alert_text_2: str = "", duration: float = 0.2, priority: Tier = Tier.LOWER, creation_delay: float = 0.0):
size = AlertSize.mid if alert_text_2 else AlertSize.small
super().__init__(alert_text_1, alert_text_2, AlertStatus.normal, size, priority, VisualAlert.none, AudibleAlert.none, duration, creation_delay)
class BootCard(AlertCard):
def __init__(self, alert_text_1: str, alert_text_2: str = "Always keep hands on wheel and eyes on road", alert_status=AlertStatus.normal):
if HARDWARE.get_device_type() == "mici":
compact_secondary = "" if alert_text_2 == "Always keep hands on wheel and eyes on road" else alert_text_2
super().__init__(alert_text_1, compact_secondary, alert_status, AlertSize.small, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
else:
super().__init__(alert_text_1, alert_text_2, alert_status, AlertSize.mid, Tier.LOWER, VisualAlert.none, AudibleAlert.none, 5.0)
class AlertBase(AlertCard):
pass
NULL_ALERT = AlertCard("", "", AlertStatus.normal, AlertSize.none, Tier.LOWEST, VisualAlert.none, AudibleAlert.none, 0.0)

View File

@@ -0,0 +1,58 @@
import time
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot.common.geo_regions import UNKNOWN_REGION, region_for_position, region_is_metric
CHECK_INTERVAL = 10.0
CONFIRMATIONS = 3
class AutoUnits:
def __init__(self, params: Params | None = None):
self.params = params or Params()
self._next_check = 0.0
self._candidate = UNKNOWN_REGION
self._confirmations = 0
def _position(self) -> tuple[float, float, bool]:
from openpilot.selfdrive.ui.lib.nav_helpers import current_or_last_gps_position
lat, lon, _, valid = current_or_last_gps_position(self.params)
return lat, lon, valid
def update(self, now: float | None = None) -> None:
if not self.params.get_bool("IQAutoUnits"):
self._candidate = UNKNOWN_REGION
self._confirmations = 0
return
now = time.monotonic() if now is None else now
if now < self._next_check:
return
self._next_check = now + CHECK_INTERVAL
lat, lon, valid = self._position()
region = region_for_position(lat, lon) if valid else UNKNOWN_REGION
if region == UNKNOWN_REGION:
self._confirmations = 0
return
if region != self._candidate:
self._candidate = region
self._confirmations = 1
return
self._confirmations += 1
if self._confirmations < CONFIRMATIONS:
return
if region == self.params.get("IQAutoUnitsRegion"):
return
self.params.put("IQAutoUnitsRegion", region)
metric = region_is_metric(region)
if metric != self.params.get_bool("IsMetric"):
self.params.put_bool("IsMetric", metric)
cloudlog.warning(f"auto units: {region} detected, switching to {'km/h' if metric else 'mph'}")

View File

@@ -0,0 +1,140 @@
MPH_REGIONS = ("US", "GB", "LR")
METRIC_REGION = "METRIC"
UNKNOWN_REGION = ""
_US_CONUS = [
(-123.32, 49.00), (-117.03, 49.00), (-110.00, 49.00), (-104.05, 49.00), (-97.23, 49.00), (-95.15, 49.00),
(-95.15, 49.38), (-94.82, 49.30), (-94.68, 48.77), (-93.85, 48.63), (-93.35, 48.62), (-92.72, 48.54),
(-92.30, 48.24), (-91.55, 48.10), (-90.84, 48.24), (-89.99, 48.02), (-89.60, 48.02), (-89.10, 48.32),
(-88.40, 48.30), (-87.00, 47.80), (-85.60, 47.15), (-84.60, 46.75), (-84.42, 46.56), (-84.30, 46.49),
(-84.12, 46.28), (-83.90, 46.05), (-83.40, 45.75), (-82.90, 45.05), (-82.55, 44.00), (-82.42, 43.00),
(-82.70, 42.47), (-82.93, 42.34), (-83.00, 42.33), (-83.05, 42.32), (-83.075, 42.312), (-83.13, 42.25),
(-83.15, 42.18), (-83.11, 42.10), (-83.09, 42.02),
(-82.50, 41.70), (-81.50, 42.00), (-80.20, 42.40), (-79.06, 42.85), (-79.05, 43.27), (-78.00, 43.45),
(-77.00, 43.65), (-76.40, 44.10), (-75.80, 44.50), (-74.75, 45.00), (-73.35, 45.01), (-71.50, 45.01),
(-71.29, 45.30), (-70.90, 45.30), (-70.72, 45.42), (-70.31, 45.86), (-70.05, 46.44), (-69.99, 46.70),
(-69.24, 47.46), (-68.90, 47.20), (-68.38, 47.29), (-67.79, 47.07), (-67.78, 45.94), (-67.42, 45.60),
(-67.03, 44.80), (-68.00, 44.30), (-69.06, 43.80), (-70.20, 43.60), (-70.80, 42.85), (-70.00, 41.90),
(-70.00, 41.55), (-71.20, 41.30), (-72.00, 41.05), (-73.90, 40.55), (-74.20, 39.60), (-75.05, 38.45),
(-75.90, 37.05), (-75.50, 35.20), (-78.50, 33.85), (-80.90, 32.00), (-81.40, 30.70), (-80.03, 26.80),
(-80.15, 25.15), (-81.20, 24.55), (-82.00, 26.40), (-82.80, 27.80), (-83.00, 29.15), (-84.30, 29.90),
(-85.30, 29.65), (-87.50, 30.25), (-89.00, 29.15), (-89.40, 28.95), (-91.30, 29.10), (-93.80, 29.65),
(-95.00, 29.10), (-97.10, 27.80), (-97.14, 25.96), (-98.30, 26.05), (-99.10, 26.40), (-99.50, 27.60),
(-100.40, 28.50), (-101.40, 29.77), (-102.30, 29.88), (-102.90, 29.30), (-103.30, 29.00), (-104.37, 29.56),
(-104.68, 30.13), (-105.30, 30.80), (-105.85, 31.30), (-106.15, 31.50), (-106.30, 31.68), (-106.45, 31.755),
(-106.53, 31.786), (-108.21, 31.783), (-108.21, 31.33), (-111.07, 31.33), (-114.72, 32.72),
(-117.13, 32.53), (-118.40, 33.75), (-119.80, 34.40), (-120.65, 35.10), (-121.90, 36.60), (-122.52, 37.78),
(-123.75, 39.40), (-124.20, 40.45), (-124.15, 42.00), (-124.05, 43.35), (-123.95, 46.25), (-124.75, 48.40),
(-123.30, 48.25), (-123.15, 48.70),
]
_US_ALASKA = [
(-141.00, 70.20), (-141.00, 60.30), (-139.05, 60.35), (-137.45, 58.95), (-136.47, 59.63), (-135.03, 59.57),
(-134.30, 58.90), (-133.40, 58.20), (-132.20, 56.90), (-130.60, 56.20), (-130.01, 54.80), (-131.80, 54.70),
(-133.80, 55.90), (-136.60, 58.20), (-140.00, 59.70), (-145.00, 60.00), (-149.20, 59.10), (-152.30, 57.30),
(-155.20, 55.60), (-160.00, 54.60), (-164.50, 54.40), (-162.00, 57.50), (-165.00, 60.20), (-167.50, 62.50),
(-164.00, 64.50), (-168.10, 65.60), (-166.00, 68.30), (-161.00, 70.30), (-156.50, 71.40), (-150.00, 70.50),
]
_US_ALEUTIANS_EAST = [(-180.00, 51.00), (-158.50, 51.00), (-158.50, 56.00), (-180.00, 56.00)]
_US_ALEUTIANS_WEST = [(172.00, 51.00), (180.00, 51.00), (180.00, 54.00), (172.00, 54.00)]
_US_HAWAII = [(-160.50, 18.80), (-154.70, 18.80), (-154.70, 22.30), (-160.50, 22.30)]
_US_PUERTO_RICO = [(-67.35, 17.85), (-64.55, 17.85), (-64.55, 18.55), (-67.35, 18.55)]
_US_MARIANAS = [(144.50, 13.10), (146.20, 13.10), (146.20, 20.60), (144.50, 20.60)]
_US_SAMOA = [(-171.20, -14.60), (-168.10, -14.60), (-168.10, -11.00), (-171.20, -11.00)]
_GB_BRITAIN = [
(-5.72, 50.07), (-4.20, 50.32), (-3.41, 50.62), (-2.45, 50.52), (-1.80, 50.72), (-0.90, 50.77),
(0.58, 50.85), (1.35, 51.13), (1.38, 51.38), (1.15, 51.79), (1.35, 51.95), (1.75, 52.48),
(1.30, 52.94), (0.49, 52.94), (0.34, 53.15), (-0.08, 53.57), (-0.08, 54.12), (-0.61, 54.49),
(-1.18, 54.69), (-1.38, 54.91), (-1.50, 55.13), (-2.00, 55.77), (-2.52, 56.00), (-2.62, 56.28),
(-2.47, 56.55), (-2.21, 56.96), (-2.08, 57.14), (-1.77, 57.50), (-2.00, 57.70), (-2.96, 57.68),
(-3.90, 57.60), (-4.22, 57.48), (-4.05, 57.81), (-3.85, 58.01), (-3.65, 58.12), (-3.09, 58.44),
(-3.01, 58.67), (-3.35, 58.62), (-3.52, 58.60), (-4.99, 58.62), (-5.05, 58.45), (-5.16, 57.90), (-5.70, 57.72),
(-5.72, 57.28), (-5.83, 57.00), (-5.72, 56.65), (-5.47, 56.41), (-5.79, 55.60), (-5.62, 55.31),
(-4.82, 55.64), (-4.63, 55.46), (-4.85, 55.24), (-5.12, 54.84), (-4.86, 54.63), (-4.44, 54.87),
(-4.05, 54.83), (-3.26, 54.98), (-3.05, 54.90), (-3.50, 54.72), (-3.23, 54.07), (-3.05, 53.82),
(-3.40, 53.34), (-3.83, 53.33), (-4.63, 53.42), (-4.72, 53.28), (-4.35, 53.12), (-4.76, 52.80),
(-4.06, 52.72), (-4.09, 52.41), (-4.66, 52.09), (-5.31, 51.88), (-5.06, 51.70), (-4.70, 51.67),
(-4.30, 51.62), (-3.95, 51.56), (-3.70, 51.48), (-3.17, 51.45), (-2.99, 51.55), (-2.67, 51.62),
(-2.48, 51.72), (-2.30, 51.85), (-2.70, 51.50), (-2.98, 51.35), (-3.00, 51.20), (-3.47, 51.21),
(-4.12, 51.21), (-4.55, 50.83), (-5.08, 50.42), (-5.48, 50.21),
]
_GB_NORTHERN_IRELAND = [
(-6.03, 54.05), (-6.28, 54.10), (-6.65, 54.17), (-6.86, 54.33), (-7.16, 54.34), (-7.31, 54.12),
(-7.62, 54.14), (-8.00, 54.31), (-8.18, 54.47), (-8.20, 54.52), (-7.90, 54.55), (-7.85, 54.72), (-7.55, 54.75),
(-7.44, 54.94), (-7.25, 55.06), (-6.95, 55.22), (-6.50, 55.25), (-6.25, 55.31), (-6.03, 55.22),
(-5.43, 54.62), (-5.53, 54.24),
]
_GB_ISLE_OF_MAN = [(-4.85, 54.03), (-4.30, 54.03), (-4.30, 54.42), (-4.85, 54.42)]
_GB_CHANNEL_ISLANDS = [(-2.75, 49.15), (-1.95, 49.15), (-1.95, 49.80), (-2.75, 49.80)]
_GB_ISLE_OF_WIGHT = [(-1.60, 50.55), (-1.05, 50.55), (-1.05, 50.80), (-1.60, 50.80)]
_GB_OUTER_HEBRIDES = [(-7.75, 56.75), (-6.05, 56.75), (-6.05, 58.55), (-7.75, 58.55)]
_GB_INNER_HEBRIDES = [(-7.00, 55.45), (-5.55, 55.45), (-5.55, 57.85), (-7.00, 57.85)]
_GB_ORKNEY = [(-3.50, 58.70), (-2.35, 58.70), (-2.35, 59.45), (-3.50, 59.45)]
_GB_SHETLAND = [(-1.85, 59.80), (-0.65, 59.80), (-0.65, 60.90), (-1.85, 60.90)]
_LR_LIBERIA = [
(-11.46, 6.77), (-11.30, 6.95), (-11.16, 7.15), (-11.05, 7.40), (-10.85, 7.75), (-10.60, 8.00),
(-10.28, 8.49), (-9.70, 8.54), (-9.35, 7.80),
(-8.85, 7.40), (-8.48, 7.55), (-8.30, 6.90), (-7.95, 6.20), (-7.60, 5.20), (-7.40, 4.55),
(-7.74, 4.33), (-8.46, 4.61), (-9.06, 4.97), (-9.52, 5.36), (-10.08, 5.85), (-10.40, 6.11),
(-10.83, 6.27),
]
_REGION_RINGS = {
"US": (_US_CONUS, _US_ALASKA, _US_ALEUTIANS_EAST, _US_ALEUTIANS_WEST, _US_HAWAII, _US_PUERTO_RICO,
_US_MARIANAS, _US_SAMOA),
"GB": (_GB_BRITAIN, _GB_NORTHERN_IRELAND, _GB_ISLE_OF_MAN, _GB_CHANNEL_ISLANDS, _GB_ISLE_OF_WIGHT,
_GB_OUTER_HEBRIDES, _GB_INNER_HEBRIDES, _GB_ORKNEY, _GB_SHETLAND),
"LR": (_LR_LIBERIA,),
}
def _bounded(rings):
out = []
for ring in rings:
lons = [p[0] for p in ring]
lats = [p[1] for p in ring]
out.append(((min(lons), min(lats), max(lons), max(lats)), ring))
return tuple(out)
_REGIONS = tuple((region, _bounded(rings)) for region, rings in _REGION_RINGS.items())
def _point_in_ring(lat: float, lon: float, ring) -> bool:
inside = False
count = len(ring)
j = count - 1
for i in range(count):
lon_i, lat_i = ring[i]
lon_j, lat_j = ring[j]
if (lat_i > lat) != (lat_j > lat):
crossing = (lon_j - lon_i) * (lat - lat_i) / (lat_j - lat_i) + lon_i
if lon < crossing:
inside = not inside
j = i
return inside
def valid_position(lat: float, lon: float) -> bool:
return abs(lat) <= 90.0 and abs(lon) <= 180.0 and (abs(lat) > 1e-4 or abs(lon) > 1e-4)
def region_for_position(lat: float, lon: float) -> str:
if not valid_position(lat, lon):
return UNKNOWN_REGION
for region, rings in _REGIONS:
for (min_lon, min_lat, max_lon, max_lat), ring in rings:
if min_lon <= lon <= max_lon and min_lat <= lat <= max_lat and _point_in_ring(lat, lon, ring):
return region
return METRIC_REGION
def region_is_metric(region: str) -> bool:
return bool(region) and region not in MPH_REGIONS

View File

@@ -0,0 +1,15 @@
from datetime import datetime
from openpilot.common.swaglog import cloudlog
K3_SLC_LOG_FILE = "/data/openpilot/k3_slc.txt"
def k3_slc_log(message: str) -> None:
try:
with open(K3_SLC_LOG_FILE, "a") as f:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
f.write(f"[{timestamp}] {message}\n")
f.flush()
except Exception as e:
cloudlog.error(f"[K3_SLC] Failed to write debug log: {e}")

View File

@@ -0,0 +1,126 @@
import math
import numpy as np
try:
import requests
except ImportError:
requests = None
from openpilot.iqpilot.common.slc_variables import EARTH_RADIUS
def calculate_bearing_offset(latitude, longitude, current_bearing, distance):
"""
Calculate new GPS coordinates given a starting point, bearing, and distance.
Used for Mapbox API lookahead calculations.
Args:
latitude: Starting latitude in degrees
longitude: Starting longitude in degrees
current_bearing: Bearing in degrees (0-360)
distance: Distance to project in meters
Returns:
Tuple of (new_latitude, new_longitude) in degrees
"""
bearing = math.radians(current_bearing)
lat_rad = math.radians(latitude)
lon_rad = math.radians(longitude)
delta = distance / EARTH_RADIUS
new_lat = math.asin(math.sin(lat_rad) * math.cos(delta) + math.cos(lat_rad) * math.sin(delta) * math.cos(bearing))
new_lon = lon_rad + math.atan2(math.sin(bearing) * math.sin(delta) * math.cos(lat_rad), math.cos(delta) - math.sin(lat_rad) * math.sin(new_lat))
return math.degrees(new_lat), math.degrees(new_lon)
def calculate_distance_to_point(lat1, lon1, lat2, lon2):
"""
Calculate the great circle distance between two GPS points using the Haversine formula.
Args:
lat1, lon1: First point coordinates in degrees
lat2, lon2: Second point coordinates in degrees
Returns:
Distance in meters
"""
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
delta_lat = lat2_rad - lat1_rad
delta_lon = lon2_rad - lon1_rad
a = (math.sin(delta_lat / 2) ** 2) + math.cos(lat1_rad) * math.cos(lat2_rad) * (math.sin(delta_lon / 2) ** 2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return EARTH_RADIUS * c
def calculate_lane_width(lane_line1, lane_line2, road_edge=None):
"""
Calculate the width of a lane based on lane line positions.
Used for speed limit filler to determine road width.
Args:
lane_line1: First lane line object with x, y coordinates
lane_line2: Second lane line object with x, y coordinates
road_edge: Optional road edge object with x, y coordinates
Returns:
Lane width in meters
"""
lane_line1_x = np.asarray(lane_line1.x)
lane_line1_y = np.asarray(lane_line1.y)
lane_line2_x = np.asarray(lane_line2.x)
lane_line2_y = np.asarray(lane_line2.y)
lane_y_interp = np.interp(lane_line2_x, lane_line1_x, lane_line1_y)
distance_to_lane = np.median(np.abs(lane_line2_y - lane_y_interp))
if road_edge is None:
return distance_to_lane
road_edge_x = np.asarray(road_edge.x)
road_edge_y = np.asarray(road_edge.y)
edge_y_interp = np.interp(lane_line2_x, road_edge_x, road_edge_y)
distance_to_edge = np.median(np.abs(lane_line2_y - edge_y_interp))
return max(distance_to_lane, distance_to_edge)
def is_url_pingable(url):
"""
Check if a URL is accessible and responding.
Used to verify Mapbox/Overpass API availability before making requests.
Args:
url: URL to ping
Returns:
Boolean indicating if URL is accessible
"""
if not url:
return False
if requests is None:
return False
if not hasattr(is_url_pingable, "session"):
is_url_pingable.session = requests.Session()
is_url_pingable.session.headers.update({"User-Agent": "iqpilot-ping-test/1.0"})
try:
response = is_url_pingable.session.head(url, timeout=10, allow_redirects=True)
if response.status_code in (405, 501):
response = is_url_pingable.session.get(url, timeout=10, allow_redirects=True, stream=True)
is_accessible = response.ok
response.close()
return is_accessible
except Exception:
return False

View File

@@ -0,0 +1,35 @@
# Earth radius in meters (for GPS calculations)
EARTH_RADIUS = 6378137
# Mapbox API limits
FREE_MAPBOX_REQUESTS = 100_000
# Speed limit offset zones for different unit systems
# Each entry is (min_speed_ms, max_speed_ms, param_name); the param value is a
# percent offset applied to the resolved limit (e.g. 10 -> +10%), lower bound inclusive
OFFSET_PERCENT_MAX = 50.0
OFFSET_MAP_IMPERIAL = [
(0, 8.94, "speed_limit_offset1"), # 0-20 mph
(8.94, 17.88, "speed_limit_offset2"), # 20-40 mph
(17.88, float("inf"), "speed_limit_offset3"), # 40+ mph
]
OFFSET_MAP_METRIC = [
(0, 8.33, "speed_limit_offset1"), # 0-30 km/h
(8.33, 16.67, "speed_limit_offset2"), # 30-60 km/h
(16.67, float("inf"), "speed_limit_offset3"), # 60+ km/h
]
# Speed limit filler constants
BOUNDING_BOX_RADIUS_DEGREE = 0.1
MAX_ENTRIES = 1_000_000
MAX_OVERPASS_DATA_BYTES = 1_073_741_824
MAX_OVERPASS_REQUESTS = 10_000
METERS_PER_DEG_LAT = 111_320
VETTING_INTERVAL_DAYS = 7
# Overpass API URLs
OVERPASS_API_URL = "https://overpass-api.de/api/interpreter"
OVERPASS_STATUS_URL = "https://overpass-api.de/api/status"

View File

@@ -0,0 +1,20 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Engagement tiers for the speed-assist feature. A tier is persisted as an integer
under the "IQSpeedAssistMode" param; the ordinal IS the stored value and must remain
stable (0..3), ordered by how much the tier is allowed to intervene.
"""
from enum import IntEnum
STORE_KEY = "IQSpeedAssistMode"
# none -> just display the limit -> highlight overspeed -> move the set speed
SpeedAssistTier = IntEnum("SpeedAssistTier", "DISABLED ADVISORY ALERTING ACTUATING", start=0)
DEFAULT_TIER = SpeedAssistTier.ADVISORY
def actuates_speed(tier) -> bool:
"""Only the top tier is permitted to drive the cruise set speed."""
return int(tier) == SpeedAssistTier.ACTUATING

View File

@@ -0,0 +1,43 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Chooses which steer-actuator delay the lateral controllers run with: the value the
live estimator learned, or the driver's fixed software delay — gated by the
"IQLiveSteerDelay" param. The pick is mirrored into "IQSteerDelayCache" so consumers that do
not subscribe to liveDelay can still read the current value.
"""
from openpilot.common.params import Params
_ENABLE_KEY = "IQLiveSteerDelay"
_FIXED_KEY = "IQSoftwareSteerDelay"
_CACHE_KEY = "IQSteerDelayCache"
def resolve_steer_delay(params, stock_delay):
"""Learned lateral delay while live-learning is enabled, otherwise the stock delay."""
if not params.get_bool(_ENABLE_KEY):
return stock_delay
return float(params.get(_CACHE_KEY, return_default=True))
def cached_steer_delay():
"""Last value SteerDelayPublisher mirrored into the param — usable without a
liveDelay subscription (e.g. at process startup)."""
return Params().get(_CACHE_KEY, return_default=True)
class SteerDelayPublisher:
"""Refreshes IQSteerDelayCache every lag message: the learned live delay when the
toggle is on, else the actuator delay plus the driver's fixed software offset."""
def __init__(self, car_params):
self._params = Params()
self._actuator_delay = car_params.steerActuatorDelay
def _fixed_delay(self):
return self._actuator_delay + self._params.get(_FIXED_KEY, return_default=True)
def update(self, lag_msg):
live = self._params.get_bool(_ENABLE_KEY)
value = lag_msg.liveDelay.lateralDelay if live else self._fixed_delay()
self._params.put_nonblocking(_CACHE_KEY, value)

1
iqpilot/common/version.h Normal file
View File

@@ -0,0 +1 @@
#define IQPILOT_VERSION "IQ.Pilot 1.0c"