486 lines
17 KiB
Python
486 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
|
"""
|
|
import calendar
|
|
import json
|
|
import time
|
|
import numpy as np
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
from openpilot.common.constants import CV
|
|
from openpilot.common.realtime import DT_MDL
|
|
from openpilot.common.swaglog import cloudlog
|
|
|
|
from openpilot.iqpilot.common.k3_slc_log import k3_slc_log
|
|
from openpilot.iqpilot.common.slc_utilities import calculate_bearing_offset, is_url_pingable
|
|
from openpilot.iqpilot.common.slc_variables import (
|
|
FREE_MAPBOX_REQUESTS,
|
|
OFFSET_MAP_IMPERIAL,
|
|
OFFSET_MAP_METRIC
|
|
)
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None
|
|
|
|
|
|
class SpeedLimitController:
|
|
def __init__(self, params):
|
|
self.params = params
|
|
|
|
self.calling_mapbox = False
|
|
self.override_slc = False
|
|
|
|
self.denied_target = 0
|
|
self.map_speed_limit = 0
|
|
self.mapbox_limit = 0
|
|
self.next_speed_limit = 0
|
|
self.overridden_speed = 0
|
|
self.segment_distance = 0
|
|
self.speed_limit_changed_timer = 0
|
|
self.target = 0
|
|
self.unconfirmed_speed_limit = 0
|
|
|
|
self.previous_source = "None"
|
|
self.source = "None"
|
|
self.active_source = "None"
|
|
self.active_target = 0.0
|
|
|
|
self.speed_limit_accepted = False
|
|
|
|
self.gps_valid = False
|
|
self.gps_position = {
|
|
"bearing": 0,
|
|
"latitude": 0,
|
|
"longitude": 0
|
|
}
|
|
|
|
mapbox_requests_raw = self.params.get("MapBoxRequests")
|
|
if isinstance(mapbox_requests_raw, dict):
|
|
self.mapbox_requests = mapbox_requests_raw
|
|
elif mapbox_requests_raw is not None:
|
|
try:
|
|
if isinstance(mapbox_requests_raw, bytes):
|
|
self.mapbox_requests = json.loads(mapbox_requests_raw.decode('utf-8'))
|
|
elif isinstance(mapbox_requests_raw, str):
|
|
self.mapbox_requests = json.loads(mapbox_requests_raw)
|
|
else:
|
|
self.mapbox_requests = {}
|
|
except (json.JSONDecodeError, AttributeError, TypeError):
|
|
self.mapbox_requests = {}
|
|
else:
|
|
self.mapbox_requests = {}
|
|
self.mapbox_requests.setdefault("total_requests", 0)
|
|
self.mapbox_requests.setdefault("max_requests", FREE_MAPBOX_REQUESTS - (28 * 100))
|
|
|
|
self.mapbox_host = "https://api.mapbox.com"
|
|
self.mapbox_token = self.params.get("MapboxToken")
|
|
if self.mapbox_token is not None and isinstance(self.mapbox_token, bytes):
|
|
self.mapbox_token = self.mapbox_token.decode('utf-8')
|
|
|
|
previous_limit = self.params.get("PreviousSpeedLimit")
|
|
if previous_limit is not None:
|
|
try:
|
|
if isinstance(previous_limit, bytes):
|
|
self.previous_target = float(previous_limit.decode('utf-8'))
|
|
else:
|
|
self.previous_target = float(previous_limit)
|
|
except (ValueError, AttributeError):
|
|
self.previous_target = 0.0
|
|
else:
|
|
self.previous_target = 0.0
|
|
|
|
self.executor = ThreadPoolExecutor(max_workers=1)
|
|
self._last_mapbox_log_t = 0.0
|
|
self._last_mapbox_diag_t = 0.0
|
|
self._last_mapbox_diag_message = None
|
|
|
|
self.session = requests.Session() if requests is not None else None
|
|
if self.session is not None:
|
|
self.session.headers.update({"Accept-Language": "en"})
|
|
self.session.headers.update({"User-Agent": "iqpilot-mapbox-speed-limit-retriever/1.0"})
|
|
|
|
@staticmethod
|
|
def _is_alive(sm, key: str) -> bool:
|
|
if hasattr(sm, "alive"):
|
|
return bool(sm.alive.get(key, False))
|
|
return isinstance(sm, dict) and key in sm
|
|
|
|
def get_offset(self, is_metric):
|
|
offset_map = OFFSET_MAP_METRIC if is_metric else OFFSET_MAP_IMPERIAL
|
|
for low, high, offset_param in offset_map:
|
|
if low < self.target < high:
|
|
offset_value = self.params.get(offset_param)
|
|
if offset_value is not None:
|
|
if isinstance(offset_value, bytes):
|
|
return float(offset_value.decode('utf-8'))
|
|
return float(offset_value)
|
|
return 0
|
|
return 0
|
|
|
|
def _log_mapbox_diag(self, message: str, force: bool = False) -> None:
|
|
now_mono = time.monotonic()
|
|
if not force and message == self._last_mapbox_diag_message and now_mono - self._last_mapbox_diag_t < 5.0:
|
|
return
|
|
if not force and now_mono - self._last_mapbox_diag_t < 2.0:
|
|
return
|
|
self._last_mapbox_diag_t = now_mono
|
|
self._last_mapbox_diag_message = message
|
|
cloudlog.info(message)
|
|
k3_slc_log(message)
|
|
|
|
def update_gps(self, sm):
|
|
llk_valid = False
|
|
if self._is_alive(sm, "liveLocationKalman"):
|
|
llk = sm["liveLocationKalman"]
|
|
llk_valid = bool(getattr(llk, "gpsOK", False))
|
|
|
|
if self._is_alive(sm, "gpsLocationExternal"):
|
|
gps_location = sm["gpsLocationExternal"]
|
|
elif self._is_alive(sm, "gpsLocation"):
|
|
gps_location = sm["gpsLocation"]
|
|
else:
|
|
gps_location = None
|
|
|
|
gps_has_fix = False
|
|
if gps_location is not None:
|
|
gps_has_fix = bool(getattr(gps_location, "hasFix", False))
|
|
gps_has_fix |= bool(getattr(gps_location, "flags", 0) > 0)
|
|
|
|
if gps_location and (gps_has_fix or llk_valid):
|
|
self.gps_valid = True
|
|
self.gps_position = {
|
|
"bearing": getattr(gps_location, 'bearingDeg', 0),
|
|
"latitude": getattr(gps_location, 'latitude', 0),
|
|
"longitude": getattr(gps_location, 'longitude', 0)
|
|
}
|
|
else:
|
|
self.gps_valid = False
|
|
|
|
def get_mapbox_speed_limit(self, now, time_validated, v_ego, sm):
|
|
if requests is None or self.session is None:
|
|
self._log_mapbox_diag("SLC Mapbox skipped: requests session unavailable")
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = 0
|
|
return
|
|
|
|
steer_angle = sm["carState"].steeringAngleDeg - sm["liveParameters"].angleOffsetDeg
|
|
if not self.gps_valid or not self.mapbox_token or steer_angle >= 45:
|
|
self._log_mapbox_diag(
|
|
"SLC Mapbox skipped: "
|
|
f"gps_valid={self.gps_valid} token={bool(self.mapbox_token)} steer_angle={round(float(steer_angle), 2)}"
|
|
)
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = 0
|
|
return
|
|
|
|
if v_ego < 1:
|
|
self._log_mapbox_diag(f"SLC Mapbox skipped: low_speed v_ego={round(float(v_ego), 2)}")
|
|
return
|
|
|
|
if self.segment_distance > 0:
|
|
self._log_mapbox_diag(
|
|
"SLC Mapbox deferred: "
|
|
f"segment_distance={round(float(self.segment_distance), 2)} v_ego={round(float(v_ego), 2)}"
|
|
)
|
|
self.segment_distance -= v_ego * DT_MDL
|
|
return
|
|
|
|
if self.calling_mapbox:
|
|
self._log_mapbox_diag("SLC Mapbox deferred: request already in flight")
|
|
self.segment_distance = v_ego
|
|
return
|
|
|
|
def make_request():
|
|
try:
|
|
self.calling_mapbox = True
|
|
|
|
successful = False
|
|
|
|
if not is_url_pingable(self.mapbox_host):
|
|
self._log_mapbox_diag("SLC Mapbox skipped: host not pingable", force=True)
|
|
self.segment_distance = 1000
|
|
return None
|
|
|
|
if time_validated:
|
|
current_month = now.month
|
|
if current_month != self.mapbox_requests.get("month"):
|
|
self.mapbox_requests.update({
|
|
"month": current_month,
|
|
"total_requests": 0,
|
|
"max_requests": FREE_MAPBOX_REQUESTS - calendar.monthrange(now.year, current_month)[1] * 100,
|
|
})
|
|
|
|
self.mapbox_requests["total_requests"] += 1
|
|
self.params.put_nonblocking("MapBoxRequests", self.mapbox_requests)
|
|
|
|
current_bearing = self.gps_position.get("bearing")
|
|
current_latitude = self.gps_position.get("latitude")
|
|
current_longitude = self.gps_position.get("longitude")
|
|
|
|
future_latitude, future_longitude = calculate_bearing_offset(current_latitude, current_longitude, current_bearing, v_ego)
|
|
|
|
self._log_mapbox_diag(
|
|
"SLC Mapbox request: "
|
|
f"lat={round(float(current_latitude), 6)} lon={round(float(current_longitude), 6)} "
|
|
f"bearing={round(float(current_bearing), 2)} v_ego={round(float(v_ego), 2)} "
|
|
f"future_lat={round(float(future_latitude), 6)} future_lon={round(float(future_longitude), 6)}",
|
|
force=True,
|
|
)
|
|
|
|
url = (
|
|
f"{self.mapbox_host}/matching/v5/mapbox/driving/"
|
|
f"{current_longitude},{current_latitude};"
|
|
f"{future_longitude},{future_latitude}.json"
|
|
)
|
|
|
|
mapbox_params = {
|
|
"access_token": self.mapbox_token,
|
|
"annotations": "maxspeed,distance",
|
|
"geometries": "polyline6",
|
|
"overview": "full",
|
|
"steps": "false",
|
|
"radiuses": "10;10",
|
|
"tidy": "true",
|
|
}
|
|
|
|
response = self.session.get(url, params=mapbox_params, timeout=10)
|
|
response.raise_for_status()
|
|
|
|
successful = True
|
|
|
|
return response.json()
|
|
except Exception as exception:
|
|
now_mono = time.monotonic()
|
|
if now_mono - self._last_mapbox_log_t >= 5.0:
|
|
self._last_mapbox_log_t = now_mono
|
|
message = f"SLC Mapbox request failed: {exception}"
|
|
cloudlog.warning(message)
|
|
k3_slc_log(message)
|
|
finally:
|
|
self.calling_mapbox = False
|
|
|
|
if not successful:
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = v_ego
|
|
|
|
return None
|
|
|
|
def complete_request(future):
|
|
try:
|
|
data = future.result()
|
|
if data:
|
|
matchings = data.get("matchings") or []
|
|
if not matchings:
|
|
self._log_mapbox_diag("SLC Mapbox callback: no matchings", force=True)
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = v_ego
|
|
return
|
|
|
|
legs = (matchings[0] or {}).get("legs") or []
|
|
if not legs:
|
|
self._log_mapbox_diag("SLC Mapbox callback: no legs", force=True)
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = v_ego
|
|
return
|
|
|
|
annotation = legs[0].get("annotation") or {}
|
|
|
|
distances = annotation.get("distance") or [v_ego]
|
|
segment_distance = distances[0]
|
|
|
|
speed_data = annotation.get("maxspeed", [])
|
|
speed_limit_kph = 0
|
|
if speed_data:
|
|
first_segment_speed = speed_data[0]
|
|
speed_limit_kph = (first_segment_speed.get("speed") if first_segment_speed.get("speed") != "none" else 0) or 0
|
|
|
|
if speed_limit_kph > 0:
|
|
self.mapbox_limit = speed_limit_kph * CV.KPH_TO_MS
|
|
self.segment_distance = segment_distance
|
|
self._log_mapbox_diag(
|
|
"SLC Mapbox callback: "
|
|
f"speed_limit_kph={round(float(speed_limit_kph), 2)} segment_distance={round(float(segment_distance), 2)}",
|
|
force=True,
|
|
)
|
|
return
|
|
|
|
self._log_mapbox_diag(
|
|
"SLC Mapbox callback: "
|
|
f"no usable maxspeed speed_data_len={len(speed_data)} segment_distance={round(float(segment_distance), 2)}",
|
|
force=True,
|
|
)
|
|
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = v_ego
|
|
|
|
except Exception as exception:
|
|
now_mono = time.monotonic()
|
|
if now_mono - self._last_mapbox_log_t >= 5.0:
|
|
self._last_mapbox_log_t = now_mono
|
|
message = f"SLC Mapbox callback failed: {exception}"
|
|
cloudlog.warning(message)
|
|
k3_slc_log(message)
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = v_ego
|
|
|
|
future = self.executor.submit(make_request)
|
|
future.add_done_callback(complete_request)
|
|
|
|
def handle_limit_change(self, desired_source, desired_target, sm, slc_params):
|
|
self.speed_limit_changed_timer += DT_MDL
|
|
|
|
car_state_iq = sm["iqCarState"]
|
|
accel_pressed = getattr(car_state_iq, "accelPressed", False)
|
|
speed_limit_accepted = (accel_pressed and sm["carControl"].longActive) or self.speed_limit_accepted
|
|
|
|
decel_pressed = getattr(car_state_iq, "decelPressed", False)
|
|
speed_limit_denied = decel_pressed or (self.speed_limit_changed_timer >= 30)
|
|
|
|
if speed_limit_accepted:
|
|
self.overridden_speed = 0
|
|
|
|
self.source = desired_source
|
|
self.target = desired_target
|
|
|
|
self.speed_limit_accepted = False
|
|
|
|
elif speed_limit_denied:
|
|
self.denied_target = desired_target
|
|
|
|
self.previous_source = desired_source
|
|
self.previous_target = desired_target
|
|
|
|
elif desired_target < self.target and not slc_params.get("speed_limit_confirmation_lower", False):
|
|
self.source = desired_source
|
|
self.target = desired_target
|
|
|
|
elif desired_target > self.target and not slc_params.get("speed_limit_confirmation_higher", False):
|
|
self.source = desired_source
|
|
self.target = desired_target
|
|
|
|
else:
|
|
self.source = "None"
|
|
self.unconfirmed_speed_limit = desired_target
|
|
|
|
if self.target != self.previous_target and self.target > 0 and not speed_limit_denied:
|
|
self.denied_target = 0
|
|
|
|
self.previous_source = self.source
|
|
self.previous_target = self.target
|
|
|
|
self.params.put_nonblocking("PreviousSpeedLimit", float(self.target))
|
|
|
|
def update_limits(self, dashboard_speed_limit, now, time_validated, v_cruise, v_ego, sm, slc_params):
|
|
self.update_gps(sm)
|
|
self.update_map_speed_limit(v_ego, sm, slc_params)
|
|
|
|
if self.mapbox_requests["total_requests"] < self.mapbox_requests["max_requests"]:
|
|
self.get_mapbox_speed_limit(now, time_validated, v_ego, sm)
|
|
else:
|
|
self.mapbox_limit = 0
|
|
self.segment_distance = 0
|
|
|
|
limits = {
|
|
"Dashboard": dashboard_speed_limit,
|
|
"Mapbox": self.mapbox_limit,
|
|
"Map Data": self.map_speed_limit
|
|
}
|
|
filtered_limits = {source: limit for source, limit in limits.items() if limit >= 1}
|
|
|
|
priority_highest = slc_params.get("speed_limit_priority_highest", False)
|
|
priority_lowest = slc_params.get("speed_limit_priority_lowest", False)
|
|
priority1 = slc_params.get("speed_limit_priority1", "Dashboard")
|
|
priority2 = slc_params.get("speed_limit_priority2", "Mapbox")
|
|
priority3 = slc_params.get("speed_limit_priority3", "Map Data")
|
|
|
|
if priority_highest:
|
|
desired_source = max(filtered_limits, key=filtered_limits.get, default="None")
|
|
desired_target = filtered_limits.get(desired_source, 0)
|
|
|
|
elif priority_lowest:
|
|
desired_source = min(filtered_limits, key=filtered_limits.get, default="None")
|
|
desired_target = filtered_limits.get(desired_source, 0)
|
|
|
|
elif filtered_limits:
|
|
for priority in [priority1, priority2, priority3]:
|
|
if priority in filtered_limits:
|
|
desired_source = priority
|
|
desired_target = filtered_limits[desired_source]
|
|
break
|
|
else:
|
|
desired_source = "None"
|
|
desired_target = 0
|
|
|
|
else:
|
|
desired_source = "None"
|
|
desired_target = 0
|
|
|
|
if desired_target == 0 or self.target == 0:
|
|
if self.denied_target != self.previous_target > 0 and slc_params.get("slc_fallback_previous_speed_limit", False):
|
|
desired_source = self.previous_source
|
|
desired_target = self.previous_target
|
|
|
|
self.target = desired_target
|
|
|
|
elif sm["selfdriveState"].enabled and slc_params.get("slc_fallback_set_speed", False):
|
|
desired_source = "None"
|
|
desired_target = v_cruise
|
|
|
|
self.active_source = desired_source
|
|
self.active_target = float(desired_target)
|
|
|
|
if abs(desired_target - self.previous_target) >= 1:
|
|
self.handle_limit_change(desired_source, desired_target, sm, slc_params)
|
|
elif desired_source != self.source and abs(desired_target - self.target) < 1:
|
|
self.source = desired_source
|
|
else:
|
|
self.speed_limit_changed_timer = 0
|
|
self.unconfirmed_speed_limit = 0
|
|
|
|
def update_map_speed_limit(self, v_ego, sm, slc_params):
|
|
|
|
if self._is_alive(sm, "iqLiveData"):
|
|
map_data = sm["iqLiveData"]
|
|
self.map_speed_limit = getattr(map_data, "speedLimit", 0) if getattr(map_data, "speedLimitValid", False) else 0
|
|
self.next_speed_limit = getattr(map_data, "speedLimitAhead", 0) if getattr(map_data, "speedLimitAheadValid", False) else 0
|
|
|
|
if self.next_speed_limit > 0:
|
|
if self.map_speed_limit < self.next_speed_limit:
|
|
lookahead_higher = slc_params.get("map_speed_lookahead_higher", 5)
|
|
max_lookahead = lookahead_higher * v_ego
|
|
elif self.map_speed_limit > self.next_speed_limit:
|
|
lookahead_lower = slc_params.get("map_speed_lookahead_lower", 5)
|
|
max_lookahead = lookahead_lower * v_ego
|
|
else:
|
|
max_lookahead = 0
|
|
|
|
next_distance = getattr(map_data, "speedLimitAheadDistance", 0)
|
|
if next_distance < max_lookahead:
|
|
self.map_speed_limit = self.next_speed_limit
|
|
else:
|
|
self.map_speed_limit = 0
|
|
self.next_speed_limit = 0
|
|
|
|
def update_override(self, v_cruise, v_cruise_diff, v_ego, v_ego_diff, sm, slc_params, is_metric):
|
|
offset = self.get_offset(is_metric)
|
|
|
|
self.override_slc = self.overridden_speed > self.target + offset > 0
|
|
self.override_slc |= sm["carState"].gasPressed and v_ego > self.target + offset > 0
|
|
self.override_slc &= sm["selfdriveState"].enabled
|
|
|
|
if self.override_slc:
|
|
if slc_params.get("speed_limit_controller_override_manual", False):
|
|
if sm["carState"].gasPressed:
|
|
self.overridden_speed = max(v_ego + v_ego_diff, self.overridden_speed)
|
|
self.overridden_speed = float(np.clip(self.overridden_speed, self.target + offset, v_cruise + v_cruise_diff))
|
|
elif slc_params.get("speed_limit_controller_override_set_speed", False):
|
|
self.overridden_speed = v_cruise + v_cruise_diff
|
|
|
|
self.source = "None"
|
|
else:
|
|
self.overridden_speed = 0
|