test: sync platform hardware and updater changes

This commit is contained in:
1okko
2026-10-21 19:58:14 +08:00
parent dcc8fe078d
commit 2bf9fcd397
21 changed files with 251 additions and 83 deletions
+1 -14
View File
@@ -20,7 +20,7 @@ SetOption('num_jobs', max(1, int(os.cpu_count()/(1 if "CI" in os.environ else 2)
AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line')
AddOption('--verbose', action='store_true', default=False, help='show full build commands')
release = not os.path.exists(File('#.gitattributes').abspath) # file absent on release branch, see release_files.py
release = not os.path.exists(File('#.gitmodules').abspath) # file absent on release branch, see release_files.py
AddOption('--minimal',
action='store_false',
dest='extras',
@@ -316,8 +316,6 @@ def count_scons_nodes(nodes):
if node in seen:
continue
seen.add(node)
if hasattr(node, 'has_builder') and node.has_builder():
build_product_nodes.add(node)
executor = node.get_executor()
if executor is not None:
stack += executor.get_all_prerequisites() + executor.get_all_children()
@@ -326,7 +324,6 @@ def count_scons_nodes(nodes):
progress_interval = 5
progress_count = 0
build_product_nodes = set()
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
def progress_function(node):
@@ -342,13 +339,3 @@ def progress_function(node):
Progress(progress_function, interval=progress_interval)
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
def check_build_product_size(target, source, env):
limit = 50 * 1024 * 1024 # GitHub max size
for t in target:
if str(t).endswith('.pkl'): # chunked during release packaging
continue
if hasattr(t, 'isfile') and t.isfile() and (size := os.path.getsize(t.abspath)) > limit:
raise SCons.Errors.UserError(f"{t} is {size / (1024 * 1024):.1f} MiB, exceeding the {limit / (1024 * 1024):.1f} MiB limit")
if not GetOption('extras'):
AddPostAction(list(build_product_nodes), Action(check_build_product_size, None))
+2
View File
@@ -56,6 +56,8 @@ function launch {
mv "$DIR" /data/safe_staging/old_openpilot
mv "${STAGING_ROOT}/finalized" "$DIR"
cd "$DIR"
# releases before the openpilot/ move can't swap AGNOS on newer AGNOS, use ours
[ ! -L system/hardware/tici/agnos.json ] && /data/safe_staging/old_openpilot/openpilot/common/hardware/comma/agnos.py --verify system/hardware/tici/agnos.json && sudo reboot
echo "Restarting launch script ${LAUNCHER_LOCATION}"
unset AGNOS_VERSION
@@ -8,6 +8,7 @@ from functools import cached_property, lru_cache
from pathlib import Path
from openpilot.cereal import log
from openpilot.common.esim.base import WEBBING_ICCID_PREFIX
from openpilot.common.utils import sudo_read, sudo_write
from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action
from openpilot.common.esim.base import LPABase
@@ -207,6 +208,8 @@ class HardwareComma(HardwareBase):
def get_network_metered(self, network_type) -> bool:
if network_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G):
if self.get_modem_state().get("iccid", "").startswith(WEBBING_ICCID_PREFIX):
return True
from openpilot.common.params import Params
return Params().get_bool("GsmMetered")
try:
+2 -1
View File
@@ -17,6 +17,8 @@ from ipaddress import IPv4Address, AddressValueError
from enum import Enum
from openpilot.common.esim.base import WEBBING_ICCID_PREFIX
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
@@ -44,7 +46,6 @@ NETWORK_TYPE = {0: "gsm", 1: "gsm", 3: "gsm", 8: "gsm",
11: "nr", 12: "nr", 13: "nr"}
DIAL_CID = 1
WEBBING_ICCID_PREFIX = "8985235"
PPPD_CMD = [
"sudo", "pppd", PPP_PORT, "460800", "noauth", "nodetach", "noipdefault", "usepeerdns",
@@ -9,25 +9,22 @@ from openpilot.common.realtime import Ratekeeper
from openpilot.common.filter_simple import FirstOrderFilter
def read_power(panda=None):
if panda is not None and panda.get_type() == panda.HW_TYPE_CUATRO:
health = panda.health()
return health['voltage'] * health['current'] / 1e6
def read_power():
with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/power1_input") as f:
return int(f.read()) / 1e6
def sample_power(seconds=5, panda=None) -> list[float]:
def sample_power(seconds=5) -> list[float]:
rate = 123
rk = Ratekeeper(rate, print_delay_threshold=None)
pwrs = []
for _ in range(rate*seconds):
pwrs.append(read_power(panda))
pwrs.append(read_power())
rk.keep_time()
return pwrs
def get_power(seconds=5, panda=None):
pwrs = sample_power(seconds, panda)
def get_power(seconds=5):
pwrs = sample_power(seconds)
return np.mean(pwrs)
def wait_for_power(min_pwr, max_pwr, min_secs_in_range, timeout):
@@ -11,7 +11,7 @@ TITLE = tr_noop("Firehose Mode")
DESCRIPTION = tr_noop(
"sunnypilot learns to drive by watching humans, like you, drive.\n\n"
+ "Firehose Mode allows you to maximize your training data uploads to improve "
+ "openpilot's driving models. More data means bigger models, which means better Experimental Mode."
+ "openpilot's driving models. More data means bigger models, which means better driving."
)
INSTRUCTIONS = tr_noop(
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n"
+96
View File
@@ -10,6 +10,7 @@ from openpilot.common.realtime import drop_realtime
from openpilot.common.swaglog import cloudlog
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
from openpilot.selfdrive.ui.lib.api_helpers import get_token
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
class PrimeType(IntEnum):
@@ -22,6 +23,10 @@ class PrimeType(IntEnum):
MAGENTA_NEW = 4
PURPLE = 5
class Provider(str):
GOOGLE = "google"
GITHUB = "github"
APPLE = "apple"
class PrimeState:
FETCH_INTERVAL = 5.0 # seconds between API calls
@@ -33,6 +38,13 @@ class PrimeState:
self._lock = threading.Lock()
self._session = requests.Session() # reuse session to reduce SSL handshake overhead
self.prime_type: PrimeType = self._load_initial_state()
self._prime_trial_available = False
pairing_provider = os.getenv("PAIRING_PROVIDER") or self._params.get("PairingProvider")
self._pairing_provider: Provider | None = Provider(pairing_provider) if pairing_provider is not None else None
self._pairing_email: str | None = self._params.get("PairingEmail")
self._commacare = False
set_offroad_alert("Offroad_Pairing", self.prime_type <= PrimeType.UNPAIRED)
self._running = False
self._thread = None
@@ -56,18 +68,81 @@ class PrimeState:
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token, session=self._session)
if response.status_code == 200:
data = response.json()
set_offroad_alert("Offroad_UploadsIgnored", bool(data.get("ignore_uploads")))
is_paired = data.get("is_paired", False)
prime_type = data.get("prime_type", 0)
self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
if not is_paired:
self.set_provider(None, None)
prime_trial_available = data.get("trial_claimed") is False and data.get("eligible_features", {}).get("prime", False)
self.set_prime_trial_available(prime_trial_available)
commacare = data.get("commacare", False)
self.set_commacare(commacare)
except Exception as e:
cloudlog.error(f"Failed to fetch prime status: {e}")
def _fetch_pairing_provider(self) -> None:
dongle_id = self._params.get("DongleId")
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
return
try:
identity_token = get_token(dongle_id)
response = api_get(f"v1/devices/{dongle_id}/owner", timeout=self.API_TIMEOUT, access_token=identity_token, session=self._session)
if response.status_code == 200:
data = response.json()
user_id = data.get("user_id", "")
provider = Provider(user_id.partition("_")[0])
email = data.get("email")
self.set_provider(provider, email)
except Exception as e:
cloudlog.error(f"Failed to fetch pairing provider: {e}")
def set_type(self, prime_type: PrimeType) -> None:
with self._lock:
if prime_type != self.prime_type:
self.prime_type = prime_type
self._params.put("PrimeType", int(prime_type))
cloudlog.info(f"Prime type updated to {prime_type}")
set_offroad_alert("Offroad_Pairing", self.prime_type <= PrimeType.UNPAIRED)
def set_provider(self, provider: Provider | None, email: str | None):
with self._lock:
if self.prime_type <= PrimeType.UNPAIRED:
provider = None
email = None
email = email or None # if data.get(email) returns "" instead of None
if self._pairing_provider != provider:
self._pairing_provider = provider
if provider is None:
self._params.remove("PairingProvider")
else:
self._params.put("PairingProvider", str(provider))
if self._pairing_email != email:
self._pairing_email = email
if email is None:
self._params.remove("PairingEmail")
else:
self._params.put("PairingEmail", email)
def set_commacare(self, has_commacare: bool):
with self._lock:
if self.prime_type <= PrimeType.UNPAIRED:
self._commacare = False
else:
self._commacare = has_commacare
def set_prime_trial_available(self, prime_trail_available: bool):
with self._lock:
if self.prime_type <= PrimeType.UNPAIRED:
self._prime_trial_available = False
else:
self._prime_trial_available = prime_trail_available
def _worker_thread(self) -> None:
drop_realtime()
@@ -75,6 +150,7 @@ class PrimeState:
while self._running:
if not ui_state.started and device._awake:
self._fetch_prime_status()
self._fetch_pairing_provider()
for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)):
if not self._running:
@@ -109,5 +185,25 @@ class PrimeState:
with self._lock:
return self.prime_type > PrimeType.UNPAIRED
def can_claim_prime_trial(self) -> bool:
with self._lock:
return self._prime_trial_available
def has_commacare(self) -> bool:
with self._lock:
return self._commacare
def get_pairing_provider(self) -> str | None:
with self._lock:
return self._pairing_provider
def get_pairing_account(self) -> str:
with self._lock:
if not self._pairing_provider:
return "unknown"
elif self._pairing_provider == Provider.GITHUB or not self._pairing_email:
return f"{self._pairing_provider} account"
return self._pairing_email
def __del__(self):
self.stop()
@@ -263,6 +263,8 @@ class OffroadAlert(AbstractAlert):
def _build_alerts(self):
self.sorted_alerts = []
for key, config in sorted(OFFROAD_ALERTS.items(), key=lambda x: x[1].get("severity", 0), reverse=True):
if key == "Offroad_Pairing":
continue
severity = config.get("severity", 0)
alert_data = AlertData(key=key, text="", severity=severity)
self.sorted_alerts.append(alert_data)
+14 -13
View File
@@ -385,19 +385,6 @@ def getVersion() -> dict[str, str]:
}
@dispatcher.add_method
def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: str | None = None, place_details: str | None = None) -> dict[str, int]:
destination = {
"latitude": latitude,
"longitude": longitude,
"place_name": place_name,
"place_details": place_details,
}
Params().put("NavDestination", json.dumps(destination))
return {"success": 1}
def scan_dir(path: str, prefix: str, base: str | None = None) -> list[str]:
if base is None:
base = path
@@ -720,6 +707,20 @@ def cancelUpload(upload_id: str | list[str]) -> dict[str, int | str]:
cancelled_uploads.update(cancelled_ids)
return {"success": 1}
@dispatcher.add_method
def setNavDestination(latitude: float, longitude: float) -> dict[str, int | str]:
if not (-90 <= latitude <= 90 and -180 <= longitude <= 180):
return {"success": 0, "error": "Invalid destination coordinates"}
Params().put("NavDestination", {"latitude": latitude, "longitude": longitude}, block=True)
return {"success": 1}
@dispatcher.add_method
def clearNavDestination() -> dict[str, int]:
Params().remove("NavDestination")
return {"success": 1}
@dispatcher.add_method
def setRouteViewed(route: str) -> dict[str, int | str]:
# maintain a list of the last 10 routes viewed in connect
+14 -8
View File
@@ -5,7 +5,7 @@ from openpilot.common.version import get_build_metadata, CHESTNUT_BRANCHES
from openpilot.selfdrive.modeld.helpers import MODELS_DIR, chestnut_compiled
CHESTNUT_RELEASE_BRANCHES = ("release-chestnut", "release-chestnut-staging", "nightly-chestnut")
CHESTNUT_RELEASE_BRANCHES = ("release-chestnut", "release-chestnut-staging", "nightly-chestnut", "nightly-chestnut-dev")
CHESTNUT_POWERED_VOLTAGE = 5000
GPU_TEMP_LIMIT = 100.
MEMORY_TEMP_LIMIT = 95.
@@ -24,6 +24,7 @@ class ChestnutStatus:
self.link_failures = 0
self.model_loading_seen = False
self.model_attempted = False
self.model_failed = False
self.overheated = False
self.usb_seen = False
self.usb_failed = False
@@ -43,14 +44,18 @@ class ChestnutStatus:
self.link_failures = 0
self.model_loading_seen = False
self.model_attempted = False
self.model_failed = False
self.usb_seen = firmware_ok
self.usb_failed = False
self.model_loading_seen |= model_loading
self.model_attempted |= self.model_loading_seen and not model_loading and model_active is not None
self.model_failed |= not offroad and self.model_attempted and model_active is False and len(detected) > 0
if not offroad and self.usb_seen and not firmware_ok:
if not offroad and self.usb_seen and len(devices) != 1:
self.usb_failed = True
if offroad and len(devices) == 1:
self.usb_failed = False
if not offroad and state is not None:
powered = state.supplyVoltage >= CHESTNUT_POWERED_VOLTAGE
@@ -72,6 +77,7 @@ class ChestnutStatus:
self.power_unavailable = False
self.power_lost = False
self.power_restored = False
self.model_failed = False
if state is not None:
gpu_limit = GPU_TEMP_LIMIT - (TEMP_HYSTERESIS if self.overheated else 0.)
@@ -87,15 +93,15 @@ class ChestnutStatus:
chestnut_needs_switch = len(devices) == 1 and not big_model_available and chestnut_target is not None
set_alert("Offroad_ChestnutBranch", chestnut_needs_switch, chestnut_target if chestnut_needs_switch else None)
set_alert("Offroad_ChestnutNotDetected", missing)
set_alert("Offroad_ChestnutOverheated", self.overheated, f"{state.tempC:.0f} °C" if state is not None else None)
set_alert("Offroad_ChestnutOverheated", self.overheated)
set_alert("Offroad_ChestnutUsbSlow", slow_usb, f"{devices[0]['speedMbps']} Mbps" if slow_usb else None)
if self.power_lost:
pcie_alert = ("Chestnut power restored. 12V is stable again, cycle ignition." if self.power_restored else
"Chestnut power disconnected. Check 12V connection, then cycle ignition." if self.power_unavailable else
"Chestnut power lost. Possibly caused by an engine-crank voltage drop. Check 12V connection, then cycle ignition.")
pcie_alert = ("Chestnut power restored. Restart the car to retry." if self.power_restored else
"Chestnut power disconnected. Check the 12V connection, then restart the car." if self.power_unavailable else
"Chestnut power lost. Check the 12V connection, then restart the car.")
else:
pcie_alert = "Chestnut GPU unavailable. PCIe link is not up. Check the GPU is securely seated."
pcie_alert = "Chestnut GPU unavailable. Check the GPU is securely seated, then restart the car."
set_alert("Offroad_ChestnutPcieUnavailable", self.pcie_failed, pcie_alert)
set_alert("Offroad_ChestnutUncompiled", offroad and firmware_ok and not chestnut_compiled())
set_alert("Offroad_ChestnutModelError", self.model_failed and not (missing or self.pcie_failed))
set_alert("Offroad_ChestnutUpdateFailed", offroad and firmware_failed)
self.offroad = offroad
+11 -4
View File
@@ -41,9 +41,10 @@ PANDA_STATES_TIMEOUT = round(1000 / SERVICE_LIST['pandaStates'].frequency * 1.5)
ONROAD_CYCLE_TIME = 1 # seconds to wait offroad after requesting an onroad cycle
class Chestnut:
# flash offroad, modeld ignores chestnut until the product string matches
# flash before going onroad, modeld ignores chestnut until the product string matches
MAX_ATTEMPTS = 3
RETRY_INTERVAL = 20.
STARTUP_TIMEOUT = 30.
def __init__(self):
self.thread: threading.Thread | None = None
@@ -52,6 +53,10 @@ class Chestnut:
self.flashed = False
self.mismatch = False
@property
def flashing(self) -> bool:
return self.thread is not None and self.thread.is_alive() and time.monotonic() - self.last_attempt < self.STARTUP_TIMEOUT
@property
def failed(self) -> bool:
return self.mismatch and self.attempts >= self.MAX_ATTEMPTS and self.thread is not None and not self.thread.is_alive() and not self.flashed
@@ -73,7 +78,7 @@ class Chestnut:
return
if self.thread is not None and self.thread.is_alive():
return
if time.monotonic() - self.last_attempt < self.RETRY_INTERVAL:
if self.attempts > 0 and time.monotonic() - self.last_attempt < self.RETRY_INTERVAL:
return
self.attempts += 1
@@ -223,7 +228,7 @@ def hardware_thread(end_event, hw_queue) -> None:
network_strength=NetworkStrength.unknown,
network_stats={'wwanTx': -1, 'wwanRx': -1},
modem_temps=[],
usb_state=[],
usb_state=get_usb_state(),
)
all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_HW, initialized=False)
@@ -315,7 +320,7 @@ def hardware_thread(end_event, hw_queue) -> None:
chestnut.update(started_ts is None, last_hw_state.usb_state)
chestnut_state = sm["chestnutState"]
chestnut_valid = sm.alive["chestnutState"] and sm.valid["chestnutState"]
chestnut_status.update(started_ts is None, branch, last_hw_state.usb_state, chestnut.failed,
chestnut_status.update(started_ts is None or not onroad_conditions["ignition"], branch, last_hw_state.usb_state, chestnut.failed,
params.get_bool("ChestnutLoading"), params.get("ChestnutActive"),
chestnut_state if chestnut_valid else None, set_offroad_alert_if_changed)
@@ -375,6 +380,8 @@ def hardware_thread(end_event, hw_queue) -> None:
# must be at an engageable thermal band to go onroad
startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.overheated
startup_conditions["not_flashing_chestnut"] = not chestnut.flashing
# ensure device is fully booted
startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted()
+58 -25
View File
@@ -67,7 +67,9 @@ def get_assistnow_messages() -> list[bytes]:
while len(dat) > 0:
assert dat[:2] == b"\xB5\x62"
msg_len = 6 + (dat[5] << 8 | dat[4]) + 2
msgs.append(dat[:msg_len])
# Skip MGA-INI messages; time from proxyserver is a stale cache
if dat[2:4] != b"\x13\x40":
msgs.append(dat[:msg_len])
dat = dat[msg_len:]
return msgs
@@ -146,6 +148,31 @@ class TTYPigeon:
return True
return False
def send_time(pigeon: TTYPigeon) -> None:
if not system_time_valid():
return
cloudlog.warning("Sending current time to ublox")
# UBX-MGA-INI-TIME_UTC
t_now = datetime.now(UTC).replace(tzinfo=None)
msg = add_ubx_checksum(b"\xB5\x62\x13\x40\x18\x00" + struct.pack("<BBBBHBBBBBxIHxxI",
0x10,
0x00,
0x00,
0x80,
t_now.year,
t_now.month,
t_now.day,
t_now.hour,
t_now.minute,
t_now.second,
t_now.microsecond * 1000,
30,
0
))
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
def save_almanac(pigeon: TTYPigeon) -> None:
# store almanac in flash
pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC")
@@ -167,6 +194,27 @@ def init_baudrate(pigeon: TTYPigeon):
pigeon.set_baud(460800)
def send_position(pigeon: TTYPigeon) -> None:
if not (position := Params().get("LastGPSPosition")):
return
with log.Event.from_bytes(position) as evt:
gps = evt.gpsLocationExternal
if datetime.now(UTC).timestamp() - gps.unixTimestampMillis / 1000 > 7 * 24 * 3600:
return
cloudlog.warning("Sending last position to ublox")
# UBX-MGA-INI-POS_LLH
msg = add_ubx_checksum(b"\xB5\x62\x13\x40\x14\x00" + struct.pack("<BB2xiiiI",
0x01, 0x00,
round(gps.latitude * 1e7),
round(gps.longitude * 1e7),
round(gps.altitude * 100),
100_000 * 100,
))
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
def init_pigeon(pigeon: TTYPigeon) -> bool:
# try initializing a few times
for _ in range(10):
@@ -185,7 +233,7 @@ def init_pigeon(pigeon: TTYPigeon) -> bool:
pigeon.send_with_ack(b"\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10")
# UBX-CFG-NAV5 (0x06 0x24)
pigeon.send_with_ack(b"\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63")
pigeon.send_with_ack(b"\xB5\x62\x06\x24\x24\x00\x07\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5C\xAB")
# UBX-CFG-ODO (0x06 0x1E)
pigeon.send_with_ack(b"\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37")
@@ -217,29 +265,6 @@ def init_pigeon(pigeon: TTYPigeon) -> bool:
else:
cloudlog.error(f"failed to restore almanac backup, status: {restore_status}")
# sending time to ublox
if system_time_valid():
t_now = datetime.now(UTC).replace(tzinfo=None)
cloudlog.warning("Sending current time to ublox")
# UBX-MGA-INI-TIME_UTC
msg = add_ubx_checksum(b"\xB5\x62\x13\x40\x18\x00" + struct.pack("<BBBBHBBBBBxIHxxI",
0x10,
0x00,
0x00,
0x80,
t_now.year,
t_now.month,
t_now.day,
t_now.hour,
t_now.minute,
t_now.second,
0,
30,
0
))
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
cloudlog.warning("Pigeon GPS on!")
break
except TimeoutError:
@@ -271,6 +296,13 @@ def init(pigeon: TTYPigeon) -> None:
init_baudrate(pigeon)
init_pigeon(pigeon)
if system_time_valid():
try:
send_time(pigeon)
send_position(pigeon)
except Exception:
cloudlog.exception("failed to send time and position to ublox")
def run_receiving(duration: int = 0):
pm = messaging.PubMaster(['ubloxRaw'])
@@ -301,6 +333,7 @@ def run_receiving(duration: int = 0):
try:
for msg in assist_messages:
pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK)
send_time(pigeon)
cloudlog.warning("AssistNow messages sent")
except Exception:
cloudlog.warning("failed to send AssistNow messages")
+8
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import math
import time
import capnp
import calendar
import numpy as np
@@ -8,6 +9,7 @@ from dataclasses import dataclass
from openpilot.cereal import log
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.system.ubloxd.ubx import Ubx
from openpilot.system.ubloxd.gps import Gps
from openpilot.system.ubloxd.glonass import Glonass
@@ -510,6 +512,8 @@ def main():
parser = UbloxMsgParser()
pm = messaging.PubMaster(['ubloxGnss', 'gpsLocationExternal'])
sock = messaging.sub_sock('ubloxRaw', timeout=100, conflate=False)
params = Params()
last_position_save = float('-inf')
while True:
msg = messaging.recv_one(sock)
@@ -529,6 +533,10 @@ def main():
service, dat = res
pm.send(service, dat)
if service == 'gpsLocationExternal' and dat.gpsLocationExternal.hasFix and time.monotonic() - last_position_save > 60:
params.put("LastGPSPosition", dat.to_bytes())
last_position_save = time.monotonic()
if __name__ == '__main__':
main()
+4
View File
@@ -263,6 +263,10 @@ class GuiScrollPanel2:
def state(self) -> ScrollState:
return self._state
@property
def velocity(self) -> float:
return self._velocity
def is_touch_valid(self) -> bool:
# MIN_VELOCITY_FOR_CLICKING is checked in auto-scroll state
return bool(self._state != ScrollState.MANUAL_SCROLL)
+6
View File
@@ -21,6 +21,7 @@ class ButtonStyle(IntEnum):
NO_EFFECT = 6
KEYBOARD = 7
FORGET_WIFI = 8
PILL = 11 # For pill buttons in list items, for example the multiple button selector
ICON_PADDING = 15
@@ -39,10 +40,12 @@ BUTTON_TEXT_COLOR = {
ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255),
ButtonStyle.KEYBOARD: rl.Color(221, 221, 221, 255),
ButtonStyle.FORGET_WIFI: rl.Color(51, 51, 51, 255),
ButtonStyle.PILL: rl.Color(228, 228, 228, 255),
}
BUTTON_DISABLED_TEXT_COLORS = {
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE,
ButtonStyle.PILL: rl.Color(150, 150, 150, 255),
}
BUTTON_BACKGROUND_COLORS = {
@@ -57,6 +60,7 @@ BUTTON_BACKGROUND_COLORS = {
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
ButtonStyle.KEYBOARD: rl.Color(68, 68, 68, 255),
ButtonStyle.FORGET_WIFI: rl.Color(189, 189, 189, 255),
ButtonStyle.PILL: rl.Color(57, 57, 57, 255),
}
BUTTON_PRESSED_BACKGROUND_COLORS = {
@@ -71,10 +75,12 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
ButtonStyle.KEYBOARD: rl.Color(51, 51, 51, 255),
ButtonStyle.FORGET_WIFI: rl.Color(130, 130, 130, 255),
ButtonStyle.PILL: rl.Color(74, 74, 74, 255),
}
BUTTON_DISABLED_BACKGROUND_COLORS = {
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
ButtonStyle.PILL: rl.Color(57, 57, 57, 150),
}
+6 -5
View File
@@ -7,7 +7,8 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, T
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.button import Button, ButtonStyle
from openpilot.system.ui.widgets.button import (Button, ButtonStyle, BUTTON_TEXT_COLOR, BUTTON_DISABLED_TEXT_COLORS, BUTTON_BACKGROUND_COLORS,
BUTTON_PRESSED_BACKGROUND_COLORS, BUTTON_DISABLED_BACKGROUND_COLORS)
from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT
from openpilot.system.ui.widgets.label import gui_label
from openpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType
@@ -242,12 +243,12 @@ class MultipleButtonAction(ItemAction):
if is_selected:
bg_color = rl.Color(51, 171, 76, 255) # Green
elif is_pressed:
bg_color = rl.Color(74, 74, 74, 255) # Dark gray
bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[ButtonStyle.PILL]
else:
bg_color = rl.Color(57, 57, 57, 255) # Gray
bg_color = BUTTON_BACKGROUND_COLORS[ButtonStyle.PILL]
if not self.enabled:
bg_color = rl.Color(bg_color.r, bg_color.g, bg_color.b, 150) # Dim
bg_color = rl.Color(bg_color.r, bg_color.g, bg_color.b, BUTTON_DISABLED_BACKGROUND_COLORS[ButtonStyle.PILL].a)
# Draw button
rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color)
@@ -257,7 +258,7 @@ class MultipleButtonAction(ItemAction):
text_size = measure_text_cached(self._font, text, 40)
text_x = button_x + (self.button_width - text_size.x) / 2
text_y = button_y + (BUTTON_HEIGHT - text_size.y) / 2
text_color = rl.Color(228, 228, 228, 255) if self.enabled else rl.Color(150, 150, 150, 255)
text_color = BUTTON_TEXT_COLOR[ButtonStyle.PILL] if self.enabled else BUTTON_DISABLED_TEXT_COLORS[ButtonStyle.PILL]
rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color)
def _handle_mouse_release(self, mouse_pos: MousePos):
+6 -2
View File
@@ -353,8 +353,12 @@ class MiciKeyboard(Widget):
# draw black circle behind selected key
circle_alpha = int(self._selected_key_filter.x * 225)
rl.draw_circle_gradient(rl.Vector2(key_x + key.rect.width / 2, key_y + key.rect.height / 2),
SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, circle_alpha), rl.BLANK)
if rl.RAYLIB_VERSION_MAJOR >= 6:
rl.draw_circle_gradient(rl.Vector2(key_x + key.rect.width / 2, key_y + key.rect.height / 2),
SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, circle_alpha), rl.BLANK)
else: # AGNOS updater compatibility for raylib 5
rl.draw_circle_gradient(int(key_x + key.rect.width / 2), int(key_y + key.rect.height / 2),
SELECTED_CHAR_FONT_SIZE, rl.Color(0, 0, 0, circle_alpha), rl.BLANK)
else:
# move other keys away from selected key a bit
dx = key.original_position.x - self._closest_key[0].original_position.x
+9 -1
View File
@@ -97,6 +97,7 @@ class _Scroller(Widget):
self._scroll_enabled: bool | Callable[[], bool] = True
self._show_scroll_indicator = scroll_indicator and self._horizontal
self.scroll_indicator_start_after: Widget | None = None
self._scroll_indicator = ScrollIndicator()
self._edge_shadows = edge_shadows and self._horizontal
@@ -364,7 +365,14 @@ class _Scroller(Widget):
# Draw scroll indicator on top of edge shadows
if self._show_scroll_indicator and len(self._visible_items) > 0:
self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect)
viewport = self._rect
if self.scroll_indicator_start_after is not None:
offset = min(self._rect.width, max(0.0, self.scroll_indicator_start_after.rect.x
+ self.scroll_indicator_start_after.rect.width
+ self._spacing - self._rect.x))
viewport = rl.Rectangle(self._rect.x + offset, self._rect.y, self._rect.width - offset, self._rect.height)
self._scroll_indicator.update(self._scroll_offset, self._content_size, viewport)
self._scroll_indicator.render()
def show_event(self):
+2
View File
@@ -277,6 +277,8 @@ def handle_agnos_update() -> None:
cloudlog.info(f"Beginning background installation for AGNOS {updated_version}")
manifest_path = os.path.join(OVERLAY_MERGED, "openpilot/system/hardware/comma/agnos.json")
if not os.path.isfile(manifest_path): # releases before the openpilot/ move
manifest_path = os.path.join(OVERLAY_MERGED, "system/hardware/tici/agnos.json")
target_slot_number = get_target_slot_number()
flash_agnos_update(manifest_path, target_slot_number, cloudlog, progress_callback=on_progress)
+1 -1
View File
@@ -47,7 +47,7 @@ class PandaJungle(Panda):
HARNESS_ORIENTATION_2 = 2
@classmethod
def spi_connect(cls, serial, ignore_version=False):
def spi_connect(cls, serial):
return None, None, None, None
def flash(self, fn=None, code=None, reconnect=True):