262 lines
11 KiB
Python
262 lines
11 KiB
Python
import json
|
|
import os
|
|
import random
|
|
import requests
|
|
import sys
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "third_party"))
|
|
|
|
from collections import Counter
|
|
from datetime import datetime, timezone
|
|
from influxdb_client import InfluxDBClient, Point
|
|
from influxdb_client.client.write_api import SYNCHRONOUS
|
|
|
|
try:
|
|
from openpilot.common.conversions import Conversions as CV
|
|
except ModuleNotFoundError:
|
|
from openpilot.common.constants import CV
|
|
from openpilot.system.hardware import HARDWARE
|
|
from openpilot.system.version import get_build_metadata
|
|
|
|
from openpilot.frogpilot.common.frogpilot_utilities import clean_model_name, run_cmd
|
|
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles, params
|
|
|
|
BASE_URL = "https://nominatim.openstreetmap.org"
|
|
MINIMUM_POPULATION = 100_000
|
|
SEARCH_RADIUS_DEGREES = 1.45
|
|
METER_TO_MILE = getattr(CV, "METER_TO_MILE", 0.000621371192237334)
|
|
|
|
def get_device_generation(device_type):
|
|
normalized = (device_type or "").lower()
|
|
mapping = {
|
|
"tici": "C3",
|
|
"tizi": "C3X",
|
|
"mici": "C4",
|
|
}
|
|
return mapping.get(normalized, (device_type or "Unknown").upper())
|
|
|
|
def _json_object(value):
|
|
if value is None:
|
|
return {}
|
|
if isinstance(value, dict):
|
|
return value
|
|
if isinstance(value, bytes):
|
|
value = value.decode("utf-8", errors="replace")
|
|
if isinstance(value, str):
|
|
try:
|
|
parsed = json.loads(value)
|
|
return parsed if isinstance(parsed, dict) else {}
|
|
except Exception:
|
|
return {}
|
|
return {}
|
|
|
|
def get_population_value(population_str):
|
|
if population_str is None:
|
|
return None
|
|
try:
|
|
return int(str(population_str).replace(",", "").split(";")[0].strip())
|
|
except Exception:
|
|
return None
|
|
|
|
def search_nearby_major_cities(lat, lon, session, state_name, country_name):
|
|
viewbox = f"{lon - SEARCH_RADIUS_DEGREES},{lat + SEARCH_RADIUS_DEGREES},{lon + SEARCH_RADIUS_DEGREES},{lat - SEARCH_RADIUS_DEGREES}"
|
|
cities = (session.get(f"{BASE_URL}/search", params={
|
|
"addressdetails": 1, "bounded": 1, "extratags": 1, "format": "jsonv2", "limit": 20, "q": "city", "viewbox": viewbox
|
|
}, timeout=10).json() or [])
|
|
|
|
qualifying = [c for c in cities if (get_population_value((c.get("extratags") or {}).get("population")) or 0) >= MINIMUM_POPULATION]
|
|
if not qualifying:
|
|
return None
|
|
|
|
nearest = min(qualifying, key=lambda c: (float(c["lat"]) - lat) ** 2 + (float(c["lon"]) - lon) ** 2)
|
|
addr = nearest.get("address") or {}
|
|
return float(nearest["lat"]), float(nearest["lon"]), addr.get("city") or addr.get("town") or nearest.get("display_name", "").split(",")[0], state_name, country_name
|
|
|
|
|
|
def get_city_center(latitude, longitude):
|
|
try:
|
|
with requests.Session() as session:
|
|
session.headers.update({"Accept-Language": "en"})
|
|
session.headers.update({"User-Agent": "frogpilot-city-center-checker/1.0 (https://github.com/FrogAi/FrogPilot)"})
|
|
|
|
response = session.get(f"{BASE_URL}/reverse", params={"addressdetails": 1, "extratags": 0, "format": "jsonv2", "lat": latitude, "lon": longitude, "namedetails": 0, "zoom": 14}, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json() or {}
|
|
|
|
address = data.get("address") or {}
|
|
city_name = address.get("city") or address.get("hamlet") or address.get("town") or address.get("village")
|
|
country_code = (address.get("country_code") or "").lower()
|
|
country_name = address.get("country") or "N/A"
|
|
state_name = address.get("province") or address.get("region") or address.get("state") or address.get("state_district") or "N/A"
|
|
|
|
if city_name:
|
|
response = session.get(f"{BASE_URL}/search", params={"addressdetails": 1, "extratags": 1, "format": "jsonv2", "limit": 1, "q": f"{city_name}, {state_name}, {country_name}"}, timeout=10)
|
|
response.raise_for_status()
|
|
data = response.json() or []
|
|
|
|
if data:
|
|
tags = data[0]
|
|
population_value = get_population_value((tags.get("extratags") or {}).get("population"))
|
|
|
|
if population_value is not None and population_value >= MINIMUM_POPULATION:
|
|
latitude_value = float(tags["lat"])
|
|
longitude_value = float(tags["lon"])
|
|
|
|
resolved_address = tags.get("address") or {}
|
|
city_label = resolved_address.get("city") or resolved_address.get("town") or city_name
|
|
|
|
return latitude_value, longitude_value, city_label, state_name, country_name
|
|
|
|
nearby_result = search_nearby_major_cities(latitude, longitude, session, state_name, country_name)
|
|
if nearby_result:
|
|
return nearby_result
|
|
|
|
query = f"{state_name} state capital" if country_code == "us" else f"capital of {state_name}, {country_name}"
|
|
response = session.get(f"{BASE_URL}/search", params={"addressdetails": 1, "extratags": 1, "format": "jsonv2", "limit": 5, "q": query}, timeout=10)
|
|
response.raise_for_status()
|
|
candidates = response.json() or []
|
|
|
|
chosen_candidate = None
|
|
for candidate in candidates:
|
|
address = candidate.get("address") or {}
|
|
capital = (candidate.get("extratags") or {}).get("capital")
|
|
country = address.get("country")
|
|
state = address.get("province") or address.get("region") or address.get("state") or address.get("state_district")
|
|
|
|
if (state == state_name or state_name == "N/A") and country == country_name and (capital in ("administrative", "state", "yes") or address.get("city") or address.get("town")):
|
|
chosen_candidate = candidate
|
|
break
|
|
|
|
if not chosen_candidate and candidates:
|
|
chosen_candidate = candidates[0]
|
|
|
|
if chosen_candidate:
|
|
latitude_value = float(chosen_candidate["lat"])
|
|
longitude_value = float(chosen_candidate["lon"])
|
|
|
|
chosen_address = chosen_candidate.get("address") or {}
|
|
city_label = chosen_address.get("city") or chosen_address.get("town") or (chosen_candidate.get("display_name") or "").split(",")[0]
|
|
|
|
return latitude_value, longitude_value, city_label, state_name, country_name
|
|
|
|
print(f"Falling back to (0, 0) for {latitude}, {longitude}")
|
|
return float(0.0), float(0.0), "N/A", "N/A", "N/A"
|
|
|
|
except Exception as exception:
|
|
print(f"Falling back to (0, 0) for {latitude}, {longitude}")
|
|
return float(0.0), float(0.0), "N/A", "N/A", "N/A"
|
|
|
|
def update_branch_commits(now):
|
|
points = []
|
|
branch = get_build_metadata().channel # Current running branch
|
|
try:
|
|
response = requests.get(f"https://api.github.com/repos/firestar5683/StarPilot/commits/{branch}")
|
|
response.raise_for_status()
|
|
sha = response.json()["sha"]
|
|
points.append(Point("branch_commits").field("commit", sha).tag("branch", branch).time(now))
|
|
except Exception as e:
|
|
print(f"Failed to fetch commit for {branch}: {e}")
|
|
|
|
return points
|
|
|
|
def is_up_to_date(build_metadata):
|
|
remote_commit = run_cmd(["git", "ls-remote", "origin", build_metadata.channel], f"Fetched remote commit", "Failed to fetch remote commit", report=False)
|
|
|
|
if remote_commit:
|
|
return build_metadata.openpilot.git_commit == remote_commit.strip().split()[0]
|
|
|
|
return True
|
|
|
|
def send_stats():
|
|
try:
|
|
build_metadata = get_build_metadata()
|
|
frogpilot_toggles = get_frogpilot_toggles()
|
|
|
|
frogs_go_moo = getattr(frogpilot_toggles, "frogs_go_moo", getattr(frogpilot_toggles, "frogsgomoo_tweak", False))
|
|
if frogs_go_moo:
|
|
return
|
|
|
|
if frogpilot_toggles.car_make == "mock":
|
|
return
|
|
|
|
bucket = "StarPilot"
|
|
org_ID = "StarPilot"
|
|
url = "https://stats.firestar.link"
|
|
frogpilot_stats = _json_object(params.get("FrogPilotStats"))
|
|
|
|
location = _json_object(params.get("LastGPSPosition"))
|
|
if not (location.get("latitude") and location.get("longitude")):
|
|
return
|
|
original_latitude = location.get("latitude")
|
|
original_longitude = location.get("longitude")
|
|
latitude, longitude, city, state, country = get_city_center(original_latitude, original_longitude)
|
|
|
|
theme_sources = [
|
|
frogpilot_toggles.icon_pack.replace("-animated", ""),
|
|
frogpilot_toggles.color_scheme,
|
|
frogpilot_toggles.distance_icons.replace("-animated", ""),
|
|
frogpilot_toggles.signal_icons.replace("-animated", ""),
|
|
frogpilot_toggles.sound_pack
|
|
]
|
|
|
|
theme_counter = Counter(theme_sources)
|
|
most_common = theme_counter.most_common()
|
|
max_count = most_common[0][1]
|
|
|
|
selected_theme = random.choice([item for item, count in most_common if count == max_count]).replace("-user_created", "").replace("_", " ")
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
device_type = HARDWARE.get_device_type()
|
|
stats_dongle_id = params.get("FrogPilotDongleId", encoding="utf-8") or params.get("DongleId", encoding="utf-8") or "unknown"
|
|
|
|
user_point = (
|
|
Point("user_stats")
|
|
.tag("car_make", "GM" if frogpilot_toggles.car_make == "gm" else frogpilot_toggles.car_make.title())
|
|
.tag("car_model", frogpilot_toggles.car_model)
|
|
.tag("city", city)
|
|
.tag("country", country)
|
|
.tag("device", device_type)
|
|
.tag("device_generation", get_device_generation(device_type))
|
|
.tag("driving_model", clean_model_name(frogpilot_toggles.model_name))
|
|
.tag("state", state)
|
|
.tag("theme", selected_theme.title())
|
|
.tag("branch", build_metadata.channel)
|
|
.tag("dongle_id", stats_dongle_id)
|
|
|
|
.field("blocked_user", frogpilot_toggles.block_user)
|
|
.field("current_months_kilometers", int(frogpilot_stats.get("CurrentMonthsKilometers", 0)))
|
|
.field("event", 1)
|
|
.field("frogpilot_drives", int(frogpilot_stats.get("FrogPilotDrives", 0)))
|
|
.field("frogpilot_hours", float(frogpilot_stats.get("FrogPilotSeconds", 0)) / (60 * 60))
|
|
.field("frogpilot_miles", float(frogpilot_stats.get("FrogPilotMeters", 0)) * METER_TO_MILE)
|
|
.field("goat_scream", frogpilot_toggles.goat_scream_alert)
|
|
.field("has_cc_long", frogpilot_toggles.has_cc_long)
|
|
.field("has_openpilot_longitudinal", frogpilot_toggles.openpilot_longitudinal)
|
|
.field("has_pedal", frogpilot_toggles.has_pedal)
|
|
.field("has_sdsu", frogpilot_toggles.has_sdsu)
|
|
.field("has_sascm", getattr(frogpilot_toggles, "has_sascm", False))
|
|
.field("has_zss", frogpilot_toggles.has_zss)
|
|
.field("latitude", latitude)
|
|
.field("longitude", longitude)
|
|
.field("rainbow_path", frogpilot_toggles.rainbow_path)
|
|
.field("random_events", frogpilot_toggles.random_events)
|
|
.field("total_aol_seconds", float(frogpilot_stats.get("AOLTime", 0)))
|
|
.field("total_lateral_seconds", float(frogpilot_stats.get("LateralTime", 0)))
|
|
.field("total_longitudinal_seconds", float(frogpilot_stats.get("LongitudinalTime", 0)))
|
|
.field("total_tracked_seconds", float(frogpilot_stats.get("TrackedTime", 0)))
|
|
.field("tuning_level", params.get_int("TuningLevel") + 1 if params.get_bool("TuningLevelConfirmed") else 0)
|
|
.field("up_to_date", is_up_to_date(build_metadata))
|
|
.field("using_stock_acc", not (frogpilot_toggles.has_cc_long or frogpilot_toggles.openpilot_longitudinal))
|
|
|
|
.time(now)
|
|
)
|
|
|
|
all_points = [user_point] + update_branch_commits(now)
|
|
|
|
client = InfluxDBClient(org=org_ID, token=org_ID, url=url)
|
|
client.write_api(write_options=SYNCHRONOUS).write(bucket=bucket, org=org_ID, record=all_points)
|
|
print("Successfully sent FrogPilot stats!")
|
|
except Exception as exception:
|
|
print(f"Failed to send FrogPilot stats: {exception}")
|