diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 5a55daf6d..d3a7adf76 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -254,11 +254,125 @@ struct CustomReserved15 @0xbd443b539493bc68 { struct CustomReserved16 @0xfc6241ed8877b611 { } -struct CustomReserved17 @0xa30662f84033036c { +struct MapdDownloadLocationDetails @0xff889853e7b0987f { + location @0 :Text; + totalFiles @1 :UInt32; + downloadedFiles @2 :UInt32; } -struct CustomReserved18 @0xc86a3d38d13eb3ef { +struct MapdDownloadProgress @0xfaa35dcac85073a2 { + active @0 :Bool; + cancelled @1 :Bool; + totalFiles @2 :UInt32; + downloadedFiles @3 :UInt32; + locations @4 :List(Text); + locationDetails @5 :List(MapdDownloadLocationDetails); } -struct CustomReserved19 @0xa4f1eb3323f5f582 { +struct MapdPathPoint @0xd6f78acca1bc3939 { + latitude @0 :Float64; + longitude @1 :Float64; + curvature @2 :Float32; + targetVelocity @3 :Float32; +} + +struct MapdExtendedOut @0xa30662f84033036c { + downloadProgress @0 :MapdDownloadProgress; + settings @1 :Text; + path @2 :List(MapdPathPoint); +} + +enum MapdInputType { + download @0; + setTargetLateralAccel @1; + setSpeedLimitOffset @2; + setSpeedLimitControl @3; + setMapCurveSpeedControl @4; + setVisionCurveSpeedControl @5; + setLogLevel @6; + setVisionCurveTargetLatA @7; + setVisionCurveMinTargetV @8; + reloadSettings @9; + saveSettings @10; + setEnableSpeed @11; + setVisionCurveUseEnableSpeed @12; + setMapCurveUseEnableSpeed @13; + setSpeedLimitUseEnableSpeed @14; + setHoldLastSeenSpeedLimit @15; + setTargetSpeedJerk @16; + setTargetSpeedAccel @17; + setTargetSpeedTimeOffset @18; + setDefaultLaneWidth @19; + setMapCurveTargetLatA @20; + loadDefaultSettings @21; + loadRecommendedSettings @22; + setSlowDownForNextSpeedLimit @23; + setSpeedUpForNextSpeedLimit @24; + setHoldSpeedLimitWhileChangingSetSpeed @25; + loadPersistentSettings @26; + cancelDownload @27; + setLogJson @28; + setLogSource @29; + setExternalSpeedLimitControl @30; + setExternalSpeedLimit @31; + setSpeedLimitPriority @32; + setSpeedLimitChangeRequiresAccept @33; + acceptSpeedLimit @34; + setPressGasToAcceptSpeedLimit @35; + setAdjustSetSpeedToAcceptSpeedLimit @36; + setAcceptSpeedLimitTimeout @37; + setPressGasToOverrideSpeedLimit @38; +} + +enum WaySelectionType { + current @0; + predicted @1; + possible @2; + extended @3; + fail @4; +} + +enum SpeedLimitOffsetType { + static @0; + percent @1; +} + +struct MapdIn @0xc86a3d38d13eb3ef { + type @0 :MapdInputType; + float @1 :Float32; + str @2 :Text; + bool @3 :Bool; +} + +enum RoadContext { + freeway @0; + city @1; + unknown @2; +} + +struct MapdOut @0xa4f1eb3323f5f582 { + wayName @0 :Text; + wayRef @1 :Text; + roadName @2 :Text; + speedLimit @3 :Float32; + nextSpeedLimit @4 :Float32; + nextSpeedLimitDistance @5 :Float32; + hazard @6 :Text; + nextHazard @7 :Text; + nextHazardDistance @8 :Float32; + advisorySpeed @9 :Float32; + nextAdvisorySpeed @10 :Float32; + nextAdvisorySpeedDistance @11 :Float32; + oneWay @12 :Bool; + lanes @13 :UInt8; + tileLoaded @14 :Bool; + speedLimitSuggestedSpeed @15 :Float32; + suggestedSpeed @16 :Float32; + estimatedRoadWidth @17 :Float32; + roadContext @18 :RoadContext; + distanceFromWayCenter @19 :Float32; + visionCurveSpeed @20 :Float32; + mapCurveSpeed @21 :Float32; + waySelectionType @22 :WaySelectionType; + speedLimitAccepted @23 :Bool; } diff --git a/cereal/log.capnp b/cereal/log.capnp index 52df45137..b58f1ff29 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2642,9 +2642,9 @@ struct Event { customReserved14 @140 :Custom.CustomReserved14; customReserved15 @141 :Custom.CustomReserved15; customReserved16 @142 :Custom.CustomReserved16; - customReserved17 @143 :Custom.CustomReserved17; - customReserved18 @144 :Custom.CustomReserved18; - customReserved19 @145 :Custom.CustomReserved19; + mapdExtendedOut @143 :Custom.MapdExtendedOut; + mapdIn @144 :Custom.MapdIn; + mapdOut @145 :Custom.MapdOut; # *********** legacy + deprecated *********** model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated diff --git a/cereal/services.py b/cereal/services.py index ecabf0dcc..b5a0a1e57 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -113,6 +113,9 @@ _services: dict[str, tuple] = { "frogpilotPlan": (True, 20., 10), "frogpilotRadarState": (True, 20., 5), "frogpilotSelfdriveState": (True, 100., 10), + "mapdExtendedOut": (True, 1., 1, QueueSize.MEDIUM), + "mapdIn": (True, 1., 1, QueueSize.MEDIUM), + "mapdOut": (True, 20., 20, QueueSize.MEDIUM), } SERVICE_LIST = {name: Service(*vals) for idx, (name, vals) in enumerate(_services.items())} diff --git a/common/params_keys.h b/common/params_keys.h index 989b3d23b..220194937 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -162,6 +162,7 @@ inline static std::unordered_map keys = { {"CalibratedLateralAcceleration", {PERSISTENT, FLOAT, "2.0", "2.0", 2}}, {"CalibrationProgress", {PERSISTENT, FLOAT, "0.0", "0.0", 3}}, {"CameraView", {PERSISTENT, INT, "3", "0", 2}}, + {"CancelDownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"CancelModelDownload", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"CancelThemeDownload", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"CarMake", {PERSISTENT, STRING, "mock", "mock", 0}}, @@ -210,10 +211,10 @@ inline static std::unordered_map keys = { {"DisableOnroadUploads", {PERSISTENT, BOOL, "0", "0", 2}}, {"DisableOpenpilotLongitudinal", {PERSISTENT, BOOL, "0", "0", 0}}, {"DiscordUsername", {PERSISTENT, STRING, "", "", 0}}, + {"DisengageVolume", {PERSISTENT, INT, "101", "101", 2}}, {"DistanceButtonControl", {PERSISTENT, INT, "1", "0", 2}}, {"DistanceIconPack", {PERSISTENT, STRING, "stock", "stock", 0}}, {"DistanceIconToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, - {"DisengageVolume", {PERSISTENT, INT, "101", "101", 2}}, {"DownloadableColors", {PERSISTENT, STRING, "", ""}}, {"DownloadableDistanceIcons", {PERSISTENT, STRING, "", ""}}, {"DownloadableIcons", {PERSISTENT, STRING, "", ""}}, @@ -221,6 +222,7 @@ inline static std::unordered_map keys = { {"DownloadableSounds", {PERSISTENT, STRING, "", ""}}, {"DownloadableWheels", {PERSISTENT, STRING, "", ""}}, {"DownloadAllModels", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, + {"DownloadMaps", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"DriverCamera", {PERSISTENT, BOOL, "0", "0", 1}}, {"DrivingModel", {PERSISTENT, STRING, "wmi-model_default", "wmi-model_default", 1}}, {"DrivingModelName", {PERSISTENT, STRING, "WMI model (Default)", "WMI model (Default)", 1}}, @@ -295,9 +297,9 @@ inline static std::unordered_map keys = { {"MapBoxRequests", {PERSISTENT, JSON, "{}", "{}"}}, {"MapboxSecretKey", {PERSISTENT | DONT_LOG, STRING, "", "", 0}}, {"MapDeceleration", {PERSISTENT, BOOL, "0", "0", 1}}, - {"MapdLogLevel", {CLEAR_ON_MANAGER_START, STRING, "0", "0"}}, + {"MapdSettings", {PERSISTENT, JSON, "{}", "{}"}}, {"MapGears", {PERSISTENT, BOOL, "0", "0", 2}}, - {"MapsSelected", {PERSISTENT, JSON, "{}", "{}", 0}}, + {"MapsSelected", {PERSISTENT, STRING, "", "", 0}}, {"MapSpeedLimit", {CLEAR_ON_MANAGER_START, FLOAT, "0.0", "0.0"}}, {"MaxDesiredAcceleration", {PERSISTENT, FLOAT, "4.0", "2.0", 2}}, {"MinimumBackupSize", {PERSISTENT, INT, "0", "0"}}, @@ -308,7 +310,6 @@ inline static std::unordered_map keys = { {"ModelToDownload", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"ModelUI", {PERSISTENT, BOOL, "1", "0", 2}}, {"NavigationUI", {PERSISTENT, BOOL, "1", "0", 1}}, - {"NextMapSpeedLimit", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}}, {"NNFF", {PERSISTENT, BOOL, "1", "0", 2}}, {"NNFFLite", {PERSISTENT, BOOL, "1", "0", 2}}, {"NNFFModelName", {CLEAR_ON_MANAGER_START, STRING, "", "", 0}}, @@ -327,10 +328,8 @@ inline static std::unordered_map keys = { {"OnroadDistanceButton", {PERSISTENT, BOOL, "0", "0", 0}}, {"OnroadDistanceButtonPressed", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"openpilotMinutes", {PERSISTENT, INT, "0", "0", 0}}, - {"OSMDownloadLocations", {PERSISTENT, JSON, "{}", "{}"}}, - {"OSMDownloadProgress", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"OverpassRequests", {PERSISTENT, JSON, "{}", "{}"}}, - {"PathEdgeWidth", {PERSISTENT, FLOAT, "20", "0", 2}}, + {"PathEdgeWidth", {PERSISTENT, FLOAT, "20.0", "0.0", 2}}, {"PathWidth", {PERSISTENT, FLOAT, "6.1", "5.9", 2}}, {"PauseAOLOnBrake", {PERSISTENT, BOOL, "0", "0", 1}}, {"PauseLateralOnSignal", {PERSISTENT, BOOL, "0", "0", 1}}, @@ -365,7 +364,6 @@ inline static std::unordered_map keys = { {"RelaxedJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}}, {"ReverseCruise", {PERSISTENT, BOOL, "0", "0", 1}}, {"RoadEdgesWidth", {PERSISTENT, FLOAT, "2.0", "2.0", 2}}, - {"RoadName", {CLEAR_ON_MANAGER_START, STRING, "", ""}}, {"RoadNameUI", {PERSISTENT, BOOL, "1", "0", 1}}, {"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1}}, {"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2}}, diff --git a/frogpilot/common/frogpilot_functions.py b/frogpilot/common/frogpilot_functions.py index f9b8c5daa..0a747e969 100644 --- a/frogpilot/common/frogpilot_functions.py +++ b/frogpilot/common/frogpilot_functions.py @@ -9,6 +9,7 @@ import time from pathlib import Path +from cereal import messaging from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid @@ -19,7 +20,7 @@ from openpilot.frogpilot.assets.theme_manager import ThemeManager from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd, use_konik_server from openpilot.frogpilot.common.frogpilot_variables import ( - DISCORD_WEBHOOK_URL_REPORT, ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPD_PATH, MAPS_PATH, THEME_SAVE_PATH, + DISCORD_WEBHOOK_URL_REPORT, ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPS_PATH, THEME_SAVE_PATH, FrogPilotVariables, get_frogpilot_toggles ) @@ -66,6 +67,21 @@ def capture_report(discord_user, report, frogpilot_toggles): def frogpilot_boot_functions(build_metadata, params): params_memory = Params(memory=True) + maps_selected = params.get("MapsSelected") + if maps_selected: + try: + data = json.loads(maps_selected) + if isinstance(data, dict): + new_items = [] + for nation in data.get("nations", []): + new_items.append(f"nation.{nation}") + for state in data.get("states", []): + new_items.append(f"us_state.{state}") + new_items.sort() + params.put("MapsSelected", ",".join(new_items)) + except (json.JSONDecodeError, TypeError, ValueError): + pass + FrogPilotVariables() ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), frogpilot_toggles=get_frogpilot_toggles(), boot_run=True) @@ -138,12 +154,9 @@ def update_boot_logo(frogpilot=False, stock=False): run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options") -def update_maps(now, params, params_memory): - while not MAPD_PATH.exists(): - time.sleep(60) - +def update_maps(now, params, params_memory, manual_update=False): maps_selected = params.get("MapsSelected") - if not maps_selected or not (maps_selected.get("nations") or maps_selected.get("states")): + if not maps_selected: return day = now.day @@ -152,22 +165,49 @@ def update_maps(now, params, params_memory): schedule = params.get("PreferredSchedule") maps_downloaded = MAPS_PATH.exists() - if maps_downloaded and (schedule == 0 or (schedule == 1 and not is_sunday) or (schedule == 2 and not is_first)): + if maps_downloaded and (schedule == 0 or (schedule == 1 and not is_sunday) or (schedule == 2 and not is_first)) and not manual_update: return suffix = "th" if 11 <= day <= 13 else {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th") todays_date = now.strftime(f"%B {day}{suffix}, %Y") - if maps_downloaded and params.get("LastMapsUpdate") == todays_date: + if maps_downloaded and params.get("LastMapsUpdate") == todays_date and not manual_update: return - if params.get("OSMDownloadProgress") is None: - params_memory.put("OSMDownloadLocations", maps_selected) + pm = messaging.PubMaster(["mapdIn"]) + sm = messaging.SubMaster(["mapdExtendedOut"]) - while params.get("OSMDownloadProgress") is not None: - time.sleep(60) + time.sleep(1) + + msg = messaging.new_message("mapdIn") + msg.mapdIn.type = 0 + msg.mapdIn.str = maps_selected + pm.send("mapdIn", msg) + + started = False + while True: + sm.update(1000) + + if params_memory.get_bool("CancelDownloadMaps"): + msg = messaging.new_message("mapdIn") + msg.mapdIn.type = 27 + pm.send("mapdIn", msg) + + params_memory.remove("CancelDownloadMaps") + params_memory.remove("DownloadMaps") + return + + if sm.updated["mapdExtendedOut"]: + progress = sm["mapdExtendedOut"].downloadProgress + + if progress.active: + started = True + + if not progress.active and started: + break params.put("LastMapsUpdate", todays_date) + params_memory.remove("DownloadMaps") def update_openpilot(thread_manager, params): diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py index 8c4ed8500..8d51508d7 100644 --- a/frogpilot/common/frogpilot_variables.py +++ b/frogpilot/common/frogpilot_variables.py @@ -71,7 +71,6 @@ HD_PATH = Path("/cache/use_HD") KONIK_LOGS_PATH = Path("/data/media/0/realdata_konik") KONIK_PATH = Path("/cache/use_konik") -MAPD_PATH = Path("/data/media/0/osm/mapd") MAPS_PATH = Path("/data/media/0/osm/offline") NNFF_MODELS_PATH = Path(BASEDIR) / "frogpilot/assets/nnff_models" diff --git a/frogpilot/controls/lib/speed_limit_controller.py b/frogpilot/controls/lib/speed_limit_controller.py index ff800ce7c..31c1dfff5 100644 --- a/frogpilot/controls/lib/speed_limit_controller.py +++ b/frogpilot/controls/lib/speed_limit_controller.py @@ -9,7 +9,7 @@ from concurrent.futures import ThreadPoolExecutor from openpilot.common.constants import CV from openpilot.common.realtime import DT_MDL -from openpilot.frogpilot.common.frogpilot_utilities import calculate_bearing_offset, calculate_distance_to_point, is_url_pingable +from openpilot.frogpilot.common.frogpilot_utilities import calculate_bearing_offset, is_url_pingable FREE_MAPBOX_REQUESTS = 100_000 @@ -239,7 +239,7 @@ class SpeedLimitController: self.frogpilot_planner.params.put_nonblocking("PreviousSpeedLimit", self.target) def update_limits(self, dashboard_speed_limit, now, time_validated, v_cruise, v_ego, sm): - self.update_map_speed_limit(v_ego) + self.update_map_speed_limit(v_ego, sm) limits = { "Dashboard": dashboard_speed_limit, @@ -302,24 +302,11 @@ class SpeedLimitController: self.speed_limit_changed_timer = 0 self.unconfirmed_speed_limit = 0 - def update_map_speed_limit(self, v_ego): - if not self.frogpilot_planner.gps_position: - return - - self.map_speed_limit = self.frogpilot_planner.params_memory.get("MapSpeedLimit") - - next_map_speed_limit = self.frogpilot_planner.params_memory.get("NextMapSpeedLimit") - self.next_speed_limit = next_map_speed_limit.get("speedlimit", 0) - - if self.next_speed_limit: - current_latitude = self.frogpilot_planner.gps_position.get("latitude") - current_longitude = self.frogpilot_planner.gps_position.get("longitude") - - next_latitude = next_map_speed_limit.get("latitude") - next_longitude = next_map_speed_limit.get("longitude") - - distance_to_upcoming = calculate_distance_to_point(current_latitude, current_longitude, next_latitude, next_longitude) + def update_map_speed_limit(self, v_ego, sm): + self.map_speed_limit = sm["mapdOut"].speedLimit + self.next_speed_limit = sm["mapdOut"].nextSpeedLimit + if self.next_speed_limit > 0: if self.map_speed_limit < self.next_speed_limit: max_lookahead = self.frogpilot_toggles.map_speed_lookahead_higher * v_ego elif self.map_speed_limit > self.next_speed_limit: @@ -327,7 +314,7 @@ class SpeedLimitController: else: max_lookahead = 0 - if distance_to_upcoming < max_lookahead: + if sm["mapdOut"].nextSpeedLimitDistance < max_lookahead: self.map_speed_limit = self.next_speed_limit def update_override(self, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm): diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py index 325d35a2b..4b678f67b 100644 --- a/frogpilot/frogpilot_process.py +++ b/frogpilot/frogpilot_process.py @@ -19,7 +19,7 @@ from openpilot.frogpilot.system.frogpilot_tracking import FrogPilotTracking ASSET_CHECK_RATE = (1 / DT_MDL) -def check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles): +def check_assets(now, theme_manager, thread_manager, params, params_memory, frogpilot_toggles): for asset_type, asset_param in THEME_COMPONENT_PARAMS.items(): asset_to_download = params_memory.get(asset_param) if asset_to_download: @@ -33,6 +33,9 @@ def check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles capture_report(report_data["DiscordUser"], report_data["Issue"], vars(frogpilot_toggles)) params_memory.remove("IssueReported") + if params_memory.get_bool("DownloadMaps"): + thread_manager.run_with_lock(update_maps, (now, params, params_memory, True)) + def transition_offroad(frogpilot_planner, theme_manager, thread_manager, time_validated, sm, params, frogpilot_toggles): params.put("LastGPSPosition", json.dumps(frogpilot_planner.gps_position)) @@ -53,10 +56,10 @@ def update_checks(now, theme_manager, thread_manager, params, params_memory, fro while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")): time.sleep(60) - thread_manager.run_with_lock(update_maps, (now, params, params_memory)) - theme_manager.update_themes(frogpilot_toggles, boot_run) + thread_manager.run_with_lock(update_maps, (now, params, params_memory)) + if frogpilot_toggles.automatic_updates: thread_manager.run_with_lock(update_openpilot, (thread_manager, params)) @@ -89,7 +92,7 @@ def frogpilot_thread(): sm = messaging.SubMaster(["carControl", "carState", "controlsState", "deviceState", "driverMonitoringState", "gpsLocation", "gpsLocationExternal", "liveParameters", "managerState", "modelV2", "onroadEvents", "pandaStates", "radarState", "selfdriveState", "frogpilotCarState", - "frogpilotSelfdriveState", "frogpilotModelV2", "frogpilotOnroadEvents"], + "frogpilotSelfdriveState", "frogpilotModelV2", "frogpilotOnroadEvents", "mapdOut"], poll="modelV2") params = Params(return_defaults=True) @@ -141,7 +144,7 @@ def frogpilot_thread(): started_previously = started if rate_keeper.frame % ASSET_CHECK_RATE == 0: - check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles) + check_assets(now, theme_manager, thread_manager, params, params_memory, frogpilot_toggles) if params_memory.get_bool("FrogPilotTogglesUpdated") or theme_manager.theme_updated: frogpilot_toggles = update_toggles(frogpilot_variables, started, theme_manager, thread_manager, time_validated, params, frogpilot_toggles) diff --git a/frogpilot/navigation/mapd b/frogpilot/navigation/mapd new file mode 100755 index 000000000..f7e115548 Binary files /dev/null and b/frogpilot/navigation/mapd differ diff --git a/frogpilot/navigation/mapd.py b/frogpilot/navigation/mapd.py deleted file mode 100644 index c7c2181f2..000000000 --- a/frogpilot/navigation/mapd.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -# PFEIFER - MAPD - Modified by FrogAi for FrogPilot -import json -import os -import shutil -import stat -import subprocess -import tempfile -import time -import urllib.request - -from pathlib import Path - -from openpilot.common.params import Params - -from openpilot.frogpilot.common.frogpilot_utilities import is_url_pingable -from openpilot.frogpilot.common.frogpilot_variables import MAPD_PATH, RESOURCES_REPO - -VERSION = "v2" - -GITHUB_VERSION_URL = f"https://github.com/{RESOURCES_REPO}/raw/Versions/mapd_version_{VERSION}.json" -GITLAB_VERSION_URL = f"https://gitlab.com/{RESOURCES_REPO}/-/raw/Versions/mapd_version_{VERSION}.json" - -VERSION_PATH = Path("/data/media/0/osm/mapd_version") - -def cleanup_temp_files(): - parent = MAPD_PATH.parent - try: - if not parent.exists() or not parent.is_dir(): - return - except OSError as e: - print(f"Skipping cleanup; cannot access {parent}: {e}") - return - try: - for file in parent.glob("mapd*"): - if file == MAPD_PATH or file == VERSION_PATH: - continue - if file.is_file(): - try: - file.unlink() - except Exception as exception: - print(f"Failed to delete leftover file {file}: {exception}") - except OSError as exception: - print(f"Skipping cleanup in {parent} due to I/O error: {exception}") - -def download(): - Path(MAPD_PATH).parent.mkdir(parents=True, exist_ok=True) - - while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")): - time.sleep(60) - - latest_version = get_latest_version() - urls = [ - f"https://github.com/pfeiferj/openpilot-mapd/releases/download/{latest_version}/mapd", - f"https://gitlab.com/{RESOURCES_REPO}/-/raw/Mapd/{latest_version}" - ] - for url in urls: - try: - with urllib.request.urlopen(url) as response: - with tempfile.NamedTemporaryFile("wb", delete=False, dir=MAPD_PATH.parent) as temp_file: - shutil.copyfileobj(response, temp_file) - temp_file_path = Path(temp_file.name) - os.fsync(temp_file.fileno()) - - os.chmod(temp_file_path, os.stat(temp_file_path).st_mode | stat.S_IEXEC) - os.rename(temp_file_path, MAPD_PATH) - - with open(VERSION_PATH, "w") as version_file: - version_file.write(latest_version) - os.fsync(version_file.fileno()) - - return - except Exception as exception: - print(f"Failed to download mapd from {url}: {exception}") - if "temp_file_path" in locals() and temp_file_path.exists(): - temp_file_path.unlink(missing_ok=True) - -def get_latest_version(): - while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")): - time.sleep(60) - - for url in [GITHUB_VERSION_URL, GITLAB_VERSION_URL]: - try: - with urllib.request.urlopen(url, timeout=10) as response: - return json.loads(response.read().decode("utf-8"))["version"] - except Exception as exception: - print(f"Error fetching mapd version from {url}: {exception}") - return "v0" - -def update_mapd(): - if not MAPD_PATH.exists(): - print(f"{MAPD_PATH} not found. Downloading...") - download() - return False - - if not VERSION_PATH.exists(): - print(f"{VERSION_PATH} not found. Downloading mapd...") - download() - return False - - if is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com"): - try: - with open(VERSION_PATH) as version_file: - local_version = version_file.read().strip() - if local_version != get_latest_version(): - print("New mapd version available. Updating...") - download() - return False - else: - return True - except Exception as exception: - print(f"Error checking version: {exception}") - return False - - if not os.access(MAPD_PATH, os.X_OK): - print(f"{MAPD_PATH} is not executable. Fixing permissions...") - try: - os.chmod(MAPD_PATH, os.stat(MAPD_PATH).st_mode | stat.S_IEXEC) - except Exception as exception: - print(f"Failed to set executable permissions on {MAPD_PATH}: {exception}") - return False - - return True - -def mapd_thread(): - params_memory = Params(memory=True) - - params_memory.put("MapdLogLevel", "disabled") - - while True: - try: - cleanup_temp_files() - except OSError as exception: - print(f"Cleanup errored: {exception}") - time.sleep(5) - continue - - while not update_mapd(): - time.sleep(60) - continue - - try: - process = subprocess.Popen(str(MAPD_PATH)) - process.wait() - except FileNotFoundError as error: - print(f"Subprocess failed: {error}") - download() - -def main(): - mapd_thread() - -if __name__ == "__main__": - main() diff --git a/frogpilot/system/speed_limit_filler.py b/frogpilot/system/speed_limit_filler.py index a4277eb2c..3a1789891 100644 --- a/frogpilot/system/speed_limit_filler.py +++ b/frogpilot/system/speed_limit_filler.py @@ -50,7 +50,7 @@ class MapSpeedLogger: self.gps_location_service = get_gps_location_service(self.params) - self.sm = messaging.SubMaster(["deviceState", "frogpilotCarState", "frogpilotPlan", self.gps_location_service, "modelV2"]) + self.sm = messaging.SubMaster(["deviceState", "frogpilotCarState", "frogpilotPlan", self.gps_location_service, "mapdOut", "modelV2"]) @property def can_make_overpass_request(self): @@ -227,7 +227,8 @@ class MapSpeedLogger: self.previous_coordinates = None return - road_name = self.params_memory.get("RoadName") + road_name = self.sm["mapdOut"].roadName + if not road_name or not current_speed_source: return diff --git a/frogpilot/ui/frogpilot_ui.cc b/frogpilot/ui/frogpilot_ui.cc index b3dcce744..2fafa6132 100644 --- a/frogpilot/ui/frogpilot_ui.cc +++ b/frogpilot/ui/frogpilot_ui.cc @@ -38,7 +38,7 @@ FrogPilotUIState::FrogPilotUIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "carControl", "deviceState", "frogpilotCarState", "frogpilotDeviceState", "frogpilotPlan", "frogpilotRadarState", "frogpilotSelfdriveState", "liveDelay", - "liveParameters", "liveTorqueParameters", "liveTracks", "selfdriveState" + "liveParameters", "liveTorqueParameters", "liveTracks", "mapdExtendedOut", "mapdOut", "selfdriveState" }); wifi = new WifiManager(this); diff --git a/frogpilot/ui/qt/offroad/maps_settings.cc b/frogpilot/ui/qt/offroad/maps_settings.cc index 3e0913411..3aeee0943 100644 --- a/frogpilot/ui/qt/offroad/maps_settings.cc +++ b/frogpilot/ui/qt/offroad/maps_settings.cc @@ -1,5 +1,3 @@ -#include - #include #include "frogpilot/ui/qt/offroad/maps_settings.h" @@ -64,34 +62,6 @@ FrogPilotMapsPanel::FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool for }); settingsList->addItem(removeMapsButton); - resetMapdButton = new ButtonControl(tr("Reset Downloader"), tr("RESET"), - tr("Reset the map downloader. Use this if downloads are stuck or failing.")); - QObject::connect(resetMapdButton, &ButtonControl::clicked, [parent, this]() { - if (ConfirmationDialog::confirm(tr("Reset the map downloader? Your device will reboot afterward."), tr("Reset"), this)) { - std::thread([parent, this]() { - parent->keepScreenOn = true; - - resetMapdButton->setEnabled(false); - resetMapdButton->setValue(tr("Resetting...")); - - std::system("pkill mapd"); - - QDir("/data/media/0/osm").removeRecursively(); - - resetMapdButton->setValue(tr("Reset!")); - - util::sleep_for(2500); - - resetMapdButton->setValue(tr("Rebooting...")); - - util::sleep_for(2500); - - Hardware::reboot(); - }).detach(); - } - }); - settingsList->addItem(resetMapdButton); - settingsList->addItem(mapsSize = new LabelControl(tr("Storage Used"), calculateDirectorySize(mapsFolderPath))); ScrollView *settingsPanel = new ScrollView(settingsList, this); @@ -138,13 +108,10 @@ FrogPilotMapsPanel::FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool for downloadMapsButton->showDescription(); preferredSchedule->showDescription(); removeMapsButton->showDescription(); - resetMapdButton->showDescription(); selectMaps->showDescription(); } - std::string mapsSelected = params.get("MapsSelected"); - hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty(); - hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty(); + hasMapsSelected = !params.get("MapsSelected").empty(); mapsLayout->setCurrentWidget(settingsPanel); }); @@ -156,7 +123,6 @@ void FrogPilotMapsPanel::showEvent(QShowEvent *event) { downloadMapsButton->showDescription(); preferredSchedule->showDescription(); removeMapsButton->showDescription(); - resetMapdButton->showDescription(); selectMaps->showDescription(); } @@ -165,17 +131,23 @@ void FrogPilotMapsPanel::showEvent(QShowEvent *event) { FrogPilotUIState &fs = *frogpilotUIState(); FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + SubMaster &fpsm = *(fs.sm); - std::string mapsSelected = params.get("MapsSelected"); - hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty(); - hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty(); + const cereal::MapdExtendedOut::Reader &mapdExtendedOut = fpsm["mapdExtendedOut"].getMapdExtendedOut(); + const cereal::MapdDownloadProgress::Reader &downloadProgress = mapdExtendedOut.getDownloadProgress(); + + bool mapDownloadActive = downloadProgress.getActive(); + + int mapDownloadDownloaded = downloadProgress.getDownloadedFiles(); + int mapDownloadTotal = downloadProgress.getTotalFiles(); + + hasMapsSelected = !params.get("MapsSelected").empty(); bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; removeMapsButton->setVisible(mapsFolderPath.exists()); - std::string osmDownloadProgress = params.get("OSMDownloadProgress"); - if (!osmDownloadProgress.empty()) { + if (mapDownloadActive) { downloadMapsButton->setText(tr("CANCEL")); downloadStatus->setText(tr("Calculating...")); @@ -183,9 +155,8 @@ void FrogPilotMapsPanel::showEvent(QShowEvent *event) { lastMapsDownload->setVisible(false); removeMapsButton->setVisible(false); - resetMapdButton->setVisible(false); - updateDownloadLabels(osmDownloadProgress); + updateDownloadLabels(mapDownloadDownloaded, mapDownloadTotal); } else { downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked); downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline...")); @@ -194,24 +165,33 @@ void FrogPilotMapsPanel::showEvent(QShowEvent *event) { void FrogPilotMapsPanel::updateState(const UIState &s, const FrogPilotUIState &fs) { - if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) { + if (!isVisible()) { return; } const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; const UIScene &scene = s.scene; + const SubMaster &fpsm = *(fs.sm); + const cereal::MapdExtendedOut::Reader &mapdExtendedOut = fpsm["mapdExtendedOut"].getMapdExtendedOut(); + const cereal::MapdDownloadProgress::Reader &downloadProgress = mapdExtendedOut.getDownloadProgress(); + + bool mapDownloadActive = downloadProgress.getActive(); bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; - std::string osmDownloadProgress = params.get("OSMDownloadProgress"); - if (!osmDownloadProgress.empty() && !cancellingDownload) { - updateDownloadLabels(osmDownloadProgress); + int mapDownloadDownloaded = downloadProgress.getDownloadedFiles(); + int mapDownloadTotal = downloadProgress.getTotalFiles(); + + if (mapDownloadActive && !cancellingDownload) { + updateDownloadLabels(mapDownloadDownloaded, mapDownloadTotal); + } else if (downloadMapsButton->text() == tr("CANCEL")) { + updateDownloadLabels(mapDownloadDownloaded, mapDownloadTotal); } else { downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked); downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline...")); } - parent->keepScreenOn = !osmDownloadProgress.empty(); + parent->keepScreenOn = mapDownloadActive; } void FrogPilotMapsPanel::cancelDownload() { @@ -224,10 +204,8 @@ void FrogPilotMapsPanel::cancelDownload() { downloadStatus->setText(tr("Calculating...")); downloadTimeElapsed->setText(tr("Calculating...")); - params.remove("OSMDownloadProgress"); - params_memory.remove("OSMDownloadLocations"); - - std::system("pkill mapd"); + params_memory.putBool("CancelDownloadMaps", true); + params_memory.remove("DownloadMaps"); QTimer::singleShot(2500, [this]() { cancellingDownload = false; @@ -242,7 +220,6 @@ void FrogPilotMapsPanel::cancelDownload() { lastMapsDownload->setVisible(true); removeMapsButton->setVisible(mapsFolderPath.exists()); - resetMapdButton->setVisible(true); update(); }); @@ -260,53 +237,42 @@ void FrogPilotMapsPanel::startDownload() { lastMapsDownload->setVisible(false); removeMapsButton->setVisible(false); - resetMapdButton->setVisible(false); elapsedTime.start(); startTime = QDateTime::currentDateTime(); - params_memory.put("OSMDownloadLocations", params.get("MapsSelected")); + params_memory.putBool("DownloadMaps", true); } -void FrogPilotMapsPanel::updateDownloadLabels(std::string &osmDownloadProgress) { - static std::regex fileStatusRegex(R"("total_files":(\d+),.*"downloaded_files":(\d+))"); +void FrogPilotMapsPanel::updateDownloadLabels(int downloadedFiles, int totalFiles) { + if (downloadedFiles == totalFiles && totalFiles != 0) { + downloadMapsButton->setText(tr("DOWNLOAD")); + lastMapsDownload->setText(formatCurrentDate()); - std::smatch match; - if (std::regex_search(osmDownloadProgress, match, fileStatusRegex)) { - int totalFiles = std::stoi(match[1].str()); - int downloadedFiles = std::stoi(match[2].str()); + downloadETA->setVisible(false); + downloadStatus->setVisible(false); + downloadTimeElapsed->setVisible(false); - if (downloadedFiles == totalFiles) { - downloadMapsButton->setText(tr("DOWNLOAD")); - lastMapsDownload->setText(formatCurrentDate()); + lastMapsDownload->setVisible(true); + removeMapsButton->setVisible(true); - downloadETA->setVisible(false); - downloadStatus->setVisible(false); - downloadTimeElapsed->setVisible(false); + params.put("LastMapsUpdate", formatCurrentDate().toStdString()); - lastMapsDownload->setVisible(true); - removeMapsButton->setVisible(true); - resetMapdButton->setVisible(true); + update(); - params.put("LastMapsUpdate", formatCurrentDate().toStdString()); - params.remove("OSMDownloadProgress"); - - update(); - - return; - } - - static int previousDownloadedFiles = 0; - if (downloadedFiles != previousDownloadedFiles) { - std::thread([this]() { - mapsSize->setText(calculateDirectorySize(mapsFolderPath)); - }).detach(); - } - - downloadETA->setText(QString("%1").arg(formatETA(elapsedTime.elapsed(), downloadedFiles, previousDownloadedFiles, totalFiles, startTime))); - downloadStatus->setText(QString("%1 / %2 (%3%)").arg(downloadedFiles).arg(totalFiles).arg((downloadedFiles * 100) / totalFiles)); - downloadTimeElapsed->setText(formatElapsedTime(elapsedTime.elapsed())); - - previousDownloadedFiles = downloadedFiles; + return; } + + static int previousDownloadedFiles = 0; + if (downloadedFiles != previousDownloadedFiles) { + std::thread([this]() { + mapsSize->setText(calculateDirectorySize(mapsFolderPath)); + }).detach(); + } + + downloadETA->setText(QString("%1").arg(formatETA(elapsedTime.elapsed(), downloadedFiles, previousDownloadedFiles, totalFiles, startTime))); + downloadStatus->setText(QString("%1 / %2 (%3%)").arg(downloadedFiles).arg(totalFiles).arg((downloadedFiles * 100) / (totalFiles == 0 ? 1 : totalFiles))); + downloadTimeElapsed->setText(formatElapsedTime(elapsedTime.elapsed())); + + previousDownloadedFiles = downloadedFiles; } diff --git a/frogpilot/ui/qt/offroad/maps_settings.h b/frogpilot/ui/qt/offroad/maps_settings.h index 2b5388e89..2b9dba888 100644 --- a/frogpilot/ui/qt/offroad/maps_settings.h +++ b/frogpilot/ui/qt/offroad/maps_settings.h @@ -18,7 +18,7 @@ protected: private: void cancelDownload(); void startDownload(); - void updateDownloadLabels(std::string &osmDownloadProgress); + void updateDownloadLabels(int downloadedFiles, int totalFiles); void updateState(const UIState &s, const FrogPilotUIState &fs); bool cancellingDownload; @@ -27,7 +27,6 @@ private: ButtonControl *downloadMapsButton; ButtonControl *removeMapsButton; - ButtonControl *resetMapdButton; ButtonParamControl *preferredSchedule; diff --git a/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc index 4ff2582f9..4380ae73d 100644 --- a/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc +++ b/frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc @@ -134,6 +134,7 @@ void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPil const cereal::FrogPilotCarState::Reader &frogpilotCarState = fpsm["frogpilotCarState"].getFrogpilotCarState(); const cereal::FrogPilotPlan::Reader &frogpilotPlan = fpsm["frogpilotPlan"].getFrogpilotPlan(); const cereal::FrogPilotSelfdriveState::Reader &frogpilotSelfdriveState = fpsm["frogpilotSelfdriveState"].getFrogpilotSelfdriveState(); + const cereal::MapdOut::Reader &mapdOut = fpsm["mapdOut"].getMapdOut(); const cereal::ModelDataV2::Reader &modelV2 = sm["modelV2"].getModelV2(); const cereal::SelfdriveState::Reader &selfdriveState = sm["selfdriveState"].getSelfdriveState(); @@ -177,7 +178,7 @@ void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPil nextSpeedLimit = frogpilotPlan.getSlcNextSpeedLimit(); redLight = frogpilotPlan.getRedLight(); roadCurvature = frogpilotPlan.getRoadCurvature(); - roadName = QString::fromStdString(params_memory.get("RoadName")); + roadName = QString::fromStdString(mapdOut.getRoadName()); slcOverriddenSpeed = frogpilotPlan.getSlcOverriddenSpeed(); speedLimit = slcOverriddenSpeed != 0 ? slcOverriddenSpeed : frogpilotPlan.getSlcSpeedLimit(); speedLimitChanged = frogpilotPlan.getSpeedLimitChanged(); diff --git a/frogpilot/ui/qt/widgets/navigation_functions.cc b/frogpilot/ui/qt/widgets/navigation_functions.cc index 1ef7c8d36..c4252f95f 100644 --- a/frogpilot/ui/qt/widgets/navigation_functions.cc +++ b/frogpilot/ui/qt/widgets/navigation_functions.cc @@ -1,7 +1,3 @@ -#include -#include -#include - #include "frogpilot/ui/qt/widgets/navigation_functions.h" MapSelectionControl::MapSelectionControl(const QMap &map, bool isCountry) : selectionType(isCountry ? "nations" : "states") { @@ -28,11 +24,15 @@ MapSelectionControl::MapSelectionControl(const QMap &map, bool } void MapSelectionControl::loadSelectedMaps() { - QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object(); + QString mapsSelected = QString::fromStdString(params.get("MapsSelected")); + QStringList mapList = mapsSelected.split(",", QString::SkipEmptyParts); + QString prefix = (selectionType == "nations") ? "nation." : "us_state."; QSet selectedMaps; - for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) { - selectedMaps.insert(value.toString()); + for (const QString &map : mapList) { + if (map.startsWith(prefix)) { + selectedMaps.insert(map.mid(prefix.length())); + } } for (QAbstractButton *button : mapButtons->buttons()) { @@ -41,22 +41,23 @@ void MapSelectionControl::loadSelectedMaps() { } void MapSelectionControl::updateSelectedMaps() { - QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object(); + QString mapsSelected = QString::fromStdString(params.get("MapsSelected")); + QStringList mapList = mapsSelected.split(",", QString::SkipEmptyParts); + QString prefix = (selectionType == "nations") ? "nation." : "us_state."; - QSet selectedMaps; - for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) { - selectedMaps.insert(value.toString()); - } - - for (QAbstractButton *button : mapButtons->buttons()) { - QString mapKey = button->property("mapKey").toString(); - if (button->isChecked()) { - selectedMaps.insert(mapKey); - } else { - selectedMaps.remove(mapKey); + QStringList newMapList; + for (const QString &map : mapList) { + if (!map.startsWith(prefix)) { + newMapList.append(map); } } - existingMaps[selectionType] = QJsonArray::fromStringList(selectedMaps.values()); - params.putNonBlocking("MapsSelected", QJsonDocument(existingMaps).toJson(QJsonDocument::Compact).toStdString()); + for (QAbstractButton *button : mapButtons->buttons()) { + if (button->isChecked()) { + newMapList.append(prefix + button->property("mapKey").toString()); + } + } + + newMapList.sort(); + params.putNonBlocking("MapsSelected", newMapList.join(",").toStdString()); } diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 206728a7b..a8a4173c0 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -133,7 +133,7 @@ elif TICI: procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=5)), procs += [ PythonProcess("frogpilot_process", "frogpilot.frogpilot_process", always_run), - PythonProcess("mapd", "frogpilot.navigation.mapd", always_run), + NativeProcess("mapd", "frogpilot/navigation", ["./mapd"], always_run), PythonProcess("speed_limit_filler", "frogpilot.system.speed_limit_filler", run_speed_limit_filler), ]