mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-05 16:26:06 +08:00
Custom Themes
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"LaneLines": {
|
||||
"red": 255,
|
||||
"green": 255,
|
||||
"blue": 255,
|
||||
"alpha": 178
|
||||
},
|
||||
"LeadMarker": {
|
||||
"red": 201,
|
||||
"green": 34,
|
||||
"blue": 49,
|
||||
"alpha": 255
|
||||
},
|
||||
"Path": {
|
||||
"red": 48,
|
||||
"green": 255,
|
||||
"blue": 156,
|
||||
"alpha": 255
|
||||
},
|
||||
"PathEdge": {
|
||||
"red": 38,
|
||||
"green": 209,
|
||||
"blue": 125,
|
||||
"alpha": 255
|
||||
},
|
||||
"Sidebar1": {
|
||||
"red": 255,
|
||||
"green": 255,
|
||||
"blue": 255,
|
||||
"alpha": 178
|
||||
},
|
||||
"Sidebar2": {
|
||||
"red": 255,
|
||||
"green": 255,
|
||||
"blue": 255,
|
||||
"alpha": 255
|
||||
},
|
||||
"Sidebar3": {
|
||||
"red": 255,
|
||||
"green": 255,
|
||||
"blue": 255,
|
||||
"alpha": 255
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
+1
@@ -0,0 +1 @@
|
||||
../../../selfdrive/assets/images
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../selfdrive/assets/sounds
|
||||
@@ -0,0 +1 @@
|
||||
../../../selfdrive/assets/icons/chffr_wheel.png
|
||||
@@ -0,0 +1,522 @@
|
||||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import requests
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_download_utilities import GITLAB_URL, download_file, get_repository_url, handle_error, verify_download
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import delete_file, extract_zip, load_json_file, update_json_file
|
||||
from openpilot.frogpilot.common.frogpilot_variables import ACTIVE_THEME_PATH, RESOURCES_REPO, THEME_SAVE_PATH
|
||||
|
||||
CANCEL_DOWNLOAD_PARAM = "CancelThemeDownload"
|
||||
DOWNLOAD_PROGRESS_PARAM = "ThemeDownloadProgress"
|
||||
|
||||
STOCKOP_THEME_PATH = Path(__file__).parent / "stock_theme"
|
||||
|
||||
THEME_COMPONENT_PARAMS = {
|
||||
"colors": "ColorToDownload",
|
||||
"distance_icons": "DistanceIconToDownload",
|
||||
"icons": "IconToDownload",
|
||||
"signals": "SignalToDownload",
|
||||
"sounds": "SoundToDownload",
|
||||
"steering_wheels": "WheelToDownload"
|
||||
}
|
||||
|
||||
class ThemeManager:
|
||||
def __init__(self, params, params_memory, boot_run=False):
|
||||
self.params = params
|
||||
self.params_memory = params_memory
|
||||
|
||||
self.downloading_theme = False
|
||||
self.theme_updated = False
|
||||
|
||||
self.previous_asset_mappings = {}
|
||||
|
||||
self.theme_sizes_path = THEME_SAVE_PATH / "theme_sizes.json"
|
||||
|
||||
self.theme_sizes = load_json_file(self.theme_sizes_path)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Accept-Language": "en",
|
||||
"User-Agent": "frogpilot-theme-downloader/1.0 (https://github.com/FrogAi/FrogPilot)"
|
||||
})
|
||||
|
||||
if boot_run:
|
||||
self.copy_default_theme()
|
||||
|
||||
@staticmethod
|
||||
def copy_default_theme():
|
||||
world_frog_day_theme_path = HOLIDAY_THEME_PATH / "world_frog_day"
|
||||
|
||||
for theme_subfolder_name, save_subfolder_path in [
|
||||
("colors", "theme_packs/frog/colors"),
|
||||
("distance_icons", "theme_packs/frog-animated/distance_icons"),
|
||||
("icons", "theme_packs/frog-animated/icons"),
|
||||
("signals", "theme_packs/frog/signals"),
|
||||
("sounds", "theme_packs/frog/sounds"),
|
||||
]:
|
||||
source_folder_path = world_frog_day_theme_path / theme_subfolder_name
|
||||
destination_folder_path = THEME_SAVE_PATH / save_subfolder_path
|
||||
destination_folder_path.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(source_folder_path, destination_folder_path, dirs_exist_ok=True)
|
||||
|
||||
steering_wheel_image_path = world_frog_day_theme_path / "steering_wheel/wheel.png"
|
||||
steering_wheel_save_path = THEME_SAVE_PATH / "steering_wheels/frog.png"
|
||||
steering_wheel_save_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(steering_wheel_image_path, steering_wheel_save_path)
|
||||
|
||||
def download_theme(self, theme_component, theme_name, asset_param, frogpilot_toggles):
|
||||
self.downloading_theme = True
|
||||
|
||||
repo_url = get_repository_url(self.session)
|
||||
if not repo_url:
|
||||
handle_error(None, asset_param, "Repository unavailable", "GitHub and GitLab are offline...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
|
||||
self.downloading_theme = False
|
||||
return
|
||||
|
||||
if theme_component == "distance_icons":
|
||||
download_link = f"{repo_url}/Distance-Icons/{theme_name}"
|
||||
download_path = THEME_SAVE_PATH / "theme_packs" / theme_name / theme_component
|
||||
extension = ".zip"
|
||||
elif theme_component == "steering_wheels":
|
||||
download_link = f"{repo_url}/Steering-Wheels/{theme_name}"
|
||||
download_path = THEME_SAVE_PATH / theme_component / theme_name
|
||||
extension = ".gif"
|
||||
else:
|
||||
download_link = f"{repo_url}/Themes/{theme_name}/{theme_component}"
|
||||
download_path = THEME_SAVE_PATH / "theme_packs" / theme_name / theme_component
|
||||
extension = ".zip"
|
||||
|
||||
theme_path = download_path.with_suffix(extension)
|
||||
theme_url = download_link + extension
|
||||
|
||||
delete_file(theme_path)
|
||||
|
||||
print(f"Downloading theme from GitHub: {theme_name}")
|
||||
download_file(CANCEL_DOWNLOAD_PARAM, theme_path, asset_param, self.params_memory, DOWNLOAD_PROGRESS_PARAM, self.session, theme_url)
|
||||
|
||||
if theme_component == "steering_wheels" and not theme_path.exists() and theme_path.with_suffix(".png").exists():
|
||||
theme_path = theme_path.with_suffix(".png")
|
||||
extension = ".png"
|
||||
theme_url = theme_url.replace(".gif", ".png")
|
||||
|
||||
if self.params_memory.get_bool(CANCEL_DOWNLOAD_PARAM):
|
||||
delete_file(theme_path)
|
||||
handle_error(None, asset_param, "Download cancelled...", "Download cancelled...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
|
||||
|
||||
self.downloading_theme = False
|
||||
return
|
||||
|
||||
if verify_download(theme_path, self.params_memory, self.session, theme_url):
|
||||
print(f"Theme {theme_name} downloaded and verified successfully from GitHub!")
|
||||
self.update_theme_size(theme_component, theme_name, theme_path.stat().st_size)
|
||||
|
||||
if extension == ".zip":
|
||||
self.params_memory.put(DOWNLOAD_PROGRESS_PARAM, "Unpacking theme...")
|
||||
extract_zip(theme_path, download_path)
|
||||
|
||||
self.params_memory.put(DOWNLOAD_PROGRESS_PARAM, "Downloaded!")
|
||||
self.params_memory.remove(asset_param)
|
||||
|
||||
self.downloading_theme = False
|
||||
|
||||
self.update_themes(frogpilot_toggles)
|
||||
return
|
||||
elif self.handle_verification_failure(extension, theme_component, theme_name, asset_param, theme_path, download_path, frogpilot_toggles):
|
||||
return
|
||||
|
||||
handle_error(download_path, asset_param, "Download failed...", "Download failed...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
|
||||
self.downloading_theme = False
|
||||
|
||||
def fetch_assets(self, repo_url, frogpilot_toggles):
|
||||
is_github = "github" in repo_url
|
||||
is_gitlab = "gitlab" in repo_url
|
||||
|
||||
repo_encoded = quote_plus(RESOURCES_REPO)
|
||||
|
||||
assets = {"themes": {}, "wheels": []}
|
||||
try:
|
||||
def list_files(branch):
|
||||
if is_github:
|
||||
response = self.session.get(f"https://api.github.com/repos/{RESOURCES_REPO}/git/trees/{branch}?recursive=1", timeout=10)
|
||||
response.raise_for_status()
|
||||
return [
|
||||
{
|
||||
"path": item.get("path", ""),
|
||||
"name": Path(item.get("path", "")).name,
|
||||
"type": item.get("type"),
|
||||
"size": item.get("size", 0),
|
||||
}
|
||||
for item in response.json().get("tree", [])
|
||||
if item.get("type") == "blob"
|
||||
]
|
||||
if is_gitlab:
|
||||
response = self.session.get(f"https://gitlab.com/api/v4/projects/{repo_encoded}/repository/tree?ref={branch}&recursive=true", timeout=10)
|
||||
response.raise_for_status()
|
||||
return [
|
||||
{
|
||||
"path": item.get("path", ""),
|
||||
"name": item.get("name", ""),
|
||||
"type": item.get("type"),
|
||||
"size": 0,
|
||||
}
|
||||
for item in response.json()
|
||||
if item.get("type") in ("blob", "file")
|
||||
]
|
||||
print(f"Unsupported repository URL: {repo_url}")
|
||||
return []
|
||||
|
||||
def file_size(branch, path, fallback):
|
||||
if is_github:
|
||||
return int(fallback or 0)
|
||||
response = self.session.head(f"https://gitlab.com/api/v4/projects/{repo_encoded}/repository/files/{quote_plus(path)}/raw?ref={branch}", timeout=10)
|
||||
return int(response.headers.get("content-length", 0)) if response.ok else 0
|
||||
|
||||
for branch in ["Distance-Icons", "Steering-Wheels"]:
|
||||
for item in list_files(branch):
|
||||
if item.get("type") not in ("file", "blob"):
|
||||
continue
|
||||
|
||||
path = item["path"]
|
||||
size = file_size(branch, path, item.get("size", 0))
|
||||
|
||||
if branch == "Steering-Wheels":
|
||||
assets["wheels"].append(path)
|
||||
theme_name = Path(path).stem
|
||||
local_files = list((THEME_SAVE_PATH / "steering_wheels").glob(f"{theme_name}.*"))
|
||||
if local_files and size > 0:
|
||||
local_size = self.theme_sizes.get("wheels", {}).get(theme_name)
|
||||
if local_size != size:
|
||||
self.download_theme("steering_wheels", theme_name, THEME_COMPONENT_PARAMS["steering_wheels"], frogpilot_toggles)
|
||||
|
||||
elif branch == "Distance-Icons":
|
||||
component_name = "distance_icons"
|
||||
theme_name = Path(path).stem
|
||||
assets["themes"].setdefault(theme_name, set()).add(component_name)
|
||||
|
||||
local_path = THEME_SAVE_PATH / "theme_packs" / theme_name / component_name
|
||||
if local_path.exists() and size > 0:
|
||||
local_size = self.theme_sizes.get("themes", {}).get(theme_name, {}).get(component_name)
|
||||
if local_size != size:
|
||||
self.download_theme(component_name, theme_name, THEME_COMPONENT_PARAMS[component_name], frogpilot_toggles)
|
||||
|
||||
branch = "Themes"
|
||||
for item in list_files(branch):
|
||||
if item.get("type") not in ("file", "blob") or "/" not in item["path"]:
|
||||
continue
|
||||
|
||||
expected_size = file_size(branch, item["path"], item.get("size", 0))
|
||||
|
||||
theme_name, sub_path = item["path"].split("/", 1)
|
||||
theme_path = sub_path.lower()
|
||||
|
||||
for key in ("colors", "icons", "signals", "sounds"):
|
||||
if key in theme_path:
|
||||
assets["themes"].setdefault(theme_name, set()).add(key)
|
||||
|
||||
local_path = THEME_SAVE_PATH / "theme_packs" / theme_name / key
|
||||
if local_path.exists():
|
||||
local_size = self.theme_sizes.get("themes", {}).get(theme_name, {}).get(key)
|
||||
if local_size != expected_size:
|
||||
print(f"{key} {theme_name} is outdated, redownloading...")
|
||||
self.download_theme(key, theme_name, THEME_COMPONENT_PARAMS[key], frogpilot_toggles)
|
||||
break
|
||||
|
||||
assets["themes"] = {key: sorted(list(value)) for key, value in assets["themes"].items()}
|
||||
assets["wheels"].sort()
|
||||
return assets
|
||||
|
||||
except requests.exceptions.RequestException as error:
|
||||
print(f"Failed to fetch theme sizes from {'GitHub' if is_github else 'GitLab'}: {error}")
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def format_name(name, component):
|
||||
base = Path(name).stem
|
||||
creator = ""
|
||||
if "~" in base:
|
||||
base, creator = base.split("~", 1)
|
||||
|
||||
parts = base.replace("_", "-").split("-")
|
||||
capitalized_parts = [part.capitalize() for part in parts if part]
|
||||
|
||||
if len(capitalized_parts) > 1 and component != "steering_wheels":
|
||||
display = f"{capitalized_parts[0]} ({' '.join(capitalized_parts[1:])})"
|
||||
else:
|
||||
display = " ".join(capitalized_parts)
|
||||
|
||||
if creator:
|
||||
return f"{display} - by: {creator}"
|
||||
return display
|
||||
|
||||
@staticmethod
|
||||
def get_full_themes():
|
||||
theme_packs_path = THEME_SAVE_PATH / "theme_packs"
|
||||
if not theme_packs_path.exists():
|
||||
return []
|
||||
|
||||
valid_themes = set()
|
||||
for theme_directory in theme_packs_path.iterdir():
|
||||
if not theme_directory.is_dir():
|
||||
continue
|
||||
|
||||
base_name = theme_directory.name.replace("-animated", "")
|
||||
|
||||
animated_path = theme_packs_path / f"{base_name}-animated"
|
||||
base_path = theme_packs_path / base_name
|
||||
|
||||
base_valid = all((base_path / asset).is_dir() for asset in {"colors", "sounds"})
|
||||
animated_icons_exist = (animated_path / "icons").is_dir()
|
||||
base_icons_exist = (base_path / "icons").is_dir()
|
||||
|
||||
if base_valid and (animated_icons_exist or base_icons_exist):
|
||||
if animated_icons_exist:
|
||||
valid_themes.add(f"{base_name}-animated")
|
||||
else:
|
||||
valid_themes.add(base_name)
|
||||
|
||||
return sorted(valid_themes)
|
||||
|
||||
def handle_verification_failure(self, extension, theme_component, theme_name, asset_param, theme_path, download_path, frogpilot_toggles):
|
||||
if theme_component == "distance_icons":
|
||||
download_link = f"{GITLAB_URL}/Distance-Icons/{theme_name}"
|
||||
elif theme_component == "steering_wheels":
|
||||
download_link = f"{GITLAB_URL}/Steering-Wheels/{theme_name}"
|
||||
else:
|
||||
download_link = f"{GITLAB_URL}/Themes/{theme_name}/{theme_component}"
|
||||
|
||||
delete_file(theme_path)
|
||||
|
||||
theme_url = download_link + extension
|
||||
print(f"Downloading theme from GitLab: {theme_name}")
|
||||
download_file(CANCEL_DOWNLOAD_PARAM, theme_path, asset_param, self.params_memory, DOWNLOAD_PROGRESS_PARAM, self.session, theme_url)
|
||||
|
||||
if theme_component == "steering_wheels" and not theme_path.exists() and theme_path.with_suffix(".png").exists():
|
||||
theme_path = theme_path.with_suffix(".png")
|
||||
extension = ".png"
|
||||
theme_url = theme_url.replace(".gif", ".png")
|
||||
|
||||
if verify_download(theme_path, self.params_memory, self.session, theme_url):
|
||||
print(f"Theme {theme_name} downloaded and verified successfully from GitLab!")
|
||||
self.update_theme_size(theme_component, theme_name, theme_path.stat().st_size)
|
||||
|
||||
if extension == ".zip":
|
||||
self.params_memory.put(DOWNLOAD_PROGRESS_PARAM, "Unpacking theme...")
|
||||
extract_zip(theme_path, download_path)
|
||||
|
||||
self.params_memory.put(DOWNLOAD_PROGRESS_PARAM, "Downloaded!")
|
||||
self.params_memory.remove(asset_param)
|
||||
|
||||
self.downloading_theme = False
|
||||
|
||||
self.update_themes(frogpilot_toggles)
|
||||
return True
|
||||
|
||||
handle_error(None, asset_param, "Download failed...", "Download failed...", self.params_memory, DOWNLOAD_PROGRESS_PARAM)
|
||||
self.downloading_theme = False
|
||||
return False
|
||||
|
||||
def update_active_theme(self, frogpilot_toggles, boot_run=False):
|
||||
asset_mappings = {
|
||||
"color_scheme": ("colors", frogpilot_toggles.color_scheme),
|
||||
"distance_icons": ("distance_icons", frogpilot_toggles.distance_icons),
|
||||
"icon_pack": ("icons", frogpilot_toggles.icon_pack),
|
||||
"sound_pack": ("sounds", frogpilot_toggles.sound_pack),
|
||||
"turn_signal_pack": ("signals", frogpilot_toggles.signal_icons),
|
||||
"wheel_image": ("wheel_image", frogpilot_toggles.wheel_image)
|
||||
}
|
||||
|
||||
if asset_mappings != self.previous_asset_mappings:
|
||||
for asset, (asset_type, current_value) in asset_mappings.items():
|
||||
print(f"Updating {asset}: {asset_type} with value {current_value}")
|
||||
|
||||
if asset_type == "wheel_image":
|
||||
self.update_wheel_image(current_value, boot_run=boot_run)
|
||||
else:
|
||||
self.update_theme_asset(asset_type, current_value, boot_run=boot_run)
|
||||
|
||||
self.previous_asset_mappings = asset_mappings
|
||||
|
||||
self.theme_updated = True
|
||||
|
||||
def update_theme_asset(self, asset_type, theme, boot_run=False):
|
||||
save_location = ACTIVE_THEME_PATH / asset_type
|
||||
asset_location = THEME_SAVE_PATH / "theme_packs" / theme / asset_type
|
||||
|
||||
if not asset_location.exists() or theme == "stock":
|
||||
asset_location = STOCKOP_THEME_PATH / asset_type
|
||||
print(f"Using the stock {asset_type[:-1]} instead")
|
||||
|
||||
delete_file(save_location, print_error=not boot_run)
|
||||
|
||||
save_location.parent.mkdir(parents=True, exist_ok=True)
|
||||
save_location.symlink_to(asset_location, target_is_directory=True)
|
||||
print(f"Linked {save_location} to {asset_location}")
|
||||
|
||||
def update_theme_params(self, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels):
|
||||
def update_param(key, assets, subfolder):
|
||||
if subfolder == "steering_wheels":
|
||||
themes_path = THEME_SAVE_PATH / subfolder
|
||||
existing_assets = {self.format_name(item.name, "steering_wheels") for item in themes_path.glob("*") if item.is_file()}
|
||||
else:
|
||||
themes_path = THEME_SAVE_PATH / "theme_packs"
|
||||
existing_assets = {self.format_name(item.parent.name, subfolder) for item in themes_path.glob(f"*/{subfolder}") if item.is_dir()}
|
||||
|
||||
self.params.put(key, ",".join(sorted(set(assets) - existing_assets)))
|
||||
print(f"{key} updated successfully")
|
||||
|
||||
update_param("DownloadableColors", downloadable_colors, "colors")
|
||||
update_param("DownloadableDistanceIcons", downloadable_distance_icons, "distance_icons")
|
||||
update_param("DownloadableIcons", downloadable_icons, "icons")
|
||||
update_param("DownloadableSignals", downloadable_signals, "signals")
|
||||
update_param("DownloadableSounds", downloadable_sounds, "sounds")
|
||||
update_param("DownloadableWheels", downloadable_wheels, "steering_wheels")
|
||||
|
||||
downloaded_themes = {}
|
||||
for theme_dir in (THEME_SAVE_PATH / "theme_packs").iterdir():
|
||||
components = []
|
||||
for component in ["colors", "distance_icons", "icons", "signals", "sounds"]:
|
||||
if (theme_dir / component).is_dir():
|
||||
components.append(component)
|
||||
|
||||
if components:
|
||||
theme_name = self.format_name(theme_dir.name, "theme_packs")
|
||||
downloaded_themes[theme_name] = sorted(components)
|
||||
|
||||
downloaded_wheels = []
|
||||
for wheel_file in (THEME_SAVE_PATH / "steering_wheels").iterdir():
|
||||
if wheel_file.is_file():
|
||||
downloaded_wheels.append(self.format_name(wheel_file.name, "steering_wheels"))
|
||||
|
||||
self.params.put("ThemesDownloaded", {
|
||||
"themes": {key: downloaded_themes[key] for key in sorted(downloaded_themes)},
|
||||
"steering_wheels": sorted(downloaded_wheels)
|
||||
})
|
||||
|
||||
print("ThemesDownloaded updated successfully")
|
||||
|
||||
def update_theme_size(self, theme_component, theme_name, file_size):
|
||||
if theme_component == "steering_wheels":
|
||||
key = "wheels"
|
||||
else:
|
||||
key = "themes"
|
||||
|
||||
if key not in self.theme_sizes:
|
||||
self.theme_sizes[key] = {}
|
||||
|
||||
if key == "wheels":
|
||||
self.theme_sizes[key][theme_name] = file_size
|
||||
else:
|
||||
if theme_name not in self.theme_sizes[key]:
|
||||
self.theme_sizes[key][theme_name] = {}
|
||||
self.theme_sizes[key][theme_name][theme_component] = file_size
|
||||
|
||||
update_json_file(self.theme_sizes_path, self.theme_sizes)
|
||||
|
||||
def update_themes(self, frogpilot_toggles, boot_run=False):
|
||||
if self.downloading_theme:
|
||||
return
|
||||
|
||||
repo_url = get_repository_url(self.session)
|
||||
if repo_url is None:
|
||||
print("GitHub and GitLab are offline...")
|
||||
return
|
||||
|
||||
assets = self.fetch_assets(repo_url, frogpilot_toggles)
|
||||
if not assets:
|
||||
return
|
||||
|
||||
downloadable_colors = []
|
||||
downloadable_distance_icons = []
|
||||
downloadable_icons = []
|
||||
downloadable_signals = []
|
||||
downloadable_sounds = []
|
||||
|
||||
for theme, available_assets in assets["themes"].items():
|
||||
theme_name = self.format_name(theme, "theme_packs")
|
||||
print(f"Theme found: {theme_name}")
|
||||
|
||||
if "colors" in available_assets:
|
||||
downloadable_colors.append(theme_name)
|
||||
if "distance_icons" in available_assets:
|
||||
downloadable_distance_icons.append(theme_name)
|
||||
if "icons" in available_assets:
|
||||
downloadable_icons.append(theme_name)
|
||||
if "signals" in available_assets:
|
||||
downloadable_signals.append(theme_name)
|
||||
if "sounds" in available_assets:
|
||||
downloadable_sounds.append(theme_name)
|
||||
|
||||
downloadable_wheels = [self.format_name(wheel, "steering_wheels") for wheel in assets["wheels"]]
|
||||
|
||||
print(f"Downloadable Colors: {downloadable_colors}")
|
||||
print(f"Downloadable Icons: {downloadable_icons}")
|
||||
print(f"Downloadable Signals: {downloadable_signals}")
|
||||
print(f"Downloadable Sounds: {downloadable_sounds}")
|
||||
print(f"Downloadable Distance Icons: {downloadable_distance_icons}")
|
||||
print(f"Downloadable Wheels: {downloadable_wheels}")
|
||||
|
||||
if boot_run:
|
||||
self.validate_themes(downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels, frogpilot_toggles)
|
||||
|
||||
self.update_theme_params(downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels)
|
||||
|
||||
def update_wheel_image(self, image, boot_run=False):
|
||||
wheel_save_location = ACTIVE_THEME_PATH / "steering_wheel"
|
||||
|
||||
if image == "stock":
|
||||
wheel_location = STOCKOP_THEME_PATH / "steering_wheel"
|
||||
else:
|
||||
wheel_location = THEME_SAVE_PATH / "steering_wheels"
|
||||
|
||||
if not wheel_location.exists():
|
||||
wheel_location = STOCKOP_THEME_PATH / "steering_wheel"
|
||||
print("Using the stock steering wheel instead")
|
||||
|
||||
delete_file(wheel_save_location, print_error=not boot_run)
|
||||
wheel_save_location.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
image_name = image.replace(" ", "_").lower()
|
||||
matching_files = [images for images in wheel_location.iterdir() if images.stem.lower() in {image_name, "wheel"}]
|
||||
if matching_files:
|
||||
source_file = matching_files[0]
|
||||
destination_file = wheel_save_location / f"wheel{source_file.suffix}"
|
||||
destination_file.symlink_to(source_file)
|
||||
print(f"Linked {destination_file} to {source_file}")
|
||||
|
||||
def validate_themes(self, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels, frogpilot_toggles):
|
||||
downloaded_data = self.params.get("ThemesDownloaded")
|
||||
|
||||
for display_name, components in downloaded_data.get("themes", {}).items():
|
||||
raw_name = display_name.lower().replace(" ", "_").replace("(", "").replace(")", "")
|
||||
theme_folder_name = raw_name.replace("_animated", "-animated")
|
||||
|
||||
for component in components:
|
||||
component_path = THEME_SAVE_PATH / "theme_packs" / theme_folder_name / component
|
||||
if not component_path.is_dir() or not any(component_path.iterdir()):
|
||||
print(f"Missing or empty component '{component}' for theme '{theme_folder_name}'. Downloading...")
|
||||
self.download_theme(component, theme_folder_name, THEME_COMPONENT_PARAMS.get(component), frogpilot_toggles)
|
||||
self.update_active_theme(frogpilot_toggles)
|
||||
|
||||
wheels_path = THEME_SAVE_PATH / "steering_wheels"
|
||||
for display_name in downloaded_data.get("steering_wheels", []):
|
||||
file_stem = display_name.replace(" ", "_").lower()
|
||||
matching_files = list(wheels_path.glob(f"{file_stem}.*"))
|
||||
if not matching_files:
|
||||
print(f"Missing steering wheel '{display_name}'. Downloading...")
|
||||
self.download_theme("steering_wheels", file_stem, THEME_COMPONENT_PARAMS["steering_wheels"], frogpilot_toggles)
|
||||
self.update_active_theme(frogpilot_toggles)
|
||||
|
||||
for dir_path in THEME_SAVE_PATH.glob("**/*"):
|
||||
if dir_path.is_dir() and not any(dir_path.iterdir()):
|
||||
print(f"Deleting empty folder: {dir_path}")
|
||||
delete_file(dir_path)
|
||||
elif dir_path.is_file() and dir_path.name.startswith("tmp"):
|
||||
print(f"Deleting temp file: {dir_path}")
|
||||
delete_file(dir_path)
|
||||
|
||||
print("Theme validation complete.")
|
||||
@@ -11,11 +11,12 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
|
||||
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
|
||||
from openpilot.frogpilot.common.frogpilot_variables import (
|
||||
FROGS_GO_MOO_PATH,
|
||||
FrogPilotVariables
|
||||
FROGS_GO_MOO_PATH, THEME_SAVE_PATH,
|
||||
FrogPilotVariables, get_frogpilot_toggles
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +24,7 @@ def frogpilot_boot_functions(build_metadata, params):
|
||||
params_memory = Params(memory=True)
|
||||
|
||||
FrogPilotVariables()
|
||||
ThemeManager(params, params_memory, boot_run=True).update_active_theme(frogpilot_toggles=get_frogpilot_toggles(), boot_run=True)
|
||||
|
||||
def boot_thread():
|
||||
while not system_time_valid():
|
||||
@@ -36,6 +38,7 @@ def frogpilot_boot_functions(build_metadata, params):
|
||||
|
||||
def install_frogpilot(build_metadata, params):
|
||||
paths = [
|
||||
THEME_SAVE_PATH
|
||||
]
|
||||
for path in paths:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -148,8 +148,18 @@ class FrogPilotVariables:
|
||||
self.frogs_go_moo = FROGS_GO_MOO_PATH.is_file()
|
||||
toggle.block_user = (self.development_branch or branch == "MAKE-PRS-HERE" or self.vetting_branch) and not self.frogs_go_moo
|
||||
|
||||
stock_colors_json = (STOCK_THEME_PATH / "colors/colors.json")
|
||||
self.stock_colors = json.loads(stock_colors_json.read_text()) if stock_colors_json.is_file() else {}
|
||||
|
||||
self.update()
|
||||
|
||||
def get_color(self, key, theme_colors):
|
||||
for source in (theme_colors, self.stock_colors):
|
||||
color = source.get(key)
|
||||
if isinstance(color, dict):
|
||||
return f"#{color.get("alpha", 255):02X}{color.get("red", 255):02X}{color.get("green", 255):02X}{color.get("blue", 255):02X}"
|
||||
return "#FFFFFFFF"
|
||||
|
||||
def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None):
|
||||
if not condition or (self.tuning_level < self.tuning_levels.get(key, 0)):
|
||||
if default is not None:
|
||||
@@ -336,6 +346,22 @@ class FrogPilotVariables:
|
||||
toggle.traffic_mode_jerk_speed_decrease = [self.get_value("TrafficJerkSpeedDecrease", cast=float, condition=toggle.custom_personalities, conversion=0.01, min=0.25, max=2.0), toggle.aggressive_jerk_speed_decrease]
|
||||
toggle.traffic_mode_follow = [self.get_value("TrafficFollow", cast=float, condition=toggle.custom_personalities, min=0.5, max=MAX_T_FOLLOW), toggle.aggressive_follow]
|
||||
|
||||
custom_themes = self.get_value("CustomThemes")
|
||||
toggle.color_scheme = self.get_value("ColorScheme", cast=None, condition=custom_themes, default="stock")
|
||||
theme_colors = json.loads(THEME_COLORS_PATH.read_text()) if THEME_COLORS_PATH.is_file() else {}
|
||||
toggle.lane_lines_color = self.get_color("LaneLines", theme_colors)
|
||||
toggle.lead_marker_color = self.get_color("LeadMarker", theme_colors)
|
||||
toggle.path_color = self.get_color("Path", theme_colors)
|
||||
toggle.path_edges_color = self.get_color("PathEdge", theme_colors)
|
||||
toggle.sidebar_color1 = self.get_color("Sidebar1", theme_colors)
|
||||
toggle.sidebar_color2 = self.get_color("Sidebar2", theme_colors)
|
||||
toggle.sidebar_color3 = self.get_color("Sidebar3", theme_colors)
|
||||
toggle.distance_icons = self.get_value("DistanceIconPack", cast=None, condition=custom_themes, default="stock")
|
||||
toggle.icon_pack = self.get_value("IconPack", cast=None, condition=custom_themes, default="stock")
|
||||
toggle.signal_icons = self.get_value("SignalAnimation", cast=None, condition=custom_themes, default="stock")
|
||||
toggle.sound_pack = self.get_value("SoundPack", cast=None, condition=custom_themes, default="stock")
|
||||
toggle.wheel_image = self.get_value("WheelIcon", cast=None, condition=custom_themes, default="stock")
|
||||
|
||||
custom_ui = self.get_value("CustomUI")
|
||||
toggle.acceleration_path = toggle.openpilot_longitudinal and (self.get_value("AccelerationPath", condition=custom_ui))
|
||||
toggle.adjacent_paths = self.get_value("AdjacentPath", condition=custom_ui)
|
||||
|
||||
@@ -106,7 +106,7 @@ class FrogPilotPlanner:
|
||||
self.tracking_lead_filter.update(following_lead)
|
||||
return self.tracking_lead_filter.x >= THRESHOLD
|
||||
|
||||
def publish(self, sm, pm, frogpilot_toggles):
|
||||
def publish(self, theme_updated, sm, pm, frogpilot_toggles):
|
||||
frogpilot_plan_send = messaging.new_message("frogpilotPlan")
|
||||
frogpilot_plan_send.valid = sm.all_checks(service_list=["carState", "controlsState", "selfdriveState", "radarState"])
|
||||
frogpilotPlan = frogpilot_plan_send.frogpilotPlan
|
||||
@@ -136,6 +136,8 @@ class FrogPilotPlanner:
|
||||
|
||||
frogpilotPlan.roadCurvature = self.road_curvature
|
||||
|
||||
frogpilotPlan.themeUpdated = theme_updated
|
||||
|
||||
frogpilotPlan.vCruise = float(self.v_cruise)
|
||||
|
||||
pm.send("frogpilotPlan", frogpilot_plan_send)
|
||||
|
||||
@@ -8,6 +8,7 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL, Priority, Ratekeeper, config_realtime_process
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
|
||||
from openpilot.frogpilot.assets.theme_manager import THEME_COMPONENT_PARAMS, ThemeManager
|
||||
from openpilot.frogpilot.common.frogpilot_backups import backup_toggles
|
||||
from openpilot.frogpilot.common.frogpilot_functions import update_openpilot
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import ThreadManager, is_url_pingable
|
||||
@@ -18,7 +19,11 @@ from openpilot.frogpilot.system.frogpilot_tracking import FrogPilotTracking
|
||||
|
||||
ASSET_CHECK_RATE = (1 / DT_MDL)
|
||||
|
||||
def check_assets(thread_manager, params_memory, frogpilot_toggles):
|
||||
def check_assets(theme_manager, thread_manager, 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:
|
||||
thread_manager.run_with_lock(theme_manager.download_theme, (asset_type, asset_to_download, asset_param, frogpilot_toggles))
|
||||
|
||||
def transition_offroad(frogpilot_planner, thread_manager, time_validated, sm, params, frogpilot_toggles):
|
||||
params.put("LastGPSPosition", json.dumps(frogpilot_planner.gps_position))
|
||||
@@ -28,19 +33,23 @@ def transition_offroad(frogpilot_planner, thread_manager, time_validated, sm, pa
|
||||
|
||||
def transition_onroad():
|
||||
|
||||
def update_checks(now, thread_manager, params, params_memory, frogpilot_toggles, boot_run=False):
|
||||
def update_checks(now, theme_manager, thread_manager, params, params_memory, frogpilot_toggles, boot_run=False):
|
||||
while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")):
|
||||
time.sleep(60)
|
||||
|
||||
theme_manager.update_themes(frogpilot_toggles, boot_run)
|
||||
|
||||
if frogpilot_toggles.automatic_updates:
|
||||
thread_manager.run_with_lock(update_openpilot, (thread_manager, params))
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def update_toggles(frogpilot_variables, started, thread_manager, time_validated, params):
|
||||
def update_toggles(frogpilot_variables, started, theme_manager, thread_manager, time_validated, params):
|
||||
frogpilot_variables.update(started)
|
||||
frogpilot_toggles = frogpilot_variables.frogpilot_toggles
|
||||
|
||||
theme_manager.update_active_theme(frogpilot_toggles)
|
||||
|
||||
if time_validated:
|
||||
thread_manager.run_with_lock(backup_toggles, (params))
|
||||
|
||||
@@ -62,6 +71,7 @@ def frogpilot_thread():
|
||||
params_memory = Params(memory=True)
|
||||
|
||||
frogpilot_variables = FrogPilotVariables()
|
||||
theme_manager = ThemeManager(params, params_memory)
|
||||
thread_manager = ThreadManager()
|
||||
|
||||
frogpilot_toggles = frogpilot_variables.frogpilot_toggles
|
||||
@@ -78,7 +88,7 @@ def frogpilot_thread():
|
||||
started = sm["deviceState"].started
|
||||
|
||||
if not started and started_previously:
|
||||
frogpilot_toggles = update_toggles(frogpilot_variables, started, thread_manager, time_validated, params)
|
||||
frogpilot_toggles = update_toggles(frogpilot_variables, started, theme_manager, thread_manager, time_validated, params)
|
||||
transition_offroad(frogpilot_planner, thread_manager, time_validated, sm, params, frogpilot_toggles)
|
||||
|
||||
run_update_checks = True
|
||||
@@ -90,28 +100,29 @@ def frogpilot_thread():
|
||||
|
||||
if started and sm.updated["modelV2"]:
|
||||
frogpilot_planner.update(now, time_validated, sm, frogpilot_toggles)
|
||||
frogpilot_planner.publish(sm, pm, frogpilot_toggles)
|
||||
frogpilot_planner.publish(theme_manager.theme_updated, sm, pm, frogpilot_toggles)
|
||||
|
||||
frogpilot_tracking.update(now, time_validated, sm, frogpilot_toggles)
|
||||
elif not started:
|
||||
frogpilot_plan_send = messaging.new_message("frogpilotPlan")
|
||||
frogpilot_plan_send.frogpilotPlan.frogpilotToggles = json.dumps(vars(frogpilot_toggles))
|
||||
frogpilot_plan_send.frogpilotPlan.themeUpdated = theme_manager.theme_updated
|
||||
pm.send("frogpilotPlan", frogpilot_plan_send)
|
||||
|
||||
started_previously = started
|
||||
|
||||
if rate_keeper.frame % ASSET_CHECK_RATE == 0:
|
||||
check_assets(thread_manager, params_memory, frogpilot_toggles)
|
||||
check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles)
|
||||
|
||||
if params_memory.get_bool("FrogPilotTogglesUpdated"):
|
||||
frogpilot_toggles = update_toggles(frogpilot_variables, started, thread_manager, time_validated, params)
|
||||
frogpilot_toggles = update_toggles(frogpilot_variables, started, theme_manager, thread_manager, time_validated, params)
|
||||
|
||||
run_update_checks |= params_memory.get_bool("ManualUpdateInitiated")
|
||||
run_update_checks |= now.second == 0 and (now.minute % 60 == 0 or (now.minute % 5 == 0 and frogpilot_variables.frogs_go_moo))
|
||||
run_update_checks &= time_validated
|
||||
|
||||
if run_update_checks:
|
||||
thread_manager.run_with_lock(update_checks, (now, thread_manager, params, params_memory, frogpilot_toggles))
|
||||
thread_manager.run_with_lock(update_checks, (now, theme_manager, thread_manager, params, params_memory, frogpilot_toggles))
|
||||
|
||||
run_update_checks = False
|
||||
elif not time_validated:
|
||||
@@ -121,7 +132,7 @@ def frogpilot_thread():
|
||||
|
||||
thread_manager.run_with_lock(backup_toggles, (params, True))
|
||||
thread_manager.run_with_lock(send_stats, (params, frogpilot_toggles))
|
||||
thread_manager.run_with_lock(update_checks, (now, thread_manager, params, params_memory, frogpilot_toggles, True))
|
||||
thread_manager.run_with_lock(update_checks, (now, theme_manager, thread_manager, params, params_memory, frogpilot_toggles, True))
|
||||
|
||||
rate_keeper.keep_time()
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ static void update_state(FrogPilotUIState *fs) {
|
||||
}
|
||||
if (fpsm.updated("frogpilotPlan")) {
|
||||
const cereal::FrogPilotPlan::Reader &frogpilotPlan = fpsm["frogpilotPlan"].getFrogpilotPlan();
|
||||
if (frogpilotPlan.getThemeUpdated()) {
|
||||
emit fs->themeUpdated();
|
||||
}
|
||||
capnp::Text::Reader toggles = frogpilotPlan.getFrogpilotToggles();
|
||||
QByteArray current_toggles(toggles.cStr(), toggles.size());
|
||||
static QByteArray previous_toggles;
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
WifiManager *wifi;
|
||||
|
||||
signals:
|
||||
void themeUpdated();
|
||||
};
|
||||
|
||||
FrogPilotUIState *frogpilotUIState();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "frogpilot/ui/qt/onroad/frogpilot_annotated_camera.h"
|
||||
|
||||
FrogPilotAnnotatedCameraWidget::FrogPilotAnnotatedCameraWidget(QWidget *parent) : QWidget(parent) {
|
||||
animationTimer = new QTimer(this);
|
||||
|
||||
QSize iconSize(img_size / 4, img_size / 4);
|
||||
|
||||
curveSpeedIcon = loadPixmap("../../frogpilot/assets/other_images/curve_speed.png", {btn_size, btn_size});
|
||||
@@ -13,9 +15,96 @@ FrogPilotAnnotatedCameraWidget::FrogPilotAnnotatedCameraWidget(QWidget *parent)
|
||||
loadGif("../../frogpilot/assets/other_images/turn_icon.gif", cemTurnIcon, QSize(widget_size, widget_size), this);
|
||||
loadGif("../../frogpilot/assets/other_images/chill_mode_icon.gif", chillModeIcon, QSize(widget_size, widget_size), this);
|
||||
loadGif("../../frogpilot/assets/other_images/experimental_mode_icon.gif", experimentalModeIcon, QSize(widget_size, widget_size), this);
|
||||
|
||||
QObject::connect(animationTimer, &QTimer::timeout, [this] {
|
||||
animationFrameIndex = (animationFrameIndex + 1) % totalFrames;
|
||||
});
|
||||
QObject::connect(frogpilotUIState(), &FrogPilotUIState::themeUpdated, this, &FrogPilotAnnotatedCameraWidget::updateSignals);
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, [this] {
|
||||
QJsonObject stats = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object();
|
||||
stats["FrogHops"] = stats.value("FrogHops").toInt(0) + frogHopCount;
|
||||
params.putNonBlocking("FrogPilotStats", QJsonDocument(stats).toJson(QJsonDocument::Compact).toStdString());
|
||||
|
||||
frogHopCount = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void FrogPilotAnnotatedCameraWidget::showEvent(QShowEvent *event) {
|
||||
updateSignals();
|
||||
}
|
||||
|
||||
void FrogPilotAnnotatedCameraWidget::updateSignals() {
|
||||
QVector<QPixmap>().swap(blindspotImages);
|
||||
QVector<QPixmap>().swap(blindspotImagesRight);
|
||||
QVector<QPixmap>().swap(signalImages);
|
||||
QVector<QPixmap>().swap(signalImagesRight);
|
||||
|
||||
bool isGif = false;
|
||||
|
||||
QFileInfoList files = QDir("../../frogpilot/assets/active_theme/signals/").entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
|
||||
for (const QFileInfo &fileInfo : files) {
|
||||
QString fileName = fileInfo.fileName();
|
||||
QString filePath = fileInfo.absoluteFilePath();
|
||||
|
||||
if (fileName.endsWith(".gif", Qt::CaseInsensitive)) {
|
||||
isGif = true;
|
||||
|
||||
QMovie movie(filePath);
|
||||
movie.setCacheMode(QMovie::CacheNone);
|
||||
movie.start();
|
||||
|
||||
int frameCount = movie.frameCount();
|
||||
signalImages.reserve(frameCount);
|
||||
signalImagesRight.reserve(frameCount);
|
||||
|
||||
for (int i = 0; i < frameCount; ++i) {
|
||||
movie.jumpToFrame(i);
|
||||
|
||||
QPixmap frame = movie.currentPixmap();
|
||||
signalImages.append(frame);
|
||||
signalImagesRight.append(frame.transformed(QTransform().scale(-1, 1)));
|
||||
}
|
||||
|
||||
movie.stop();
|
||||
} else if (fileName.endsWith(".png", Qt::CaseInsensitive)) {
|
||||
QPixmap img(filePath);
|
||||
if (fileName.contains("blindspot", Qt::CaseInsensitive)) {
|
||||
blindspotImages.append(img);
|
||||
blindspotImagesRight.append(img.transformed(QTransform().scale(-1, 1)));
|
||||
} else {
|
||||
signalImages.append(img);
|
||||
signalImagesRight.append(img.transformed(QTransform().scale(-1, 1)));
|
||||
}
|
||||
} else {
|
||||
QStringList parts = fileName.split('_');
|
||||
if (parts.size() == 2) {
|
||||
signalStyle = parts[0];
|
||||
signalAnimationLength = parts[1].toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!signalImages.isEmpty()) {
|
||||
QPixmap &firstImage = signalImages.front();
|
||||
signalHeight = firstImage.height();
|
||||
signalWidth = firstImage.width();
|
||||
totalFrames = signalImages.size();
|
||||
|
||||
if (isGif && signalStyle == "traditional") {
|
||||
signalMovement = (width() + signalWidth * 2) / totalFrames;
|
||||
signalStyle = "traditional_gif";
|
||||
} else {
|
||||
signalMovement = 0;
|
||||
}
|
||||
} else {
|
||||
signalAnimationLength = 0;
|
||||
signalHeight = 0;
|
||||
signalMovement = 0;
|
||||
signalWidth = 0;
|
||||
totalFrames = 0;
|
||||
|
||||
signalStyle = "None";
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPilotUIState &fs) {
|
||||
@@ -51,6 +140,8 @@ void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPil
|
||||
|
||||
blindspotLeft = carState.getLeftBlindspot();
|
||||
blindspotRight = carState.getRightBlindspot();
|
||||
blinkerLeft = carState.getLeftBlinker();
|
||||
blinkerRight = carState.getRightBlinker();
|
||||
cscControllingSpeed = frogpilotPlan.getCscControllingSpeed();
|
||||
cscSpeed = frogpilotPlan.getCscSpeed();
|
||||
cscTraining = frogpilotPlan.getCscTraining();
|
||||
@@ -59,6 +150,22 @@ void FrogPilotAnnotatedCameraWidget::updateState(const UIState &s, const FrogPil
|
||||
|
||||
hideBottomIcons = selfdriveState.getAlertSize() != cereal::SelfdriveState::AlertSize::NONE;
|
||||
hideBottomIcons |= frogpilotSelfdriveState.getAlertSize() != cereal::FrogPilotSelfdriveState::AlertSize::NONE;
|
||||
hideBottomIcons |= signalStyle.startsWith("traditional") && (blinkerLeft || blinkerRight);
|
||||
|
||||
static int lastFrameIndex;
|
||||
if (lastFrameIndex > animationFrameIndex && frogpilot_toggles.value("signal_icons").toString() == "frog") {
|
||||
frogHopCount++;
|
||||
}
|
||||
lastFrameIndex = animationFrameIndex;
|
||||
|
||||
if ((blinkerLeft || blinkerRight) && signalStyle != "None") {
|
||||
if (!animationTimer->isActive()) {
|
||||
animationTimer->start(signalAnimationLength);
|
||||
}
|
||||
} else if (animationTimer->isActive()) {
|
||||
animationFrameIndex = 0;
|
||||
animationTimer->stop();
|
||||
}
|
||||
|
||||
if (cscTraining) {
|
||||
if (!glowTimer.isValid()) {
|
||||
@@ -95,6 +202,10 @@ void FrogPilotAnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &p, UIState
|
||||
paintCurveSpeedControl(p);
|
||||
}
|
||||
}
|
||||
|
||||
if ((blinkerLeft || blinkerRight) && signalStyle != "None") {
|
||||
paintTurnSignals(p);
|
||||
}
|
||||
}
|
||||
|
||||
void FrogPilotAnnotatedCameraWidget::paintBlindSpotPath(QPainter &p) {
|
||||
@@ -306,3 +417,32 @@ void FrogPilotAnnotatedCameraWidget::paintCurveSpeedControlTraining(QPainter &p)
|
||||
|
||||
p.restore();
|
||||
}
|
||||
|
||||
void FrogPilotAnnotatedCameraWidget::paintTurnSignals(QPainter &p) {
|
||||
int frameIndex = qBound(0, animationFrameIndex, totalFrames - 1);
|
||||
|
||||
bool blindspotActive = blinkerLeft ? blindspotLeft : blindspotRight;
|
||||
|
||||
int signalXPosition = 0;
|
||||
int signalYPosition = 0;
|
||||
|
||||
if (signalStyle == "static") {
|
||||
signalXPosition = blinkerLeft ? (rect().center().x() * 0.75) - signalWidth : rect().center().x() * 1.25;
|
||||
signalYPosition = signalHeight / 2;
|
||||
} else {
|
||||
if (signalStyle == "traditional_gif") {
|
||||
signalXPosition = blinkerLeft ? width() - (frameIndex * signalMovement) + signalWidth : (frameIndex * signalMovement) - signalWidth;
|
||||
} else {
|
||||
signalXPosition = blinkerLeft ? width() - ((frameIndex + 1) * signalWidth) : frameIndex * signalWidth;
|
||||
}
|
||||
signalYPosition = height() - signalHeight - alertHeight;
|
||||
}
|
||||
|
||||
if (blinkerLeft) {
|
||||
QPixmap &imgToDraw = (blindspotActive && !blindspotImages.empty()) ? blindspotImages[0] : signalImages[frameIndex];
|
||||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, imgToDraw);
|
||||
} else {
|
||||
QPixmap &imgToDraw = (blindspotActive && !blindspotImagesRight.empty()) ? blindspotImagesRight[0] : signalImagesRight[frameIndex];
|
||||
p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, imgToDraw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
bool isCruiseSet;
|
||||
bool rightHandDM;
|
||||
|
||||
int alertHeight;
|
||||
|
||||
float speed;
|
||||
|
||||
FrogPilotUIScene frogpilot_scene;
|
||||
@@ -37,6 +39,8 @@ public:
|
||||
|
||||
QSize defaultSize;
|
||||
|
||||
QString signalStyle;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
@@ -45,13 +49,25 @@ private:
|
||||
void paintCompass(QPainter &p);
|
||||
void paintCurveSpeedControl(QPainter &p);
|
||||
void paintCurveSpeedControlTraining(QPainter &p);
|
||||
void paintTurnSignals(QPainter &p);
|
||||
void updateSignals();
|
||||
|
||||
bool blindspotLeft;
|
||||
bool blindspotRight;
|
||||
bool blinkerLeft;
|
||||
bool blinkerRight;
|
||||
bool cscControllingSpeed;
|
||||
bool cscTraining;
|
||||
bool experimentalMode;
|
||||
|
||||
int animationFrameIndex;
|
||||
int frogHopCount;
|
||||
int signalAnimationLength;
|
||||
int signalHeight;
|
||||
int signalMovement;
|
||||
int signalWidth;
|
||||
int totalFrames;
|
||||
|
||||
float cscSpeed;
|
||||
float distanceConversion;
|
||||
float roadCurvature;
|
||||
@@ -84,4 +100,11 @@ private:
|
||||
QString leadDistanceUnit;
|
||||
QString leadSpeedUnit;
|
||||
QString speedUnit;
|
||||
|
||||
QTimer *animationTimer;
|
||||
|
||||
QVector<QPixmap> blindspotImages;
|
||||
QVector<QPixmap> blindspotImagesRight;
|
||||
QVector<QPixmap> signalImages;
|
||||
QVector<QPixmap> signalImagesRight;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals
|
||||
QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode);
|
||||
|
||||
// FrogPilot variables
|
||||
QObject::connect(frogpilotUIState(), &FrogPilotUIState::themeUpdated, this, &ExperimentalButton::updateTheme);
|
||||
}
|
||||
|
||||
void ExperimentalButton::changeMode() {
|
||||
@@ -57,12 +58,22 @@ void ExperimentalButton::updateState(const UIState &s, const FrogPilotUIState &f
|
||||
|
||||
void ExperimentalButton::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
QPixmap img = experimental_mode ? experimental_img : engage_img;
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0);
|
||||
p.setClipRegion(QRegion(QRect(0, 0, btn_size, btn_size), QRegion::Ellipse));
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
if (frogpilot_toggles.value("wheel_image").toString() == "stock") {
|
||||
QPixmap img = experimental_mode ? experimental_img : engage_img;
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0);
|
||||
} else if (wheel_gif) {
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), wheel_gif->currentPixmap(), background_color, (isDown() || !engageable) ? 0.6 : 1.0);
|
||||
} else if (!wheel_img.isNull()) {
|
||||
drawIcon(p, QPoint(btn_size / 2, btn_size / 2), wheel_img, background_color, (isDown() || !engageable) ? 0.6 : 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// FrogPilot variables
|
||||
void ExperimentalButton::showEvent(QShowEvent *event) {
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
void ExperimentalButton::updateBackgroundColor() {
|
||||
@@ -78,3 +89,7 @@ void ExperimentalButton::updateBackgroundColor() {
|
||||
background_color = QColor(0, 0, 0, 166);
|
||||
}
|
||||
}
|
||||
|
||||
void ExperimentalButton::updateTheme() {
|
||||
loadImage("../../frogpilot/assets/active_theme/steering_wheel/wheel", wheel_img, wheel_gif, QSize(img_size, img_size), this);
|
||||
}
|
||||
|
||||
@@ -32,10 +32,15 @@ private:
|
||||
// FrogPilot variables
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void updateBackgroundColor();
|
||||
void updateTheme();
|
||||
|
||||
Params params_memory{"", true};
|
||||
|
||||
QColor background_color;
|
||||
|
||||
QPixmap wheel_img;
|
||||
|
||||
QSharedPointer<QMovie> wheel_gif;
|
||||
};
|
||||
|
||||
void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrush &bg, float opacity);
|
||||
|
||||
@@ -41,11 +41,11 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
update_leads(radar_state, model.getPosition());
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
if (lead_one.getStatus()) {
|
||||
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
|
||||
drawLead(painter, lead_one, lead_vertices[0], surface_rect, QColor(frogpilot_toggles.value("lead_marker_color").toString()));
|
||||
} else {
|
||||
// FrogPilot variables
|
||||
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect, QColor(frogpilot_toggles.value("lead_marker_color").toString()));
|
||||
}
|
||||
|
||||
// FrogPilot variables
|
||||
@@ -120,7 +120,13 @@ void ModelRenderer::update_model(const cereal::ModelDataV2::Reader &model, const
|
||||
void ModelRenderer::drawLaneLines(QPainter &painter) {
|
||||
// lanelines
|
||||
for (int i = 0; i < std::size(lane_line_vertices); ++i) {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(lane_line_probs[i], 0.0, 0.7)));
|
||||
if (frogpilot_toggles.value("color_scheme").toString() != "stock") {
|
||||
painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp<float>(lane_line_probs[i], 0.0, 0.7)));
|
||||
} else {
|
||||
QColor lane_color = QColor(frogpilot_toggles.value("lane_lines_color").toString());
|
||||
lane_color.setAlphaF(lane_color.alphaF() * std::clamp<float>(lane_line_probs[i], 0.0, 0.7));
|
||||
painter.setBrush(lane_color);
|
||||
}
|
||||
painter.drawPolygon(lane_line_vertices[i]);
|
||||
}
|
||||
|
||||
@@ -146,18 +152,24 @@ void ModelRenderer::drawPath(QPainter &painter, const cereal::ModelDataV2::Reade
|
||||
// Flip so 0 is bottom of frame
|
||||
float lin_grad_point = (height - track_vertices[track_idx].y()) / height;
|
||||
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
if (fabs(acceleration[i]) < 0.25 && frogpilot_toggles.value("color_scheme").toString() != "stock") {
|
||||
QColor color = QColor(frogpilot_toggles.value("path_color").toString());
|
||||
color.setAlphaF(util::map_val(lin_grad_point, 0.0f, 1.0f, 1.0f, 0.1f));
|
||||
bg.setColorAt(lin_grad_point, color);
|
||||
} else {
|
||||
// speed up: 120, slow down: 0
|
||||
float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0);
|
||||
// FIXME: painter.drawPolygon can be slow if hue is not rounded
|
||||
path_hue = int(path_hue * 100 + 0.5) / 100;
|
||||
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
float saturation = fmin(fabs(acceleration[i] * 1.5), 1);
|
||||
float lightness = util::map_val(saturation, 0.0f, 1.0f, 0.95f, 0.62f); // lighter when grey
|
||||
float alpha = util::map_val(lin_grad_point, 0.75f / 2.f, 0.75f, 0.4f, 0.0f); // matches previous alpha fade
|
||||
bg.setColorAt(lin_grad_point, QColor::fromHslF(path_hue / 360., saturation, lightness, alpha));
|
||||
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
// Skip a point, unless next is last
|
||||
i += (i + 2) < max_len ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -42,6 +42,7 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(
|
||||
pm = std::make_unique<PubMaster>(std::vector<const char*>{"bookmarkButton"});
|
||||
|
||||
// FrogPilot variables
|
||||
QObject::connect(frogpilotUIState(), &FrogPilotUIState::themeUpdated, this, &Sidebar::updateTheme);
|
||||
}
|
||||
|
||||
void Sidebar::mousePressEvent(QMouseEvent *event) {
|
||||
@@ -109,7 +110,7 @@ void Sidebar::updateState(const UIState &s, const FrogPilotUIState &fs) {
|
||||
connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color};
|
||||
} else {
|
||||
connectStatus = nanos_since_boot() - last_ping < 80e9
|
||||
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, good_color}
|
||||
? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, QColor(frogpilot_toggles.value("sidebar_color3").toString())}
|
||||
: ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color};
|
||||
}
|
||||
setProperty("connectStatus", QVariant::fromValue(connectStatus));
|
||||
@@ -117,13 +118,13 @@ void Sidebar::updateState(const UIState &s, const FrogPilotUIState &fs) {
|
||||
ItemStatus tempStatus = {{tr("TEMP"), tr("HIGH")}, danger_color};
|
||||
auto ts = deviceState.getThermalStatus();
|
||||
if (ts == cereal::DeviceState::ThermalStatus::GREEN) {
|
||||
tempStatus = {{tr("TEMP"), tr("GOOD")}, good_color};
|
||||
tempStatus = {{tr("TEMP"), tr("GOOD")}, QColor(frogpilot_toggles.value("sidebar_color1").toString())};
|
||||
} else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) {
|
||||
tempStatus = {{tr("TEMP"), tr("OK")}, warning_color};
|
||||
}
|
||||
setProperty("tempStatus", QVariant::fromValue(tempStatus));
|
||||
|
||||
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, good_color};
|
||||
ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, QColor(frogpilot_toggles.value("sidebar_color2").toString())};
|
||||
if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) {
|
||||
pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
|
||||
}
|
||||
@@ -143,9 +144,9 @@ void Sidebar::paintEvent(QPaintEvent *event) {
|
||||
|
||||
// buttons
|
||||
p.setOpacity(settings_pressed ? 0.65 : 1.0);
|
||||
p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img);
|
||||
p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_gif ? settings_gif->currentPixmap() : settings_img);
|
||||
p.setOpacity(onroad && flag_pressed ? 0.65 : 1.0);
|
||||
p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_img : home_img);
|
||||
p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_gif ? flag_gif->currentPixmap() : flag_img : home_gif ? home_gif->currentPixmap() : home_img);
|
||||
if (recording_audio) {
|
||||
p.setBrush(danger_color);
|
||||
p.setOpacity(mic_indicator_pressed ? 0.65 : 1.0);
|
||||
@@ -188,4 +189,11 @@ void Sidebar::paintEvent(QPaintEvent *event) {
|
||||
|
||||
// FrogPilot variables
|
||||
void Sidebar::showEvent(QShowEvent *event) {
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
void Sidebar::updateTheme() {
|
||||
loadImage("../../frogpilot/assets/active_theme/icons/button_home", home_img, home_gif, home_btn.size(), this);
|
||||
loadImage("../../frogpilot/assets/active_theme/icons/button_flag", flag_img, flag_gif, home_btn.size(), this);
|
||||
loadImage("../../frogpilot/assets/active_theme/icons/button_settings", settings_img, settings_gif, settings_btn.size(), this);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ private:
|
||||
|
||||
// FrogPilot variables
|
||||
void showEvent(QShowEvent *event);
|
||||
void updateTheme();
|
||||
|
||||
Params params;
|
||||
|
||||
QSharedPointer<QMovie> flag_gif;
|
||||
QSharedPointer<QMovie> home_gif;
|
||||
QSharedPointer<QMovie> settings_gif;
|
||||
};
|
||||
|
||||
+42
-11
@@ -3,6 +3,7 @@ import numpy as np
|
||||
import time
|
||||
import wave
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import car, custom, messaging
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
@@ -15,7 +16,7 @@ from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.system import micd
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
|
||||
from openpilot.frogpilot.common.frogpilot_variables import ACTIVE_THEME_PATH, get_frogpilot_toggles
|
||||
|
||||
SAMPLE_RATE = 48000
|
||||
SAMPLE_BUFFER = 4096 # (approx 100ms)
|
||||
@@ -70,8 +71,6 @@ def check_selfdrive_timeout_alert(sm):
|
||||
|
||||
class Soundd:
|
||||
def __init__(self):
|
||||
self.load_sounds()
|
||||
|
||||
self.current_alert = AudibleAlert.none
|
||||
self.current_volume = MIN_VOLUME
|
||||
self.current_sound_frame = 0
|
||||
@@ -87,6 +86,8 @@ class Soundd:
|
||||
|
||||
self.auto_volume = 0
|
||||
|
||||
self.previous_sound_pack = None
|
||||
|
||||
self.update_frogpilot_sounds()
|
||||
|
||||
def load_sounds(self):
|
||||
@@ -96,13 +97,26 @@ class Soundd:
|
||||
for sound in sound_list:
|
||||
filename, play_count, volume = sound_list[sound]
|
||||
|
||||
with wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') as wavefile:
|
||||
assert wavefile.getnchannels() == 1
|
||||
assert wavefile.getsampwidth() == 2
|
||||
assert wavefile.getframerate() == SAMPLE_RATE
|
||||
sounds_path = self.sound_directory / filename
|
||||
|
||||
length = wavefile.getnframes()
|
||||
self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2)
|
||||
if not sounds_path.exists() and "_tizi" in filename:
|
||||
standard_path = self.sound_directory / filename.replace("_tizi", "")
|
||||
if standard_path.exists():
|
||||
sounds_path = standard_path
|
||||
|
||||
if sounds_path.exists():
|
||||
wavefile = wave.open(str(sounds_path), 'r')
|
||||
else:
|
||||
if filename == "startup.wav":
|
||||
filename = "engage.wav"
|
||||
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
|
||||
|
||||
assert wavefile.getnchannels() == 1
|
||||
assert wavefile.getsampwidth() == 2
|
||||
assert wavefile.getframerate() == SAMPLE_RATE
|
||||
|
||||
length = wavefile.getnframes()
|
||||
self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2)
|
||||
|
||||
def get_sound_data(self, frames): # get "frames" worth of data from the current alert sound, looping when required
|
||||
|
||||
@@ -207,9 +221,9 @@ class Soundd:
|
||||
if frogpilot_toggles != self.frogpilot_toggles:
|
||||
self.frogpilot_toggles = frogpilot_toggles
|
||||
|
||||
self.update_frogpilot_sounds()
|
||||
stream = self.update_frogpilot_sounds(sd, stream)
|
||||
|
||||
def update_frogpilot_sounds(self):
|
||||
def update_frogpilot_sounds(self, sd=None, stream=None):
|
||||
self.volume_map = {
|
||||
AudibleAlert.engage: self.frogpilot_toggles.engage_volume / 100.0,
|
||||
AudibleAlert.disengage: self.frogpilot_toggles.disengage_volume / 100.0,
|
||||
@@ -230,6 +244,23 @@ class Soundd:
|
||||
if sound not in self.volume_map:
|
||||
self.volume_map[sound] = 1.01
|
||||
|
||||
if self.frogpilot_toggles.sound_pack != "stock":
|
||||
self.sound_directory = ACTIVE_THEME_PATH / "sounds"
|
||||
else:
|
||||
self.sound_directory = Path(BASEDIR) / "selfdrive" / "assets" / "sounds"
|
||||
|
||||
if self.frogpilot_toggles.sound_pack != self.previous_sound_pack:
|
||||
self.load_sounds()
|
||||
|
||||
self.previous_sound_pack = self.frogpilot_toggles.sound_pack
|
||||
|
||||
if stream is not None:
|
||||
stream.close()
|
||||
stream = self.get_stream(sd)
|
||||
stream.start()
|
||||
|
||||
return stream
|
||||
|
||||
|
||||
def main():
|
||||
s = Soundd()
|
||||
|
||||
Reference in New Issue
Block a user