IQ.Pilot Release Commit @ b79954e

This commit is contained in:
IQ.Lvbs CI [bot]
2026-07-25 18:44:03 -05:00
parent f94087822f
commit 563022daa3
179 changed files with 696 additions and 610 deletions

View File

@@ -462,7 +462,7 @@ def hardware_thread(end_event, hw_queue) -> None:
startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted()
# user-forced status (Always Offroad can be temporarily overridden)
offroad_mode = params.get_bool("OffroadMode")
offroad_mode = params.get_bool("IQAlwaysOffroad")
force_onroad_until = params.get("ForceOnroadUntil", return_default=True)
now = int(time.time())
force_onroad_active = offroad_mode and force_onroad_until > now

View File

@@ -78,17 +78,17 @@
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79.img.xz",
"hash": "a0bf6d22e1134fc6c47158d63901c06324ea3b3808ed2fec35801a888cf3d526",
"hash_raw": "036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz",
"hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0",
"hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "9a08b97618dceceed48205ec674330bb97c7f76487a6cca2f68288aaa42aaf8e",
"ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596",
"alt": {
"hash": "036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79.img",
"hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img",
"size": 6291456000
}
}

View File

@@ -1 +1 @@
Xmaq5VKDz8zpg4uJI4cTwgkgKp+HzFlL+5pzHfWvJXqVSNnlOZ/H5dNzm2MScLgzjnxcRYxferbIXjTyZKrzAw==
6ZRxEduDaLm1coUVvI/0QAZtmbf+tY0TWMZtaEaxV7I2fKFfGPG/6oOizuFsmd/SSQls84kap9MwKDEc8XfIDA==

View File

@@ -67,17 +67,17 @@
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79.img.xz",
"hash": "a0bf6d22e1134fc6c47158d63901c06324ea3b3808ed2fec35801a888cf3d526",
"hash_raw": "036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz",
"hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0",
"hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "9a08b97618dceceed48205ec674330bb97c7f76487a6cca2f68288aaa42aaf8e",
"ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596",
"alt": {
"hash": "036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-036bb0bcf945115c6488efadcdb56a42192201290a5fe4572ac7b46549146c79.img",
"hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img",
"size": 6291456000
}
}

View File

@@ -1 +1 @@
IeObYseNfOAYVt6hNKkFMX+RmBAQBU9iiwbox8y6sUdZZs7ZdrUqWJ1ou6yxxsx2JCkgOCQk8z9yT5frz5CeDg==
+gqhJbKMr9i9lW7WrtKebqKXlM0Asr/i7ZodOzIAHzAgzGEvPCWbfco2jo0/jO7O2UzZwKJUl/WzHlsOtJvnCw==

View File

@@ -43,7 +43,10 @@ struct EncoderSettings {
}
static EncoderSettings QcamEncoderSettings() {
int _qcam_bitrate = getenv("QCAM_BITRATE") ? atoi(getenv("QCAM_BITRATE")) : 3'200'000;
// Keep Konn3kt route uploads useful without turning each 60-second qcamera
// segment into a 26 MB file. This is still a substantial improvement over
// stock qcam, while CBR keeps storage and upload usage predictable.
int _qcam_bitrate = getenv("QCAM_BITRATE") ? atoi(getenv("QCAM_BITRATE")) : 2'400'000;
return EncoderSettings{.encode_type = cereal::EncodeIndex::Type::QCAMERA_H264, .bitrate = _qcam_bitrate, .gop_size = 15};
}
@@ -137,8 +140,8 @@ const EncoderInfo qcam_encoder_info = {
.filename = "qcamera.ts",
.cbr = true, // enforce the bitrate so upload size stays predictable (no VBR overshoot)
.get_settings = [](int){return EncoderSettings::QcamEncoderSettings();},
.frame_width = 1578, // 3x the stock 526x330, same road-cam aspect ratio
.frame_height = 990,
.frame_width = 1315, // 2.5x the stock 526x330, same road-cam aspect ratio
.frame_height = 825,
.include_audio = Params().getBool("RecordAudio"),
INIT_ENCODE_FUNCTIONS(QRoadEncode),
};

View File

@@ -11,7 +11,6 @@ import threading
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
def unblock_stdout() -> None:
# get a non-blocking stdout
@@ -103,51 +102,3 @@ def save_bootlog():
t = threading.Thread(target=fn, args=(tmp, ))
t.daemon = True
t.start()
# REVERT ME! ---------------------------------------------------------------
# One-shot migration carrying values across the sunny -> IQ param key renames.
# Old keys are no longer registered in params_keys.h, so their values are read
# straight off disk, copied to the new IQ key, and the stale file removed.
# Delete this block (and its manager_init() call) once fielded devices have
# booted past it at least once.
_RENAMED_PARAMS = {
"QuietMode": "IQAlertSilence",
"SpeedLimitMode": "IQSpeedAssistMode",
"SpeedLimitPolicy": "IQSpeedAssistPolicy",
"SpeedLimitOffsetType": "IQSpeedAssistOffsetType",
"SpeedLimitValueOffset": "IQSpeedAssistValueOffset",
"LaneTurnDesire": "IQLaneTurnDesire",
"LaneTurnValue": "IQLaneTurnValue",
"BlinkerPauseLateralControl": "IQBlinkerPauseLateral",
"BlinkerMinLateralControlSpeed": "IQBlinkerMinLateralSpeed",
"DevUIInfo": "IQDevUIInfo",
}
def migrate_renamed_params(params: Params | None = None) -> None:
"""REVERT ME! carry stored values across the sunny->IQ key renames, then drop the old files.
Copies at the file level: on-disk param values are raw bytes, so this preserves the exact
stored representation and sidesteps put()'s typed-value check for BOOL/INT/FLOAT keys.
"""
p = params if params is not None else Params()
for old, new in _RENAMED_PARAMS.items():
try:
old_path = p.get_param_path(old)
if not os.path.isfile(old_path):
continue
new_path = p.get_param_path(new)
if not os.path.exists(new_path):
with open(old_path, "rb") as f:
value = f.read()
tmp = new_path + ".tmp"
with open(tmp, "wb") as f:
f.write(value)
f.flush()
os.fsync(f.fileno())
os.rename(tmp, new_path)
os.remove(old_path)
except Exception:
cloudlog.exception(f"param rename migration failed for {old} -> {new}")
# END REVERT ME! ------------------------------------------------------------

View File

@@ -17,7 +17,7 @@ from openpilot.common.params import Params, ParamKeyFlag
from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE
from openpilot.system.loggerd.crash_recovery import recover_unclean_segments
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog, heal_param_perms, migrate_renamed_params
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog, heal_param_perms
from openpilot.system.manager.process import ensure_running
from openpilot.system.manager.process_config import managed_processes
from openpilot.iqpilot.konn3kt.registration import register, UNREGISTERED_DONGLE_ID
@@ -40,7 +40,6 @@ def manager_init() -> None:
build_metadata = get_build_metadata()
params = Params()
migrate_renamed_params(params) # REVERT ME! sunny->IQ param key migration (runs before defaults are filled)
params.clear_all(ParamKeyFlag.CLEAR_ON_MANAGER_START)
params.clear_all(ParamKeyFlag.CLEAR_ON_ONROAD_TRANSITION)
params.clear_all(ParamKeyFlag.CLEAR_ON_OFFROAD_TRANSITION)
@@ -50,7 +49,7 @@ def manager_init() -> None:
# device boot mode
if params.get("DeviceBootMode") == 1: # start in Always Offroad mode
params.put_bool("OffroadMode", True)
params.put_bool("IQAlwaysOffroad", True)
if params.get_bool("RecordFrontLock"):
params.put_bool("RecordFront", True)

View File

@@ -33,10 +33,19 @@ EMOJI_REGEX = re.compile(
flags=re.UNICODE
)
_emoji_font_loaded = False
def _load_emoji_font() -> ImageFont.FreeTypeFont | None:
global _emoji_font
if _emoji_font is None:
_emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109)
global _emoji_font, _emoji_font_loaded
if not _emoji_font_loaded:
_emoji_font_loaded = True
try:
# FONT_DIR is an importlib.resources path. Inside the setup zipapp it points into the archive,
# so str() yields a path through the .zip that PIL can't open ("cannot open resource"). Read
# the bytes and hand PIL a file object so it works both on disk and inside the zipapp.
_emoji_font = ImageFont.truetype(io.BytesIO(FONT_DIR.joinpath("NotoColorEmoji.ttf").read_bytes()), 109)
except Exception:
_emoji_font = None # never crash the whole UI over an emoji glyph
return _emoji_font
def find_emoji(text):
@@ -44,12 +53,15 @@ def find_emoji(text):
def emoji_tex(emoji):
if emoji not in _cache:
font = _load_emoji_font()
if font is None:
return None
img = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.text((0, 0), emoji, font=_load_emoji_font(), embedded_color=True)
draw.text((0, 0), emoji, font=font, embedded_color=True)
with io.BytesIO() as buffer:
img.save(buffer, format="PNG")
l = buffer.tell()
buffer.seek(0)
_cache[emoji] = rl.load_texture_from_image(rl.load_image_from_memory(".png", buffer.getvalue(), l))
return _cache[emoji]
return _cache.get(emoji)

View File

@@ -67,6 +67,9 @@ class MeteredType(IntEnum):
NO = 2
_WARNED_UNSUPPORTED_NETWORKS: set[tuple[int, int, int]] = set()
def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType:
wpa_props = wpa_flags | rsn_flags
@@ -83,7 +86,10 @@ def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityTyp
# WPA2, WPA2+WPA3 mixed, or WPA — all handled via WPA key_mgmt (NM negotiates SAE if available)
return SecurityType.WPA2
else:
cloudlog.warning(f"Unsupported network! flags: {flags}, wpa_flags: {wpa_flags}, rsn_flags: {rsn_flags}")
_key = (flags, wpa_flags, rsn_flags)
if _key not in _WARNED_UNSUPPORTED_NETWORKS:
_WARNED_UNSUPPORTED_NETWORKS.add(_key)
cloudlog.warning(f"Unsupported network! flags: {flags}, wpa_flags: {wpa_flags}, rsn_flags: {rsn_flags}")
return SecurityType.UNSUPPORTED

View File

@@ -50,6 +50,8 @@ INSTALLER_URL_PATH = "/tmp/installer_url"
# "<user>/<branch>" maps to a GitHub fork. IQ.OS uses the DRM "magic" compositor, not Wayland,
# so comma's downloaded installers (installer.comma.ai) crash on launch; clone the fork directly.
GITHUB_FORK_URL = "https://github.com/{user}/openpilot.git"
# IQ.Pilot lives on the IQ Lvbs git server; github.com/IQLvbs is DMCA'd and dead.
GIT_URL_OVERRIDES = {"IQLvbs": "https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot.git"}
CONTINUE = """#!/usr/bin/env bash
@@ -756,7 +758,7 @@ class Setup(Widget):
pass
def _fork_install_thread(self, user: str, branch: str):
git_url = GITHUB_FORK_URL.format(user=user)
git_url = GIT_URL_OVERRIDES.get(user) or GITHUB_FORK_URL.format(user=user)
label = f"{user}/{branch}"
try:
subprocess.run(["rm", "-rf", TMP_INSTALL_PATH], check=False)
@@ -774,7 +776,8 @@ class Setup(Widget):
subprocess.run(["git", "-C", TMP_INSTALL_PATH, "submodule", "update", "--init"], check=False)
run_cmd(["rm", "-f", VALID_CACHE_PATH])
run_cmd(["rm", "-rf", INSTALL_PATH])
# sudo: a prior *run* install can leave root-owned .pyc here that a comma-user rm can't delete.
run_cmd(["sudo", "rm", "-rf", INSTALL_PATH])
run_cmd(["mv", TMP_INSTALL_PATH, INSTALL_PATH])
self._ble_progress("installing", 90)
@@ -882,6 +885,11 @@ class Setup(Widget):
# AGNOS might try to execute the installer before this process exits.
# Therefore, important to close the fd before renaming the installer.
os.close(fd)
# comma's ELF installer does `rm -rf /data/openpilot` as the comma user and asserts it
# succeeds; a prior *run* install leaves root-owned __pycache__ .pyc it can't delete, so it
# aborts before continue.sh and bounces back to setup. Clear the old tree first (sudo) so the
# installer's rm succeeds.
subprocess.run(["sudo", "rm", "-rf", INSTALL_PATH], check=False)
os.rename(tmpfile, INSTALLER_DESTINATION_PATH)
with open(INSTALLER_URL_PATH, "w") as f:

View File

@@ -51,6 +51,8 @@ INSTALLER_DESTINATION_PATH = "/tmp/installer"
INSTALLER_URL_PATH = "/tmp/installer_url"
GITHUB_FORK_URL = "https://github.com/{user}/openpilot.git"
# IQ.Pilot lives on the IQ Lvbs git server; github.com/IQLvbs is DMCA'd and dead.
GIT_URL_OVERRIDES = {"IQLvbs": "https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot.git"}
CONTINUE = """#!/usr/bin/env bash
@@ -589,7 +591,7 @@ class Setup(Widget):
pass
def _fork_install_thread(self, user: str, branch: str):
git_url = GITHUB_FORK_URL.format(user=user)
git_url = GIT_URL_OVERRIDES.get(user) or GITHUB_FORK_URL.format(user=user)
label = f"{user}/{branch}"
fail_msg = "Ensure the entered URL is valid, and the device's internet connection is good."
try:
@@ -609,7 +611,8 @@ class Setup(Widget):
subprocess.run(["git", "-C", TMP_INSTALL_PATH, "submodule", "update", "--init"], check=False)
run_cmd(["rm", "-f", VALID_CACHE_PATH])
run_cmd(["rm", "-rf", INSTALL_PATH])
# sudo: a prior *run* install can leave root-owned .pyc here that a comma-user rm can't delete.
run_cmd(["sudo", "rm", "-rf", INSTALL_PATH])
run_cmd(["mv", TMP_INSTALL_PATH, INSTALL_PATH])
self._ble_progress("installing", 90)
@@ -718,6 +721,11 @@ class Setup(Widget):
# AGNOS might try to execute the installer before this process exits.
# Therefore, important to close the fd before renaming the installer.
os.close(fd)
# comma's ELF installer does `rm -rf /data/openpilot` as the comma user and asserts it
# succeeds; a prior *run* install leaves root-owned __pycache__ .pyc it can't delete, so it
# aborts before continue.sh and bounces back to setup. Clear the old tree first (sudo) so the
# installer's rm succeeds.
subprocess.run(["sudo", "rm", "-rf", INSTALL_PATH], check=False)
os.rename(tmpfile, INSTALLER_DESTINATION_PATH)
with open(INSTALLER_URL_PATH, "w") as f:

View File

@@ -406,7 +406,8 @@ class Label(Widget):
line_pos.x += width_before.x
tex = emoji_tex(emoji)
rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height * FONT_SCALE, self._text_color)
if tex is not None:
rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height * FONT_SCALE, self._text_color)
line_pos.x += self._font_size * FONT_SCALE
prev_index = end
rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color)
@@ -849,8 +850,9 @@ class UnifiedLabel(Widget):
# Draw emoji
tex = emoji_tex(emoji)
emoji_scale = self._font_size / tex.height * FONT_SCALE
rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color)
if tex is not None:
emoji_scale = self._font_size / tex.height * FONT_SCALE
rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color)
# Emoji width is font_size * FONT_SCALE (as per measure_text_cached)
line_pos.x += self._font_size * FONT_SCALE
prev_index = end