Merge branch 'upstream/openpilot/master' into sync-20250823

# Conflicts:
#	.github/workflows/release.yaml
#	README.md
#	RELEASES.md
#	common/params_keys.h
#	docs/CARS.md
#	opendbc_repo
#	panda
#	release/build_stripped.sh
#	selfdrive/controls/lib/longitudinal_planner.py
#	selfdrive/modeld/modeld.py
#	selfdrive/ui/feedback/feedbackd.py
#	selfdrive/ui/translations/main_ar.ts
#	selfdrive/ui/translations/main_de.ts
#	selfdrive/ui/translations/main_es.ts
#	selfdrive/ui/translations/main_fr.ts
#	selfdrive/ui/translations/main_ja.ts
#	selfdrive/ui/translations/main_ko.ts
#	selfdrive/ui/translations/main_pt-BR.ts
#	selfdrive/ui/translations/main_th.ts
#	selfdrive/ui/translations/main_tr.ts
#	selfdrive/ui/translations/main_zh-CHS.ts
#	selfdrive/ui/translations/main_zh-CHT.ts
#	system/version.py
#	uv.lock
This commit is contained in:
Jason Wen
2025-08-23 20:47:06 -04:00
60 changed files with 506 additions and 579 deletions
-34
View File
@@ -1,34 +0,0 @@
#if SENSOR_ID == 1
#define VIGNETTE_PROFILE_8DT0MM
#define BIT_DEPTH 12
#define PV_MAX 4096
#define BLACK_LVL 168
float4 normalize_pv(int4 parsed, float vignette_factor) {
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX - BLACK_LVL);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.82717181, -0.31231438, 0.07307673);
corrected += rgb.y * (float3)(-0.5743977, 1.36858544, -0.53183455);
corrected += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
// tone mapping params
const float gamma_k = 0.75;
const float gamma_b = 0.125;
const float mp = 0.01; // ideally midpoint should be adaptive
const float rk = 9 - 100*mp;
// poly approximation for s curve
return (rgb > mp) ?
((rk * (rgb-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(rgb-mp))) + gamma_k*mp + gamma_b) :
((rk * (rgb-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(rgb-mp))) + gamma_k*mp + gamma_b);
}
#endif
-58
View File
@@ -1,58 +0,0 @@
#if SENSOR_ID == 3
#define BGGR
#define VIGNETTE_PROFILE_4DT6MM
#define BIT_DEPTH 12
#define PV_MAX10 1023
#define PV_MAX12 4095
#define PV_MAX16 65536 // gamma curve is calibrated to 16bit
#define BLACK_LVL 48
float combine_dual_pvs(float lv, float sv, int expo_time) {
float svc = fmax(sv * expo_time, (float)(64 * (PV_MAX10 - BLACK_LVL)));
float svd = sv * fmin(expo_time, 8.0) / 8;
if (expo_time > 64) {
if (lv < PV_MAX10 - BLACK_LVL) {
return lv / (PV_MAX16 - BLACK_LVL);
} else {
return (svc / 64) / (PV_MAX16 - BLACK_LVL);
}
} else {
if (lv > 32) {
return (lv * 64 / fmax(expo_time, 8.0)) / (PV_MAX16 - BLACK_LVL);
} else {
return svd / (PV_MAX16 - BLACK_LVL);
}
}
}
float4 normalize_pv_hdr(int4 parsed, int4 short_parsed, float vignette_factor, int expo_time) {
float4 pl = convert_float4(parsed - BLACK_LVL);
float4 ps = convert_float4(short_parsed - BLACK_LVL);
float4 pv;
pv.s0 = combine_dual_pvs(pl.s0, ps.s0, expo_time);
pv.s1 = combine_dual_pvs(pl.s1, ps.s1, expo_time);
pv.s2 = combine_dual_pvs(pl.s2, ps.s2, expo_time);
pv.s3 = combine_dual_pvs(pl.s3, ps.s3, expo_time);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float4 normalize_pv(int4 parsed, float vignette_factor) {
float4 pv = (convert_float4(parsed) - BLACK_LVL) / (PV_MAX12 - BLACK_LVL);
return clamp(pv*vignette_factor, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.55361989, -0.268894615, -0.000593219);
corrected += rgb.y * (float3)(-0.421217301, 1.51883144, -0.69760146);
corrected += rgb.z * (float3)(-0.132402589, -0.249936825, 1.69819468);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
return (10 * rgb) / (1 + 9 * rgb);
}
#endif
-47
View File
@@ -1,47 +0,0 @@
#if SENSOR_ID == 2
#define VIGNETTE_PROFILE_8DT0MM
#define BIT_DEPTH 12
#define BLACK_LVL 64
float ox_lut_func(int x) {
if (x < 512) {
return x * 5.94873e-8;
} else if (512 <= x && x < 768) {
return 3.0458e-05 + (x-512) * 1.19913e-7;
} else if (768 <= x && x < 1536) {
return 6.1154e-05 + (x-768) * 2.38493e-7;
} else if (1536 <= x && x < 1792) {
return 0.0002448 + (x-1536) * 9.56930e-7;
} else if (1792 <= x && x < 2048) {
return 0.00048977 + (x-1792) * 1.91441e-6;
} else if (2048 <= x && x < 2304) {
return 0.00097984 + (x-2048) * 3.82937e-6;
} else if (2304 <= x && x < 2560) {
return 0.0019601 + (x-2304) * 7.659055e-6;
} else if (2560 <= x && x < 2816) {
return 0.0039207 + (x-2560) * 1.525e-5;
} else {
return 0.0078421 + (exp((x-2816)/273.0) - 1) * 0.0092421;
}
}
float4 normalize_pv(int4 parsed, float vignette_factor) {
// PWL
float4 pv = {ox_lut_func(parsed.s0), ox_lut_func(parsed.s1), ox_lut_func(parsed.s2), ox_lut_func(parsed.s3)};
return clamp(pv*vignette_factor*256.0, 0.0, 1.0);
}
float3 color_correct(float3 rgb) {
float3 corrected = rgb.x * (float3)(1.5664815, -0.29808738, -0.03973474);
corrected += rgb.y * (float3)(-0.48672447, 1.41914433, -0.40295248);
corrected += rgb.z * (float3)(-0.07975703, -0.12105695, 1.44268722);
return corrected;
}
float3 apply_gamma(float3 rgb, int expo_time) {
return -0.507089*exp(-12.54124638*rgb) + 0.9655*powr(rgb, 0.5) - 0.472597*rgb + 0.507089;
}
#endif
+5 -5
View File
@@ -21,16 +21,16 @@ class TiciFanController(BaseFanController):
self.controller = PIDController(k_p=0, k_i=4e-3, k_f=1, rate=(1 / DT_HW))
def update(self, cur_temp: float, ignition: bool) -> int:
self.controller.neg_limit = -(100 if ignition else 30)
self.controller.pos_limit = -(30 if ignition else 0)
self.controller.pos_limit = 100 if ignition else 30
self.controller.neg_limit = 30 if ignition else 0
if ignition != self.last_ignition:
self.controller.reset()
error = 75 - cur_temp
fan_pwr_out = -int(self.controller.update(
error = cur_temp - 75
fan_pwr_out = int(self.controller.update(
error=error,
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, -100])
feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100])
))
self.last_ignition = ignition
+6 -6
View File
@@ -67,17 +67,17 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img.xz",
"hash": "49faee0e9b084abf0ea46f87722e3366bbd0435fb6b25cce189295c1ff368da1",
"hash_raw": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "db07761be0130e35a9d3ea6bec8df231260d3e767ae770850f18f10e14d0ab3f",
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"alt": {
"hash": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img",
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"size": 5368709120
}
}
+18 -18
View File
@@ -350,51 +350,51 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img.xz",
"hash": "49faee0e9b084abf0ea46f87722e3366bbd0435fb6b25cce189295c1ff368da1",
"hash_raw": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "db07761be0130e35a9d3ea6bec8df231260d3e767ae770850f18f10e14d0ab3f",
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"alt": {
"hash": "18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643",
"url": "https://commadist.azureedge.net/agnosupdate/system-18100d9065bb44a315262041b9fb6bfd9e59179981876e442200cc1284d43643.img",
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"size": 5368709120
}
},
{
"name": "userdata_90",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-02f7abb4b667c04043c0c6950145aaebd704851261f32256d0f7e84a52059dda.img.xz",
"hash": "1eda66d4e31222fc2e792a62ae8e7d322fc643f0b23785e7527bb51a9fee97c7",
"hash_raw": "02f7abb4b667c04043c0c6950145aaebd704851261f32256d0f7e84a52059dda",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_90-602d5103cba97e1b07f76508d5febb47cfc4463a7e31bd20e461b55c801feb0a.img.xz",
"hash": "6a11d448bac50467791809339051eed2894aae971c37bf6284b3b972a99ba3ac",
"hash_raw": "602d5103cba97e1b07f76508d5febb47cfc4463a7e31bd20e461b55c801feb0a",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "679b650ee04b7b1ef610b63fde9b43569fded39ceacf88789b564de99c221ea1"
"ondevice_hash": "e014d92940a696bf8582807259820ab73948b950656ed83a45da738f26083705"
},
{
"name": "userdata_89",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-bab8399bbe3968f3c496f7bc83c2541b33acc1f47814c4ad95801bf5cb7e7588.img.xz",
"hash": "e63d3277285aae1f04fd7f4f48429ce35010f4843ab755f10d360c3aa788e484",
"hash_raw": "bab8399bbe3968f3c496f7bc83c2541b33acc1f47814c4ad95801bf5cb7e7588",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_89-4d7f6d12a5557eb6e3cbff9a4cd595677456fdfddcc879eddcea96a43a9d8b48.img.xz",
"hash": "748e31a5fc01fc256c012e359c3382d1f98cce98feafe8ecc0fca3e47caef116",
"hash_raw": "4d7f6d12a5557eb6e3cbff9a4cd595677456fdfddcc879eddcea96a43a9d8b48",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "2947374fc5980ffe3c5b94b61cc1c81bc55214f494153ed234164801731f5dc0"
"ondevice_hash": "c181b93050787adcfef730c086bcb780f28508d84e6376d9b80d37e5dc02b55e"
},
{
"name": "userdata_30",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_30-22c874b4b66bbc000f3219abede8d62cb307f5786fd526a8473c61422765dea0.img.xz",
"hash": "12d9245711e8c49c51ff2c7b82d7301f2fcb1911edcddb35a105a80911859113",
"hash_raw": "22c874b4b66bbc000f3219abede8d62cb307f5786fd526a8473c61422765dea0",
"url": "https://commadist.azureedge.net/agnosupdate/userdata_30-80a76c8e56bbd7536fd5e87e8daa12984e2960db4edeb1f83229b2baeecc4668.img.xz",
"hash": "09ff390e639e4373d772e1688d05a5ac77a573463ed1deeff86390686fa686f9",
"hash_raw": "80a76c8e56bbd7536fd5e87e8daa12984e2960db4edeb1f83229b2baeecc4668",
"size": 32212254720,
"sparse": true,
"full_check": true,
"has_ab": false,
"ondevice_hash": "03c8b65c945207f887ed6c52d38b53d53d71c8597dcb0b63dfbb11f7cfff8d2b"
"ondevice_hash": "2c01ab470c02121c721ff6afc25582437e821686207f3afef659387afb69c507"
}
]
+1 -1
View File
@@ -44,7 +44,7 @@ def manager_init() -> None:
# set unset params to their default value
for k in params.all_keys():
default_value = params.get_default_value(k)
if default_value and params.get(k) is None:
if default_value is not None and params.get(k) is None:
params.put(k, default_value)
# Create folders needed for msgq
+2 -1
View File
@@ -46,9 +46,10 @@ class TestManager:
manager.main()
for k in params.all_keys():
default_value = params.get_default_value(k)
if default_value:
if default_value is not None:
assert params.get(k) == default_value
assert params.get("OpenpilotEnabledToggle")
assert params.get("RouteCount") == 0
@pytest.mark.skip("this test is flaky the way it's currently written, should be moved to test_onroad")
def test_clean_exit(self, subtests):
+6 -3
View File
@@ -67,7 +67,8 @@ class MouseEvent(NamedTuple):
class MouseState:
def __init__(self):
def __init__(self, scale: float = 1.0):
self._scale = scale
self._events: deque[MouseEvent] = deque(maxlen=MOUSE_THREAD_RATE) # bound event list
self._prev_mouse_event: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS
@@ -102,8 +103,10 @@ class MouseState:
def _handle_mouse_event(self):
for slot in range(MAX_TOUCH_SLOTS):
mouse_pos = rl.get_touch_position(slot)
x = mouse_pos.x / self._scale if self._scale != 1.0 else mouse_pos.x
y = mouse_pos.y / self._scale if self._scale != 1.0 else mouse_pos.y
ev = MouseEvent(
MousePos(mouse_pos.x, mouse_pos.y),
MousePos(x, y),
slot,
rl.is_mouse_button_pressed(slot),
rl.is_mouse_button_released(slot),
@@ -133,7 +136,7 @@ class GuiApplication:
self._trace_log_callback = None
self._modal_overlay = ModalOverlay()
self._mouse = MouseState()
self._mouse = MouseState(self._scale)
self._mouse_events: list[MouseEvent] = []
# Debug variables
+4
View File
@@ -39,6 +39,10 @@ vec4 getGradientColor(vec2 pos) {
float t = clamp(dot(pos - gradientStart, normalizedDir) / gradientLength, 0.0, 1.0);
if (gradientColorCount <= 1) return gradientColors[0];
// handle t before first / after last stop
if (t <= gradientStops[0]) return gradientColors[0];
if (t >= gradientStops[gradientColorCount-1]) return gradientColors[gradientColorCount-1];
for (int i = 0; i < gradientColorCount - 1; i++) {
if (t >= gradientStops[i] && t <= gradientStops[i+1]) {
float segmentT = (t - gradientStops[i]) / (gradientStops[i+1] - gradientStops[i]);
+1 -1
View File
@@ -37,7 +37,7 @@ NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device"
NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8
TETHERING_IP_ADDRESS = "192.168.43.1"
DEFAULT_TETHERING_PASSWORD = "12345678"
DEFAULT_TETHERING_PASSWORD = "swagswagcomma"
# NetworkManager device states
+75 -19
View File
@@ -6,10 +6,14 @@ import time
import urllib.request
from urllib.parse import urlparse
from enum import IntEnum
import shutil
import pyray as rl
from cereal import log
from openpilot.common.run import run_cmd
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio
@@ -30,6 +34,19 @@ BUTTON_SPACING = 50
OPENPILOT_URL = "https://openpilot.comma.ai"
USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}"
CONTINUE_PATH = "/data/continue.sh"
TMP_CONTINUE_PATH = "/data/continue.sh.new"
INSTALL_PATH = "/data/openpilot"
VALID_CACHE_PATH = "/data/.openpilot_cache"
INSTALLER_SOURCE_PATH = "/usr/comma/installer"
INSTALLER_DESTINATION_PATH = "/tmp/installer"
INSTALLER_URL_PATH = "/tmp/installer_url"
CONTINUE = """#!/usr/bin/env bash
cd /data/openpilot
exec ./launch_openpilot.sh
"""
class SetupState(IntEnum):
LOW_VOLTAGE = 0
@@ -93,14 +110,21 @@ class Setup(Widget):
self._network_setup_continue_button.set_enabled(False)
self._network_setup_title_label = Label("Connect to Wi-Fi", TITLE_FONT_SIZE, FontWeight.BOLD, TextAlignment.LEFT)
self._custom_software_warning_continue_button = Button("Continue", self._custom_software_warning_continue_button_callback)
self._custom_software_warning_continue_button = Button("Scroll to continue", self._custom_software_warning_continue_button_callback,
button_style=ButtonStyle.PRIMARY)
self._custom_software_warning_continue_button.set_enabled(False)
self._custom_software_warning_back_button = Button("Back", self._custom_software_warning_back_button_callback)
self._custom_software_warning_title_label = Label("WARNING: Custom Software", 100, FontWeight.BOLD, TextAlignment.LEFT, text_color=rl.Color(255,89,79,255),
text_padding=60)
self._custom_software_warning_body_label = Label("Use caution when installing third-party software. Third-party software has not been tested by comma,"
+ " and may cause damage to your device and/or vehicle.\n\nIf you'd like to proceed, use https://flash.comma.ai "
self._custom_software_warning_body_label = Label("Use caution when installing third-party software.\n\n"
+ "⚠️ It has not been tested by comma.\n\n"
+ "⚠️ It may not comply with relevant safety standards.\n\n"
+ "⚠️ It may cause damage to your device and/or vehicle.\n\n"
+ "If you'd like to proceed, use https://flash.comma.ai "
+ "to restore your device to a factory state later.",
85, text_alignment=TextAlignment.LEFT, text_padding=60)
self._custom_software_warning_body_scroll_panel = GuiScrollPanel()
self._downloading_body_label = Label("Downloading...", TITLE_FONT_SIZE, FontWeight.MEDIUM)
try:
@@ -136,21 +160,19 @@ class Setup(Widget):
self.state = SetupState.SOFTWARE_SELECTION
def _custom_software_warning_continue_button_callback(self):
self.state = SetupState.CUSTOM_SOFTWARE
self.state = SetupState.NETWORK_SETUP
self.stop_network_check_thread.clear()
self.start_network_check()
def _getting_started_button_callback(self):
self.state = SetupState.NETWORK_SETUP
self.stop_network_check_thread.clear()
self.start_network_check()
self.state = SetupState.SOFTWARE_SELECTION
def _software_selection_back_button_callback(self):
self.state = SetupState.NETWORK_SETUP
self.stop_network_check_thread.clear()
self.start_network_check()
self.state = SetupState.GETTING_STARTED
def _software_selection_continue_button_callback(self):
if self._software_selection_openpilot_button.selected:
self.download(OPENPILOT_URL)
self.use_openpilot()
else:
self.state = SetupState.CUSTOM_SOFTWARE_WARNING
@@ -158,11 +180,14 @@ class Setup(Widget):
self.state = SetupState.GETTING_STARTED
def _network_setup_back_button_callback(self):
self.state = SetupState.GETTING_STARTED
self.state = SetupState.SOFTWARE_SELECTION
def _network_setup_continue_button_callback(self):
self.state = SetupState.SOFTWARE_SELECTION
self.stop_network_check_thread.set()
if self._software_selection_openpilot_button.selected:
self.download(OPENPILOT_URL)
else:
self.state = SetupState.CUSTOM_SOFTWARE
def render_low_voltage(self, rect: rl.Rectangle):
rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE)
@@ -274,13 +299,23 @@ class Setup(Widget):
self._download_failed_startover_button.render(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT))
def render_custom_software_warning(self, rect: rl.Rectangle):
self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, rect.y + 150, rect.width - 265, TITLE_FONT_SIZE))
self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, rect.y + 200 , rect.width - 50, BODY_FONT_SIZE * 3))
warn_rect = rl.Rectangle(rect.x, rect.y, rect.width, 1500)
offset = self._custom_software_warning_body_scroll_panel.handle_scroll(rect, warn_rect)
button_width = (rect.width - MARGIN * 3) / 2
button_y = rect.height - MARGIN - BUTTON_HEIGHT
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE))
y_offset = rect.y + offset.y
self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE))
self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200 , rect.width - 50, BODY_FONT_SIZE * 3))
rl.end_scissor_mode()
self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT))
self._custom_software_warning_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT))
if offset.y < (rect.height - warn_rect.height):
self._custom_software_warning_continue_button.set_enabled(True)
self._custom_software_warning_continue_button.set_text("Continue")
def render_custom_software(self):
def handle_keyboard_result(result):
@@ -299,6 +334,23 @@ class Setup(Widget):
self.keyboard.set_title("Enter URL", "for Custom Software")
gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result)
def use_openpilot(self):
if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH):
os.remove(VALID_CACHE_PATH)
with open(TMP_CONTINUE_PATH, "w") as f:
f.write(CONTINUE)
run_cmd(["chmod", "+x", TMP_CONTINUE_PATH])
shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH)
shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH)
# give time for installer UI to take over
time.sleep(1)
gui_app.request_close()
else:
self.state = SetupState.NETWORK_SETUP
self.stop_network_check_thread.clear()
self.start_network_check()
def download(self, url: str):
# autocomplete incomplete URLs
if re.match("^([^/.]+)/([^/]+)$", url):
@@ -316,7 +368,7 @@ class Setup(Widget):
try:
import tempfile
_, tmpfile = tempfile.mkstemp(prefix="installer_")
fd, tmpfile = tempfile.mkstemp(prefix="installer_")
headers = {"User-Agent": USER_AGENT, "X-openpilot-serial": HARDWARE.get_serial()}
req = urllib.request.Request(self.download_url, headers=headers)
@@ -346,12 +398,16 @@ class Setup(Widget):
self.download_failed(self.download_url, "No custom software found at this URL.")
return
os.rename(tmpfile, "/tmp/installer")
os.chmod("/tmp/installer", 0o755)
# AGNOS might try to execute the installer before this process exits.
# Therefore, important to close the fd before renaming the installer.
os.close(fd)
os.rename(tmpfile, INSTALLER_DESTINATION_PATH)
with open("/tmp/installer_url", "w") as f:
with open(INSTALLER_URL_PATH, "w") as f:
f.write(self.download_url)
# give time for installer UI to take over
time.sleep(5)
gui_app.request_close()
except Exception:
+55 -71
View File
@@ -1,17 +1,17 @@
from dataclasses import dataclass
from enum import IntEnum
from functools import partial
from threading import Lock
from typing import Literal
from typing import cast
import pyray as rl
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper, SecurityType
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.button import ButtonStyle, Button, TextAlignment
from openpilot.system.ui.widgets.button import ButtonStyle, Button
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.keyboard import Keyboard
from openpilot.system.ui.widgets.label import gui_label
from openpilot.system.ui.widgets.label import TextAlignment, gui_label
NM_DEVICE_STATE_NEED_AUTH = 60
MIN_PASSWORD_LENGTH = 8
@@ -27,43 +27,20 @@ STRENGTH_ICONS = [
]
@dataclass
class StateIdle:
action: Literal["idle"] = "idle"
@dataclass
class StateConnecting:
network: NetworkInfo
action: Literal["connecting"] = "connecting"
@dataclass
class StateNeedsAuth:
network: NetworkInfo
retry: bool
action: Literal["needs_auth"] = "needs_auth"
@dataclass
class StateShowForgetConfirm:
network: NetworkInfo
action: Literal["show_forget_confirm"] = "show_forget_confirm"
@dataclass
class StateForgetting:
network: NetworkInfo
action: Literal["forgetting"] = "forgetting"
UIState = StateIdle | StateConnecting | StateNeedsAuth | StateShowForgetConfirm | StateForgetting
class UIState(IntEnum):
IDLE = 0
CONNECTING = 1
NEEDS_AUTH = 2
SHOW_FORGET_CONFIRM = 3
FORGETTING = 4
class WifiManagerUI(Widget):
def __init__(self, wifi_manager: WifiManagerWrapper):
super().__init__()
self.state: UIState = StateIdle()
self.state: UIState = UIState.IDLE
self._state_network: NetworkInfo | None = None # for CONNECTING / NEEDS_AUTH / SHOW_FORGET_CONFIRM / FORGETTING
self._password_retry: bool = False # for NEEDS_AUTH
self.btn_width: int = 200
self.scroll_panel = GuiScrollPanel()
self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True)
@@ -93,17 +70,16 @@ class WifiManagerUI(Widget):
gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
return
match self.state:
case StateNeedsAuth(network, retry):
self.keyboard.set_title("Wrong password" if retry else "Enter password", f"for {network.ssid}")
self.keyboard.reset()
gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(network, result))
case StateShowForgetConfirm(network):
self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{network.ssid}"?')
self._confirm_dialog.reset()
gui_app.set_modal_overlay(self._confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(network, result))
case _:
self._draw_network_list(rect)
if self.state == UIState.NEEDS_AUTH and self._state_network:
self.keyboard.set_title("Wrong password" if self._password_retry else "Enter password", f"for {self._state_network.ssid}")
self.keyboard.reset()
gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(NetworkInfo, self._state_network), result))
elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network:
self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{self._state_network.ssid}"?')
self._confirm_dialog.reset()
gui_app.set_modal_overlay(self._confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result))
else:
self._draw_network_list(rect)
def _on_password_entered(self, network: NetworkInfo, result: int):
if result == 1:
@@ -113,13 +89,13 @@ class WifiManagerUI(Widget):
if len(password) >= MIN_PASSWORD_LENGTH:
self.connect_to_network(network, password)
elif result == 0:
self.state = StateIdle()
self.state = UIState.IDLE
def on_forgot_confirm_finished(self, network, result: int):
if result == 1:
self.forget_network(network)
elif result == 0:
self.state = StateIdle()
self.state = UIState.IDLE
def _draw_network_list(self, rect: rl.Rectangle):
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT)
@@ -147,17 +123,18 @@ class WifiManagerUI(Widget):
security_icon_rect = rl.Rectangle(signal_icon_rect.x - spacing - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
status_text = ""
match self.state:
case StateConnecting(network=connecting):
if connecting.ssid == network.ssid:
self._networks_buttons[network.ssid].set_enabled(False)
status_text = "CONNECTING..."
case StateForgetting(network=forgetting):
if forgetting.ssid == network.ssid:
self._networks_buttons[network.ssid].set_enabled(False)
status_text = "FORGETTING..."
case _:
self._networks_buttons[network.ssid].set_enabled(True)
if self.state == UIState.CONNECTING and self._state_network:
if self._state_network.ssid == network.ssid:
self._networks_buttons[network.ssid].set_enabled(False)
status_text = "CONNECTING..."
elif self.state == UIState.FORGETTING and self._state_network:
if self._state_network.ssid == network.ssid:
self._networks_buttons[network.ssid].set_enabled(False)
status_text = "FORGETTING..."
elif network.security_type == SecurityType.UNSUPPORTED:
self._networks_buttons[network.ssid].set_enabled(False)
else:
self._networks_buttons[network.ssid].set_enabled(True)
self._networks_buttons[network.ssid].render(ssid_rect)
@@ -181,13 +158,16 @@ class WifiManagerUI(Widget):
def _networks_buttons_callback(self, network):
if self.scroll_panel.is_touch_valid():
if not network.is_saved and network.security_type != SecurityType.OPEN:
self.state = StateNeedsAuth(network, False)
self.state = UIState.NEEDS_AUTH
self._state_network = network
self._password_retry = False
elif not network.is_connected:
self.connect_to_network(network)
def _forget_networks_buttons_callback(self, network):
if self.scroll_panel.is_touch_valid():
self.state = StateShowForgetConfirm(network)
self.state = UIState.SHOW_FORGET_CONFIRM
self._state_network = network
def _draw_status_icon(self, rect, network: NetworkInfo):
"""Draw the status icon based on network's connection state"""
@@ -212,14 +192,16 @@ class WifiManagerUI(Widget):
rl.draw_texture_v(gui_app.texture(STRENGTH_ICONS[strength_level], ICON_SIZE, ICON_SIZE), rl.Vector2(rect.x, rect.y), rl.WHITE)
def connect_to_network(self, network: NetworkInfo, password=''):
self.state = StateConnecting(network)
self.state = UIState.CONNECTING
self._state_network = network
if network.is_saved and not password:
self.wifi_manager.activate_connection(network.ssid)
else:
self.wifi_manager.connect_to_network(network.ssid, password)
def forget_network(self, network: NetworkInfo):
self.state = StateForgetting(network)
self.state = UIState.FORGETTING
self._state_network = network
network.is_saved = False
self.wifi_manager.forget_connection(network.ssid)
@@ -236,22 +218,24 @@ class WifiManagerUI(Widget):
with self._lock:
network = next((n for n in self._networks if n.ssid == ssid), None)
if network:
self.state = StateNeedsAuth(network, True)
self.state = UIState.NEEDS_AUTH
self._state_network = network
self._password_retry = True
def _on_activated(self):
with self._lock:
if isinstance(self.state, StateConnecting):
self.state = StateIdle()
if self.state == UIState.CONNECTING:
self.state = UIState.IDLE
def _on_forgotten(self, ssid):
with self._lock:
if isinstance(self.state, StateForgetting):
self.state = StateIdle()
if self.state == UIState.FORGETTING:
self.state = UIState.IDLE
def _on_connection_failed(self, ssid: str, error: str):
with self._lock:
if isinstance(self.state, StateConnecting):
self.state = StateIdle()
if self.state == UIState.CONNECTING:
self.state = UIState.IDLE
def main():
+5 -2
View File
@@ -242,6 +242,9 @@ class Updater:
b: str | None = self.params.get("UpdaterTargetBranch")
if b is None:
b = self.get_branch(BASEDIR)
b = {
("tici", "release3"): "release-tici"
}.get((HARDWARE.get_device_type(), b), b)
return b
@property
@@ -283,8 +286,8 @@ class Updater:
self.params.put("LastUpdateUptimeOnroad", last_uptime_onroad)
self.params.put("LastUpdateRouteCount", last_route_count)
else:
last_uptime_onroad = self.params.get("LastUpdateUptimeOnroad") or last_uptime_onroad
last_route_count = self.params.get("LastUpdateRouteCount") or last_route_count
last_uptime_onroad = self.params.get("LastUpdateUptimeOnroad", return_default=True)
last_route_count = self.params.get("LastUpdateRouteCount", return_default=True)
if exception is None:
self.params.remove("LastUpdateException")
+1 -1
View File
@@ -13,7 +13,7 @@ from openpilot.common.git import get_commit, get_origin, get_branch, get_short_b
RELEASE_SP_BRANCHES = ['release-c3']
TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new']
MASTER_SP_BRANCHES = ['master']
RELEASE_BRANCHES = ['release3-staging', 'release3', 'nightly'] + RELEASE_SP_BRANCHES
RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] + RELEASE_SP_BRANCHES
TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + TESTED_SP_BRANCHES
BUILD_METADATA_FILENAME = "build.json"