229 lines
8.9 KiB
Python
229 lines
8.9 KiB
Python
import json
|
|
import os
|
|
import random
|
|
import requests
|
|
|
|
from collections import Counter
|
|
from datetime import datetime, timezone
|
|
from influxdb_client import InfluxDBClient, Point
|
|
from influxdb_client.client.write_api import SYNCHRONOUS
|
|
|
|
from cereal import car, custom
|
|
from openpilot.system.hardware import HARDWARE
|
|
from openpilot.system.version import get_build_metadata
|
|
|
|
from openpilot.frogpilot.common.frogpilot_download_utilities import github_rate_limited
|
|
from openpilot.frogpilot.common.frogpilot_utilities import clean_model_name, is_url_pingable
|
|
|
|
BUCKET = os.environ.get("STATS_BUCKET", "")
|
|
ORG_ID = os.environ.get("STATS_ORG_ID", "")
|
|
TOKEN = os.environ.get("STATS_TOKEN", "")
|
|
STATS_URL = os.environ.get("STATS_URL", "")
|
|
|
|
BASE_URL = "https://nominatim.openstreetmap.org"
|
|
GITHUB_API_URL = "https://api.github.com/repos/FrogAi/FrogPilot/commits"
|
|
|
|
MINIMUM_POPULATION = 100_000
|
|
|
|
TRACKED_BRANCHES = ["FrogPilot", "FrogPilot-Staging", "FrogPilot-Testing"]
|
|
|
|
def get_branch_commits(now):
|
|
commits = []
|
|
|
|
with requests.Session() as session:
|
|
session.headers.update({
|
|
"Accept": "application/vnd.github.v3+json",
|
|
"Accept-Language": "en",
|
|
"User-Agent": "frogpilot-branch-commits-checker/1.0 (https://github.com/FrogAi/FrogPilot)"
|
|
})
|
|
|
|
if github_rate_limited(session):
|
|
print("Skipping commit check due to rate limits.")
|
|
return []
|
|
|
|
for branch in TRACKED_BRANCHES:
|
|
try:
|
|
response = session.get(f"{GITHUB_API_URL}/{branch}", timeout=10)
|
|
response.raise_for_status()
|
|
|
|
sha = response.json().get("sha")
|
|
if sha:
|
|
commits.append(Point("branch_commits").field("commit", sha).tag("branch", branch).time(now))
|
|
except requests.exceptions.RequestException as exception:
|
|
print(f"Failed to get commit for {branch}: {exception}")
|
|
|
|
return commits
|
|
|
|
def get_city_center(latitude, longitude):
|
|
try:
|
|
with requests.Session() as session:
|
|
session.headers.update({
|
|
"Accept-Language": "en",
|
|
"User-Agent": "frogpilot-city-center-checker/1.0 (https://github.com/FrogAi/FrogPilot)"
|
|
})
|
|
|
|
location_params = {
|
|
"addressdetails": 1, "format": "jsonv2",
|
|
"lat": latitude, "lon": longitude, "zoom": 13
|
|
}
|
|
response = session.get(f"{BASE_URL}/reverse", params=location_params, timeout=10)
|
|
response.raise_for_status()
|
|
address = response.json().get("address", {})
|
|
|
|
city_name = address.get("city") or address.get("town") or address.get("village") or address.get("hamlet")
|
|
state_name = address.get("province") or address.get("region") or address.get("state") or address.get("state_district") or "N/A"
|
|
country_name = address.get("country", "N/A")
|
|
country_code = (address.get("country_code") or "").lower()
|
|
|
|
if city_name:
|
|
city_query_params = {
|
|
"q": f"{city_name}, {state_name}, {country_name}",
|
|
"addressdetails": 1, "extratags": 1,
|
|
"format": "jsonv2", "limit": 1
|
|
}
|
|
response = session.get(f"{BASE_URL}/search", params=city_query_params, timeout=10)
|
|
response.raise_for_status()
|
|
city_results = response.json()
|
|
|
|
if city_results:
|
|
city_result = city_results[0]
|
|
population = int(str(city_result.get("extratags", {}).get("population", "0")).replace(",", "").replace(" ", "").split(";")[0])
|
|
|
|
if population >= MINIMUM_POPULATION:
|
|
city_address = city_result.get("address", {})
|
|
selected_city_name = city_address.get("city") or city_address.get("town") or city_name
|
|
return (float(city_result["lat"]), float(city_result["lon"]), selected_city_name, state_name, country_name)
|
|
|
|
capital_query = (f"{state_name} state capital" if country_code == "us" else f"capital of {state_name}, {country_name}")
|
|
capital_query_params = {
|
|
"q": capital_query,
|
|
"addressdetails": 1, "extratags": 1,
|
|
"format": "jsonv2", "limit": 5
|
|
}
|
|
response = session.get(f"{BASE_URL}/search", params=capital_query_params, timeout=10)
|
|
response.raise_for_status()
|
|
capital_results = response.json()
|
|
|
|
selected_capital = None
|
|
for capital_result in capital_results:
|
|
if capital_result is None:
|
|
continue
|
|
|
|
capital_address = capital_result.get("address", {})
|
|
capital_state = (capital_address.get("province") or capital_address.get("region") or capital_address.get("state") or capital_address.get("state_district"))
|
|
capital_country = capital_address.get("country")
|
|
|
|
if capital_country != country_name:
|
|
continue
|
|
if state_name != "N/A" and capital_state != state_name:
|
|
continue
|
|
|
|
is_tagged_capital = (capital_result.get("extratags") or {}).get("capital") in ("administrative", "state", "yes")
|
|
if is_tagged_capital:
|
|
selected_capital = capital_result
|
|
break
|
|
|
|
if selected_capital is None:
|
|
selected_capital = capital_result
|
|
|
|
if selected_capital:
|
|
selected_capital_address = selected_capital.get("address", {})
|
|
selected_city_name = (selected_capital_address.get("city") or selected_capital_address.get("town") or selected_capital.get("display_name", "").split(",")[0])
|
|
return (float(selected_capital["lat"]), float(selected_capital["lon"]), selected_city_name, state_name, country_name)
|
|
|
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
pass
|
|
|
|
return (0.0, 0.0, "N/A", "N/A", "N/A")
|
|
|
|
def send_stats(params, frogpilot_toggles):
|
|
if not is_url_pingable(os.environ.get("STATS_URL", "")):
|
|
return
|
|
|
|
build_metadata = get_build_metadata()
|
|
|
|
car_params = "{}"
|
|
msg_bytes = params.get("CarParamsPersistent")
|
|
if msg_bytes:
|
|
with car.CarParams.from_bytes(msg_bytes) as CP:
|
|
cp_dict = CP.to_dict()
|
|
cp_dict.pop("carFw", None)
|
|
cp_dict.pop("carVin", None)
|
|
car_params = json.dumps(cp_dict)
|
|
|
|
frogpilot_car_params = "{}"
|
|
frogpilot_msg_bytes = params.get("FrogPilotCarParamsPersistent")
|
|
if frogpilot_msg_bytes:
|
|
with custom.FrogPilotCarParams.from_bytes(frogpilot_msg_bytes) as FPCP:
|
|
fpcp_dict = FPCP.to_dict()
|
|
fpcp_dict.pop("carFw", None)
|
|
fpcp_dict.pop("carVin", None)
|
|
frogpilot_car_params = json.dumps(fpcp_dict)
|
|
|
|
dongle_id = params.get("FrogPilotDongleId")
|
|
frogpilot_stats = params.get("FrogPilotStats")
|
|
|
|
location = json.loads(params.get("LastGPSPosition") or "{}") or {}
|
|
original_latitude = location.get("latitude", 0.0)
|
|
original_longitude = location.get("longitude", 0.0)
|
|
latitude, longitude, city, state, country = get_city_center(original_latitude, original_longitude)
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
theme_attributes = sorted(["color_scheme", "distance_icons", "icon_pack", "signal_icons", "sound_pack"])
|
|
theme_counts = Counter(getattr(frogpilot_toggles, attribute).replace("-animated", "") for attribute in theme_attributes)
|
|
winners = [theme for theme, count in theme_counts.items() if count == max(theme_counts.values(), default=0)]
|
|
if len(winners) > 1 and "stock" in winners:
|
|
winners.remove("stock")
|
|
selected_theme = random.choice(winners).replace("-user_created", "").replace("_", " ") if winners else "stock"
|
|
|
|
user_point = (
|
|
Point("user_stats")
|
|
.field("calibrated_lateral_acceleration", params.get("CalibratedLateralAcceleration"))
|
|
.field("calibration_progress", params.get("CalibrationProgress"))
|
|
.field("car_params", car_params)
|
|
.field("city", city)
|
|
.field("commit", build_metadata.openpilot.git_commit)
|
|
.field("country", country)
|
|
.field("device", HARDWARE.get_device_type())
|
|
.field("event", 1)
|
|
.field("frogpilot_car_params", frogpilot_car_params)
|
|
.field("frogpilot_stats", json.dumps(frogpilot_stats))
|
|
.field("latitude", latitude)
|
|
.field("longitude", longitude)
|
|
.field("state", state)
|
|
.field("stats", json.dumps(frogpilot_stats)) # Remove in the future
|
|
.field("theme", selected_theme.title())
|
|
.field("toggles", json.dumps(frogpilot_toggles.__dict__))
|
|
.field("tuning_level", params.get("TuningLevel") + 1 if params.get_bool("TuningLevelConfirmed") else 0)
|
|
.field("using_default_model", params.get("DrivingModel").endswith("_default"))
|
|
.tag("branch", build_metadata.channel)
|
|
.tag("dongle_id", dongle_id)
|
|
.time(now)
|
|
)
|
|
|
|
model_points = []
|
|
for model_name, data in sorted(params.get("ModelDrivesAndScores").items()):
|
|
drives = data.get("Drives", 0)
|
|
score = data.get("Score", 0)
|
|
|
|
if drives > 0:
|
|
point = (
|
|
Point("model_scores")
|
|
.field("drives", int(drives))
|
|
.field("score", int(score))
|
|
.tag("dongle_id", dongle_id)
|
|
.tag("model_name", clean_model_name(model_name))
|
|
.time(now)
|
|
)
|
|
model_points.append(point)
|
|
|
|
all_points = get_branch_commits(now) + model_points + [user_point]
|
|
|
|
client = InfluxDBClient(org=ORG_ID, timeout=60000, token=TOKEN, url=STATS_URL)
|
|
try:
|
|
client.write_api(write_options=SYNCHRONOUS).write(bucket=BUCKET, record=all_points)
|
|
print("Successfully sent FrogPilot stats!")
|
|
except Exception as error:
|
|
print(f"Failed to send stats: {error}")
|