Custom Themes

This commit is contained in:
James
2025-12-01 12:00:00 -07:00
parent 06df794db2
commit 04f59c55d8
23 changed files with 897 additions and 43 deletions
@@ -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
View File
@@ -0,0 +1 @@
../../../selfdrive/assets/images
+1
View File
@@ -0,0 +1 @@
../../../selfdrive/assets/sounds
@@ -0,0 +1 @@
../../../selfdrive/assets/icons/chffr_wheel.png
+522
View File
@@ -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.")
+5 -2
View File
@@ -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)
+26
View File
@@ -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)
+3 -1
View File
@@ -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)
+20 -9
View File
@@ -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()
+3
View File
@@ -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;
+1
View File
@@ -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;
};
+17 -2
View File
@@ -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);
}
+5
View File
@@ -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 &center, const QPixmap &img, const QBrush &bg, float opacity);
+25 -13
View File
@@ -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 {
+13 -5
View File
@@ -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);
}
+5
View File
@@ -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
View File
@@ -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()