mirror of
https://jihulab.com/mr-one/openpilot.git
synced 2026-10-27 21:13:42 +08:00
test: sync platform hardware and updater changes
This commit is contained in:
+1
-14
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user