IQ.Pilot Release Commit @ 4b0cfbe
This commit is contained in:
@@ -116,7 +116,7 @@
|
|||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "d07a56d54a8dfd8dd66b0a58a709cd13585de455b7eeecbb50616792757193e1",
|
"sha256": "2ff824301dba9858009c5896a1539a702e7d83a5bfa401ced739afe56d6a6663",
|
||||||
"size": 67640
|
"size": 67640
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/__init__.py": {
|
"python/iqpilot_private/navassist/__init__.py": {
|
||||||
@@ -126,32 +126,32 @@
|
|||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "d7ba6546b659c439dfd747ee80419117493db6bdfee5305965428896aa75e720",
|
"sha256": "2f37ea823cbdd13183539d343853df77c74c63f10a45c1d0d2dfa6dd504a5e05",
|
||||||
"size": 135328
|
"size": 135392
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "45f9529ee808c55bd2e8965a6c72e5050dd8d12f47fd71e996a5aad161aa2e6b",
|
"sha256": "d2d9637b01968c656d35a583b238030b13de1d0a942d1a4ba2c5895c25701399",
|
||||||
"size": 201704
|
"size": 201704
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "16d2d214e9d4a793ead2b623fade799cf842e739b25cf74ab5ed60cb7c26f2f3",
|
"sha256": "f5bca5aa39d29ae40e289bb4381b4e0e1596ae5203adc501aada8f0223ca5a21",
|
||||||
"size": 134016
|
"size": 134016
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "f2d8fd727cb6002c174ea47ccd2945977143e5abe6d188732217474bcbd931bc",
|
"sha256": "97c8ff983b101ae0f6f034f9a041cd410f7cdd656f6baf6067c1acb669767e74",
|
||||||
"size": 203048
|
"size": 203224
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "2aed237187dcfa009ebbd5f0f6beab7a5da4d89cd3b4e37968a2bb62f7fc6468",
|
"sha256": "d264372e8f219533da1f9bdc0e5793e6c10c42937012138c304e9f8ed6751763",
|
||||||
"size": 68240
|
"size": 68240
|
||||||
},
|
},
|
||||||
"python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": {
|
"python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": {
|
||||||
"mode": 493,
|
"mode": 493,
|
||||||
"sha256": "0e63117bb686de08c73176138cc3851cbdadf491774b04e752995c059dcd281a",
|
"sha256": "7bec3818357726584894cb137b4c6a07c811a8178d8aaf6e3ee77597273728bc",
|
||||||
"size": 69656
|
"size": 69656
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
@@ -163,12 +163,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"signatures": {
|
"signatures": {
|
||||||
"python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": "grzvl6Szgy0DhX0A2RHxMH+EZYP4GDUwRwrXCI278zqBo9A+xdZPxBSQ0FIxoDzMZVrAfrK5yOADYYyF1gFkDQ==",
|
"python/iqpilot_private/navassist/__init__.cpython-312-aarch64-linux-gnu.so": "pB2EuFRYCo2d0WQnYAziKZMtTF0myedrlt4jPt6baypR49sl15qOFiKnFW8iL/C91mC8yrb8mgRu9DEJNs07Bw==",
|
||||||
"python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": "e4WP9/AodSGn0e1aCmUfLvDCA36Z3lyLQXTAXeuMpFamR2uu7SRYCKBoW7gIMtrqZvNi3DNeDfmPaWdITSlDDA==",
|
"python/iqpilot_private/navassist/appsetup.cpython-312-aarch64-linux-gnu.so": "y/P4Ni0GEBmqZHULY14DX/Odh4ByFo/tDd8QlgHiAM22QJ0hXhPRwr6AqbHqNADfjYSEOrGS30Oz/aXcXlXBBw==",
|
||||||
"python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": "K9jDY0eQxqtE7RKPmnnjJhOYogK4mkUXxucZmkY+K798BWThYxT+Q0q2D50eW9vc33fKH0lk4+L4w4P/qkTzDA==",
|
"python/iqpilot_private/navassist/assets.cpython-312-aarch64-linux-gnu.so": "TzTvaBh0Wzuesg5T1GP6QKX253masiMTyYtcKkz2Mptfc8LcAq7xbJmPP5RfviDXiv0tEFe6PfH/LEvD8Xv6AQ==",
|
||||||
"python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": "qE6DWECE/wsj58n9ewIFAEts8xTCKjWGQ0p+7fZ3XKFJR7hvFt7fNTZAQm1sBSAd55697ow7kq7Y1wyicvBBDw==",
|
"python/iqpilot_private/navassist/container.cpython-312-aarch64-linux-gnu.so": "bzSB5YmGEMbPX6TJn9R+g3hvIWI9sQYUOrSWofpQTFNcoPcf2WcLJRM9Fl1LbIdRWtEx2lWPQwjAQ5pd6J+4BQ==",
|
||||||
"python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": "h2yssDIlxVDJMaPW+K3TzGKwzrGcGDdQdOrZ75LmTS02UN34tmPWzv0j7WV2ombz5n8gu8+FcxYB+o1VapqkDA==",
|
"python/iqpilot_private/navassist/daemon.cpython-312-aarch64-linux-gnu.so": "sVR22V1Mfp2b2anriiZ16zoLPUT+/WT6uTpcfTErx8PxX2Ox/gWwe4ifzQMkWezFIaWn2imT4CehxhQZCA8HDA==",
|
||||||
"python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": "kTPke8BA2muzet1xLQWUKnFtlVboJSZ6Ep6O79y2zQ8xMkEPum2fHiYZr8wkqu+2NMEB/j1HO0BvwJPdl/qOBg==",
|
"python/iqpilot_private/navassist/guard.cpython-312-aarch64-linux-gnu.so": "k9Fz2A9GIFbJE+LEcFSmPWaNfYi0y3ZtVyP6P9e9vgk7KiugIazn5q9hRwUSlbBAn92vx8vU875Zv3/sqpr4AA==",
|
||||||
"python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": "Sd7DGPwD4DDrbqrnCiIwGgGAnTXU5MTS7rNdpq2rsixxvXhFzaaxQgMbGFi6CoylBAHeOwqi9uwjtY6aiaYkCQ=="
|
"python/iqpilot_private/navassist/paths.cpython-312-aarch64-linux-gnu.so": "PplaPn3UzgTejl36JQ0tKHq9qd2Z8YqE7dX6z0+tHWaITgPdMhV5bDOXjt4haJCvzdN/Z5moPkXHPyCgKB4SCQ=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
+k8uY8dYPLT5xuTlqHHgo9YUJkOQM6KQxhPn4leJUi2t9DsHDEMBWJwdtyqzQi5Zes9eRuG9GmF7Hircxj88DQ==
|
Vx6Vm8MyJE7uWp+sCDMp9K8NBG6lbJ2w49N8AXP8pAnM82G2NB1SWe4pTI4AqwoUle/n93Ydpdvwhv1kAztuCg==
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -845,6 +845,7 @@ class CarState(CarStateBase):
|
|||||||
pt_messages += [
|
pt_messages += [
|
||||||
("Blinkmodi_01", math.nan), # From J519 BCM (is inactive when no lights active, 50Hz when active)
|
("Blinkmodi_01", math.nan), # From J519 BCM (is inactive when no lights active, 50Hz when active)
|
||||||
("Kombi_02", math.nan), # Auxiliary-bus cluster odometer
|
("Kombi_02", math.nan), # Auxiliary-bus cluster odometer
|
||||||
|
("LH_EPS_01", math.nan), # ALC key slot, absent on MLB racks that never send 0x32A
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
pt_messages += [("Kombi_02", math.nan)] # Auxiliary-bus cluster odometer
|
pt_messages += [("Kombi_02", math.nan)] # Auxiliary-bus cluster odometer
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ from iqdbc.car.volkswagen.carstate import CarState
|
|||||||
from iqdbc.car.structs import CarParams
|
from iqdbc.car.structs import CarParams
|
||||||
from iqdbc.car.volkswagen.interface import CarInterface
|
from iqdbc.car.volkswagen.interface import CarInterface
|
||||||
from iqdbc.car.volkswagen.values import (CAR, FW_QUERY_CONFIG, MLB_ACC_COORDINATOR_MSGS, MLB_GEARBOX_MSGS, MLB_MSG_ACC_10,
|
from iqdbc.car.volkswagen.values import (CAR, FW_QUERY_CONFIG, MLB_ACC_COORDINATOR_MSGS, MLB_GEARBOX_MSGS, MLB_MSG_ACC_10,
|
||||||
MLB_MSG_GATEWAY_05, MLB_MSG_GETRIEBE_01, MLB_MSG_LH_EPS_03, WMI, VolkswagenFlags,
|
MLB_MSG_GATEWAY_05, MLB_MSG_GETRIEBE_01, MLB_MSG_LH_EPS_01, MLB_MSG_LH_EPS_03,
|
||||||
VolkswagenFlagsIQ, VolkswagenSafetyFlags)
|
WMI, VolkswagenFlags, VolkswagenFlagsIQ, VolkswagenSafetyFlags)
|
||||||
from iqdbc.car.volkswagen.fingerprints import FW_VERSIONS
|
from iqdbc.car.volkswagen.fingerprints import FW_VERSIONS
|
||||||
|
|
||||||
Ecu = CarParams.Ecu
|
Ecu = CarParams.Ecu
|
||||||
@@ -210,7 +210,6 @@ def _a4_mk4_frames(packer, reverse=False, eps_torque=None):
|
|||||||
packer.make_can_msg("Kombi_02", 1, {"KBI_Inhalt_Tank": 40, "KBI_Kilometerstand": 100000}),
|
packer.make_can_msg("Kombi_02", 1, {"KBI_Inhalt_Tank": 40, "KBI_Kilometerstand": 100000}),
|
||||||
packer.make_can_msg("Airbag_02", 1, {"AB_Gurtschloss_FA": 3}),
|
packer.make_can_msg("Airbag_02", 1, {"AB_Gurtschloss_FA": 3}),
|
||||||
packer.make_can_msg("Gateway_05", 1, {"BCM1_Rueckfahrlicht_Schalter": int(reverse)}),
|
packer.make_can_msg("Gateway_05", 1, {"BCM1_Rueckfahrlicht_Schalter": int(reverse)}),
|
||||||
packer.make_can_msg("LH_EPS_01", 1, {}),
|
|
||||||
]
|
]
|
||||||
if eps_torque is not None:
|
if eps_torque is not None:
|
||||||
msgs.append(packer.make_can_msg("LH_EPS_03", 1, {"EPS_Lenkmoment": abs(eps_torque),
|
msgs.append(packer.make_can_msg("LH_EPS_03", 1, {"EPS_Lenkmoment": abs(eps_torque),
|
||||||
@@ -499,6 +498,32 @@ def test_mlb_manual_gateway_car_adds_only_the_reverse_switch_on_the_powertrain_b
|
|||||||
assert manual[Bus.aux] == automatic[Bus.aux] | {MLB_MSG_GATEWAY_05}
|
assert manual[Bus.aux] == automatic[Bus.aux] | {MLB_MSG_GATEWAY_05}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_like_the_alc_runtime(parser, name):
|
||||||
|
# the closed-source ALC reads its key slot through parser.vl, which subscribes the message on first access
|
||||||
|
if name not in parser.dat:
|
||||||
|
parser.vl[name]
|
||||||
|
return parser.dat.get(name, b"")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("build", (_a4_mk4_car, _q5_mk1_car), ids=("a4_mk4", "q5_mk1"))
|
||||||
|
def test_mlb_alc_key_slot_read_keeps_can_valid_on_a_rack_that_never_sends_it(build):
|
||||||
|
car = build()
|
||||||
|
subscribed = _subscribed_addresses(car)
|
||||||
|
never_sent = {bus: addrs - {MLB_MSG_LH_EPS_01} for bus, addrs in subscribed.items()}
|
||||||
|
|
||||||
|
packer = CANPacker("vw_mlb")
|
||||||
|
|
||||||
|
def build_frames():
|
||||||
|
_read_like_the_alc_runtime(car.can_parsers[Bus.pt], "LH_EPS_01")
|
||||||
|
return _frames_for(car, packer, never_sent)
|
||||||
|
|
||||||
|
ret = _run_past_aliveness_timeout(car, build_frames)
|
||||||
|
|
||||||
|
assert ret.canValid
|
||||||
|
assert all(parser.can_valid for parser in car.can_parsers.values())
|
||||||
|
assert not any(parser.bus_timeout for parser in car.can_parsers.values())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("build", (_a4_mk4_car, _q5_mk1_car), ids=("a4_mk4", "q5_mk1"))
|
@pytest.mark.parametrize("build", (_a4_mk4_car, _q5_mk1_car), ids=("a4_mk4", "q5_mk1"))
|
||||||
def test_mlb_carstate_subscriptions_settle_and_stay_alive(build):
|
def test_mlb_carstate_subscriptions_settle_and_stay_alive(build):
|
||||||
car = build()
|
car = build()
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ class VolkswagenFlagsIQ(IntFlag):
|
|||||||
|
|
||||||
RADAR_DISABLE_STATE = {"error": False}
|
RADAR_DISABLE_STATE = {"error": False}
|
||||||
|
|
||||||
|
MLB_MSG_LH_EPS_01 = 0x32A
|
||||||
MLB_MSG_LH_EPS_03 = 0x9F
|
MLB_MSG_LH_EPS_03 = 0x9F
|
||||||
MLB_MSG_GETRIEBE_01 = 0x82
|
MLB_MSG_GETRIEBE_01 = 0x82
|
||||||
MLB_MSG_GETRIEBE_02 = 0x83
|
MLB_MSG_GETRIEBE_02 = 0x83
|
||||||
|
|||||||
@@ -361,6 +361,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
|
|
||||||
// iqpilot model params
|
// iqpilot model params
|
||||||
{"CameraOffset", {PERSISTENT, FLOAT, "0.0"}},
|
{"CameraOffset", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
|
// Dev gate for the Android nav stack. Off by default and only konn3kt dev-flagged devices
|
||||||
|
// (or a developer, by hand) set it. navassist stays fully dormant unless this AND IQAndroidNav
|
||||||
|
// are both set, so a non-dev forcing IQAndroidNav=1 does nothing — the container never starts.
|
||||||
|
{"IQNavAssistDev", {PERSISTENT, BOOL, "0"}},
|
||||||
{"IQAndroidNav", {PERSISTENT, BOOL, "0"}},
|
{"IQAndroidNav", {PERSISTENT, BOOL, "0"}},
|
||||||
{"IQAndroidNavStatus", {PERSISTENT, STRING}},
|
{"IQAndroidNavStatus", {PERSISTENT, STRING}},
|
||||||
// "waze" or "maps": exactly one runs, the other is disabled outright.
|
// "waze" or "maps": exactly one runs, the other is disabled outright.
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ class BundleProcess(NativeProcess):
|
|||||||
def __init__(self, name, bundle, entry, should_run, enabled=True, sigkill=False, restart_if_crash=False):
|
def __init__(self, name, bundle, entry, should_run, enabled=True, sigkill=False, restart_if_crash=False):
|
||||||
self.bundle = bundle
|
self.bundle = bundle
|
||||||
self.entry = entry
|
self.entry = entry
|
||||||
self.restart_if_crash = restart_if_crash
|
|
||||||
runner_path = preferred_runner_path()
|
runner_path = preferred_runner_path()
|
||||||
runner_cmd = str(runner_path) if runner_path.is_absolute() else "./iqpilot_bundle_runner"
|
runner_cmd = str(runner_path) if runner_path.is_absolute() else "./iqpilot_bundle_runner"
|
||||||
runner_cwd = ".iqpilot/runtime_root" if runner_path.is_absolute() else "system/proprietary_runtime"
|
runner_cwd = ".iqpilot/runtime_root" if runner_path.is_absolute() else "system/proprietary_runtime"
|
||||||
@@ -238,6 +237,7 @@ class BundleProcess(NativeProcess):
|
|||||||
should_run=should_run,
|
should_run=should_run,
|
||||||
enabled=enabled,
|
enabled=enabled,
|
||||||
sigkill=sigkill,
|
sigkill=sigkill,
|
||||||
|
restart_if_crash=restart_if_crash,
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def always_run(started: bool, params: Params, CP: car.CarParams) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def nav_assist(started: bool, params: Params, CP: car.CarParams) -> bool:
|
def nav_assist(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||||
return params.get_bool("IQAndroidNav")
|
return params.get_bool("IQNavAssistDev") and params.get_bool("IQAndroidNav")
|
||||||
|
|
||||||
def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
|
def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
|
||||||
return started
|
return started
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ class TestManager:
|
|||||||
assert manager.update_modeld_watchdog(deadline, True, False, proc, 16.0) is None
|
assert manager.update_modeld_watchdog(deadline, True, False, proc, 16.0) is None
|
||||||
proc.restart.assert_not_called()
|
proc.restart.assert_not_called()
|
||||||
|
|
||||||
|
def test_bundle_process_keeps_restart_if_crash(self):
|
||||||
|
proc = BundleProcess("test", "bundle", "entry", lambda *_: True, restart_if_crash=True)
|
||||||
|
assert proc.restart_if_crash is True
|
||||||
|
assert BundleProcess("test", "bundle", "entry", lambda *_: True).restart_if_crash is False
|
||||||
|
|
||||||
def test_bundle_process_stops_with_sigterm(self, mocker):
|
def test_bundle_process_stops_with_sigterm(self, mocker):
|
||||||
proc = BundleProcess("test", "bundle", "entry", lambda *_: True)
|
proc = BundleProcess("test", "bundle", "entry", lambda *_: True)
|
||||||
proc.proc = mocker.Mock(exitcode=None, pid=123)
|
proc.proc = mocker.Mock(exitcode=None, pid=123)
|
||||||
|
|||||||
@@ -28,105 +28,98 @@ def _speed_scale() -> float:
|
|||||||
return CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
return CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
|
||||||
|
|
||||||
|
|
||||||
_STRIP_WIDTH = 28
|
_ACCEL_METER_INSET = 18.0
|
||||||
_STRIP_INSET = 14
|
_ACCEL_METER_WIDTH = 16.0
|
||||||
_STRIP_CEILING = 0.85
|
_ACCEL_METER_BACKDROP_WIDTH = 42.0
|
||||||
_STRIP_EMA = 5.0
|
_ACCEL_METER_HEIGHT = 0.5
|
||||||
_STRIP_ARC_SEGMENTS = 24
|
_ACCEL_METER_MAX_HEIGHT = 520.0
|
||||||
_STRIP_ACCEL = (33, 112, 115)
|
_ACCEL_METER_DEADBAND = 0.12
|
||||||
_STRIP_ACCEL_NEON = (94, 232, 236)
|
_ACCEL_METER_FULL_SCALE = 3.0
|
||||||
_STRIP_DECEL = (255, 0, 247)
|
_ACCEL_METER_ATTACK_TAU = 0.08
|
||||||
_STRIP_DECEL_NEON = (255, 145, 251)
|
_ACCEL_METER_RELEASE_TAU = 0.35
|
||||||
_STRIP_TIP_ALPHA = 235
|
_ACCEL_METER_ACCEL_ROOT = (0, 178, 169)
|
||||||
_STRIP_ROOT_ALPHA = 55
|
_ACCEL_METER_ACCEL_TIP = (54, 255, 232)
|
||||||
_STRIP_CORE_DEPTH = 7.0
|
_ACCEL_METER_BRAKE_ROOT = (196, 32, 139)
|
||||||
_STRIP_CORE_LAYERS = 8
|
_ACCEL_METER_BRAKE_TIP = (255, 86, 202)
|
||||||
_STRIP_CORE_ALPHA = 0.15
|
_ACCEL_METER_GLOW = ((8.0, 18), (5.0, 28), (2.5, 42))
|
||||||
_STRIP_CORE_FLOOR = 0.3
|
|
||||||
_STRIP_CORE_TAIL = 22.0
|
|
||||||
_STRIP_HALO_SPREAD = 11.0
|
|
||||||
_STRIP_HALO_LAYERS = 11
|
|
||||||
_STRIP_HALO_ALPHA = 0.095
|
|
||||||
_STRIP_HALO_TAIL = 34.0
|
|
||||||
_STRIP_NEON_FULL = 2.0
|
|
||||||
|
|
||||||
|
|
||||||
class IQAccelBar:
|
class IQAccelBar:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._eased = 0.0
|
self._shown = 0.0
|
||||||
|
self._last_frame = time.monotonic()
|
||||||
def _reach(self) -> float:
|
|
||||||
mag = abs(self._eased)
|
|
||||||
return 0.0 if mag == 0.0 else max(0.0, _STRIP_CEILING - 0.1 / mag)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _tint(fill, frac: float):
|
def _normalized(acceleration: float) -> float:
|
||||||
return canvas.shade(*fill, int(_STRIP_TIP_ALPHA + (_STRIP_ROOT_ALPHA - _STRIP_TIP_ALPHA) * frac))
|
magnitude = abs(acceleration)
|
||||||
|
if magnitude <= _ACCEL_METER_DEADBAND:
|
||||||
|
return 0.0
|
||||||
|
return float(np.clip(
|
||||||
|
(magnitude - _ACCEL_METER_DEADBAND) / (_ACCEL_METER_FULL_SCALE - _ACCEL_METER_DEADBAND),
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _halo(center, cap: float, up: bool, neon, heat: float, tail: float) -> None:
|
def _next_value(shown: float, target: float, dt: float) -> float:
|
||||||
step = int(255 * _STRIP_HALO_ALPHA * heat)
|
growing = abs(target) > abs(shown) or shown * target < 0.0
|
||||||
if step < 1:
|
tau = _ACCEL_METER_ATTACK_TAU if growing else _ACCEL_METER_RELEASE_TAU
|
||||||
return
|
alpha = 1.0 - math.exp(-max(0.0, dt) / tau)
|
||||||
start, end = (180.0, 360.0) if up else (0.0, 180.0)
|
value = shown + (target - shown) * alpha
|
||||||
tint = canvas.shade(*neon, step)
|
return 0.0 if abs(value) < 0.005 and abs(target) <= _ACCEL_METER_DEADBAND else value
|
||||||
faded = canvas.shade(*neon, 0)
|
|
||||||
top, bottom = (tint, faded) if up else (faded, tint)
|
|
||||||
for i in range(_STRIP_HALO_LAYERS):
|
|
||||||
spread = _STRIP_HALO_SPREAD * (1.0 - i / _STRIP_HALO_LAYERS)
|
|
||||||
if spread <= 0.0:
|
|
||||||
continue
|
|
||||||
canvas.annulus(center, cap, cap + spread, start, end, _STRIP_ARC_SEGMENTS, tint)
|
|
||||||
if tail <= 0.0:
|
|
||||||
continue
|
|
||||||
run = min(tail, _STRIP_HALO_TAIL)
|
|
||||||
y = center.y if up else center.y - run
|
|
||||||
canvas.v_sweep(center.x - cap - spread, y, spread, run, top, bottom)
|
|
||||||
canvas.v_sweep(center.x + cap, y, spread, run, top, bottom)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _cap(center, cap: float, up: bool, fill, neon, heat: float, tail: float) -> None:
|
def _gradient(y: float, height: float, width: float, x: float, accelerating: bool, root, tip, alpha: int) -> None:
|
||||||
start, end = (180.0, 360.0) if up else (0.0, 180.0)
|
root_color = canvas.shade(*root, max(1, int(alpha * 0.56)))
|
||||||
canvas.annulus(center, 0.0, cap, start, end, _STRIP_ARC_SEGMENTS, fill)
|
tip_color = canvas.shade(*tip, alpha)
|
||||||
IQAccelBar._halo(center, cap, up, neon, heat, tail)
|
top, bottom = (tip_color, root_color) if accelerating else (root_color, tip_color)
|
||||||
lit = _STRIP_CORE_FLOOR + (1.0 - _STRIP_CORE_FLOOR) * heat
|
canvas.v_sweep(x, y, width, height, top, bottom)
|
||||||
core = canvas.shade(*neon, max(1, int(255 * _STRIP_CORE_ALPHA * lit)))
|
|
||||||
faded = canvas.shade(*neon, 0)
|
@staticmethod
|
||||||
top, bottom = (core, faded) if up else (faded, core)
|
def _fill(x: float, y: float, width: float, height: float, accelerating: bool, root, tip) -> None:
|
||||||
run = min(tail, _STRIP_CORE_TAIL)
|
for spread, alpha in _ACCEL_METER_GLOW:
|
||||||
for i in range(_STRIP_CORE_LAYERS):
|
IQAccelBar._gradient(y, height, width + spread * 2.0, x - spread, accelerating, root, tip, alpha)
|
||||||
depth = _STRIP_CORE_DEPTH * (1.0 - i / _STRIP_CORE_LAYERS)
|
IQAccelBar._gradient(y, height, width, x, accelerating, root, tip, 235)
|
||||||
if depth <= 0.0:
|
center_x = x + width / 2.0
|
||||||
continue
|
tip_y = y if accelerating else y + height
|
||||||
canvas.annulus(center, max(0.0, cap - depth), cap, start, end, _STRIP_ARC_SEGMENTS, core)
|
canvas.disc(center_x, tip_y, width / 2.0 + 1.0, canvas.shade(*tip, 242))
|
||||||
if run <= 0.0:
|
canvas.v_sweep(x + width * 0.38, y, width * 0.24, height,
|
||||||
continue
|
canvas.shade(255, 255, 255, 92 if accelerating else 24),
|
||||||
y = center.y if up else center.y - run
|
canvas.shade(255, 255, 255, 24 if accelerating else 92))
|
||||||
canvas.v_sweep(center.x - cap, y, depth, run, top, bottom)
|
|
||||||
canvas.v_sweep(center.x + cap - depth, y, depth, run, top, bottom)
|
|
||||||
|
|
||||||
def render(self, rect, sm) -> None:
|
def render(self, rect, sm) -> None:
|
||||||
if not ui_state.rocket_fuel:
|
if not ui_state.rocket_fuel:
|
||||||
return
|
return
|
||||||
self._eased += (sm['carState'].aEgo - self._eased) / _STRIP_EMA
|
now = time.monotonic()
|
||||||
reach = self._reach() * rect.height / 2.0
|
dt = min(max(now - self._last_frame, 1.0 / 120.0), 0.1)
|
||||||
if reach <= 0.0:
|
self._last_frame = now
|
||||||
return
|
target = float(np.clip(sm['carState'].aEgo, -_ACCEL_METER_FULL_SCALE, _ACCEL_METER_FULL_SCALE))
|
||||||
accelerating = self._eased > 0.0
|
self._shown = self._next_value(self._shown, target, dt)
|
||||||
mid = rect.y + rect.height / 2.0
|
|
||||||
top = mid - reach if accelerating else mid
|
|
||||||
x = rect.x + _STRIP_INSET
|
|
||||||
cap = min(_STRIP_WIDTH / 2.0, reach / 2.0)
|
|
||||||
fill, neon = (_STRIP_ACCEL, _STRIP_ACCEL_NEON) if accelerating else (_STRIP_DECEL, _STRIP_DECEL_NEON)
|
|
||||||
near = cap / reach
|
|
||||||
frac_top, frac_bottom = (near, 1.0 - near) if accelerating else (1.0 - near, near)
|
|
||||||
heat = min(abs(self._eased) / _STRIP_NEON_FULL, 1.0)
|
|
||||||
|
|
||||||
canvas.v_sweep(x, top + cap, cap * 2.0, reach - cap * 2.0,
|
track_height = min(rect.height * _ACCEL_METER_HEIGHT, _ACCEL_METER_MAX_HEIGHT)
|
||||||
self._tint(fill, frac_top), self._tint(fill, frac_bottom))
|
mid = rect.y + rect.height / 2.0
|
||||||
tail = max(0.0, reach / 2.0 - cap)
|
track_y = mid - track_height / 2.0
|
||||||
self._cap(canvas.Pt(x + cap, top + cap), cap, True, self._tint(fill, frac_top), neon, heat, tail)
|
backdrop_x = rect.x + _ACCEL_METER_INSET
|
||||||
self._cap(canvas.Pt(x + cap, top + reach - cap), cap, False, self._tint(fill, frac_bottom), neon, heat, tail)
|
track_x = backdrop_x + (_ACCEL_METER_BACKDROP_WIDTH - _ACCEL_METER_WIDTH) / 2.0
|
||||||
|
canvas.panel(rl.Rectangle(backdrop_x, track_y - 10.0, _ACCEL_METER_BACKDROP_WIDTH, track_height + 20.0),
|
||||||
|
0.48, 18, canvas.shade(0, 8, 13, 108))
|
||||||
|
canvas.panel_outline(rl.Rectangle(backdrop_x, track_y - 10.0, _ACCEL_METER_BACKDROP_WIDTH, track_height + 20.0),
|
||||||
|
0.48, 18, 1.0, canvas.shade(126, 245, 238, 34))
|
||||||
|
canvas.panel(rl.Rectangle(track_x, track_y, _ACCEL_METER_WIDTH, track_height),
|
||||||
|
0.5, 14, canvas.shade(210, 245, 242, 25))
|
||||||
|
|
||||||
|
normalized = self._normalized(self._shown)
|
||||||
|
if normalized > 0.0:
|
||||||
|
accelerating = self._shown > 0.0
|
||||||
|
reach = normalized * (track_height / 2.0 - _ACCEL_METER_WIDTH / 2.0)
|
||||||
|
fill_y = mid - reach if accelerating else mid
|
||||||
|
root, tip = ((_ACCEL_METER_ACCEL_ROOT, _ACCEL_METER_ACCEL_TIP) if accelerating else
|
||||||
|
(_ACCEL_METER_BRAKE_ROOT, _ACCEL_METER_BRAKE_TIP))
|
||||||
|
self._fill(track_x, fill_y, _ACCEL_METER_WIDTH, reach, accelerating, root, tip)
|
||||||
|
|
||||||
|
canvas.slab(backdrop_x + 6.0, mid - 1.0, _ACCEL_METER_BACKDROP_WIDTH - 12.0, 2.0,
|
||||||
|
canvas.shade(236, 255, 253, 205))
|
||||||
|
canvas.disc(track_x + _ACCEL_METER_WIDTH / 2.0, mid, 3.0, canvas.shade(255, 255, 255, 238))
|
||||||
|
|
||||||
_BS_INSET = 20
|
_BS_INSET = 20
|
||||||
_BS_DROP = 100
|
_BS_DROP = 100
|
||||||
|
|||||||
31
iqpilot/ui/tests/test_accel_meter.py
Normal file
31
iqpilot/ui/tests/test_accel_meter.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from iqpilot.ui.onroad.hud_overlays import IQAccelBar
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("acceleration", [-0.12, 0.0, 0.12])
|
||||||
|
def test_acceleration_meter_deadband(acceleration):
|
||||||
|
assert IQAccelBar._normalized(acceleration) == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("acceleration", [-4.0, -3.0, 3.0, 4.0])
|
||||||
|
def test_acceleration_meter_clamps_full_scale(acceleration):
|
||||||
|
assert IQAccelBar._normalized(acceleration) == 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_acceleration_meter_is_symmetric():
|
||||||
|
assert IQAccelBar._normalized(-1.5) == IQAccelBar._normalized(1.5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_acceleration_meter_attack_is_faster_than_release():
|
||||||
|
attack = IQAccelBar._next_value(0.0, 1.0, 0.05)
|
||||||
|
release = IQAccelBar._next_value(1.0, 0.0, 0.05)
|
||||||
|
assert attack > 1.0 - release
|
||||||
|
|
||||||
|
|
||||||
|
def test_acceleration_meter_crosses_direction_smoothly():
|
||||||
|
shown = IQAccelBar._next_value(0.8, -0.8, 0.05)
|
||||||
|
assert -0.8 < shown < 0.8
|
||||||
Reference in New Issue
Block a user