mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-23 10:02:05 +08:00
Compare commits
299 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2439c048ed | |||
| 7b54d7d7a0 | |||
| b2bdb5787f | |||
| 282f517b78 | |||
| 1779144ff9 | |||
| 54fb2750ba | |||
| 14431ab77f | |||
| 9d45db41b3 | |||
| b57c593d92 | |||
| 0674c42866 | |||
| 1f7bcf246a | |||
| b0512dc523 | |||
| cd88fd3850 | |||
| ff78eaeba1 | |||
| 92c8aeeb92 | |||
| ecaa20e7be | |||
| 859c98c9d8 | |||
| 69cbe8c6ed | |||
| 1aafe92fc0 | |||
| adb6b9fb12 | |||
| f67a9f4624 | |||
| 094e834ac4 | |||
| e4067060b9 | |||
| 86a14640b5 | |||
| 59fb84f0d5 | |||
| 78de201ce7 | |||
| 17e49f69cc | |||
| bec75ebdd8 | |||
| 473832efa7 | |||
| 2cceeb3552 | |||
| 923a47f713 | |||
| 790a762a05 | |||
| 221c219fca | |||
| 8b60649eed | |||
| ded0b506d6 | |||
| b51d9af9a0 | |||
| 4460ce8166 | |||
| f1aa0c7f78 | |||
| e7c8126fd9 | |||
| 125999c364 | |||
| 5b25ea7f99 | |||
| b7e2631286 | |||
| a838871189 | |||
| 1827331599 | |||
| 5c777bbe01 | |||
| fb97f993d1 | |||
| 94b67077e3 | |||
| 14f17699b9 | |||
| 80e27d5cbb | |||
| 039dbcd877 | |||
| 8c134ae555 | |||
| 276c7a2b34 | |||
| 0e2dbcebfa | |||
| bbb7760a95 | |||
| 23a27b2fbf | |||
| 8b78107a40 | |||
| c27b6007de | |||
| 36c2dce247 | |||
| 8035039731 | |||
| 8aa6c9440f | |||
| 34ef40fd81 | |||
| 4d044d7618 | |||
| 6247e3dc84 | |||
| b1131289b7 | |||
| 35dc7d661e | |||
| c39b2dad94 | |||
| e6b769245c | |||
| 8b94f8b2f8 | |||
| dec014cd17 | |||
| 7b40272866 | |||
| 506456e7f0 | |||
| 4ea4b9d177 | |||
| 0e2313dc31 | |||
| 79ea7db103 | |||
| f9ae9192fa | |||
| 7ec23006c6 | |||
| 2be9447a6c | |||
| cfd926778e | |||
| a97a67e3d0 | |||
| 518b6de08d | |||
| 6933e3bcdb | |||
| 05e0ca8bee | |||
| 410614fcf3 | |||
| e2bc0996ef | |||
| 1be0c20cf5 | |||
| 839143b9ed | |||
| bce86637ae | |||
| b833d3ee89 | |||
| e0441dfb4b | |||
| 62ec40bba6 | |||
| 15c6d38028 | |||
| 56eb9f555c | |||
| 2f9951df02 | |||
| 6030bf4da3 | |||
| 074694d660 | |||
| df35f48f3b | |||
| 4fb9704540 | |||
| 48cbe266fc | |||
| 21aa7ff367 | |||
| 7caf05dd51 | |||
| 2d779f5db9 | |||
| 18208f1da0 | |||
| 5a3c6ddf57 | |||
| eaa8732ab0 | |||
| 6f0284c84f | |||
| 999ea03f23 | |||
| 0975db3ff1 | |||
| 8d70a8b80a | |||
| da93f92887 | |||
| 9117f6c071 | |||
| 3567ff9691 | |||
| f44ae2ced9 | |||
| 376e0ca615 | |||
| 32b7686468 | |||
| b9e0f52ea9 | |||
| a3163b680f | |||
| 8a927d808f | |||
| bc7d5e474d | |||
| 36f192b5fe | |||
| ffd5cd4ac2 | |||
| e4a00fcd6c | |||
| 0bdcb41103 | |||
| 78051085ca | |||
| 4910d5809a | |||
| 8dd862ff28 | |||
| 1b57497da9 | |||
| 8d8d1ffc7a | |||
| c5919d5495 | |||
| da71951c95 | |||
| 25a152cd8b | |||
| 9077a1082a | |||
| 7ae7000254 | |||
| 7029455706 | |||
| 3a71a62215 | |||
| 00622e8c33 | |||
| 549da3ee92 | |||
| e038a65ef8 | |||
| 8d0513c657 | |||
| 2cea48f4cd | |||
| 8855a9ab65 | |||
| 32321c01cc | |||
| 52cd65fefb | |||
| 1fcdeccd40 | |||
| 3517c36978 | |||
| fb30c3c1e8 | |||
| 1c25e568d5 | |||
| f172122b7c | |||
| 67d6cdc7cd | |||
| 6724085cfd | |||
| 6774f34eee | |||
| 6d0402896d | |||
| 344021a3d9 | |||
| a9b85ab27d | |||
| 17204a46e4 | |||
| 7c4d415462 | |||
| b8985b6d72 | |||
| c669473f88 | |||
| 8751435bf5 | |||
| 30ae210761 | |||
| 3eb693f58b | |||
| 7b4a31c5ac | |||
| 1527c8cf88 | |||
| ab0a7ae666 | |||
| 11ec2f1f21 | |||
| 2c6808d37e | |||
| 2e96382c49 | |||
| 0239e440ca | |||
| 76b972daff | |||
| bc3ef3e7dd | |||
| 9839291dd0 | |||
| 17cba328d6 | |||
| 86093765d8 | |||
| 05fa1c8ae8 | |||
| e8a40d6b85 | |||
| c265e0bb85 | |||
| c6b118788b | |||
| b004f6dbdc | |||
| cd4930b680 | |||
| f861aca628 | |||
| 1ff0d8e2ee | |||
| d76d70764e | |||
| dc73ce0b71 | |||
| 3f666748af | |||
| 8de8a8838c | |||
| f563b7eb71 | |||
| 3c18b83708 | |||
| bd35f5904b | |||
| 474f2737f6 | |||
| ea1ac4a212 | |||
| 165d7c7b36 | |||
| b53cbb2e18 | |||
| b95f8c5929 | |||
| e564bb0b85 | |||
| e2ec8a7b13 | |||
| 75c6f0f10e | |||
| af38044b42 | |||
| 684fa846d8 | |||
| 416e722855 | |||
| 9fd4613bbb | |||
| a0362e3c5f | |||
| e32ef1cdc0 | |||
| 20673ec8a6 | |||
| 53fbdf7329 | |||
| 6f72b74fac | |||
| e83705a32e | |||
| 95d36b9ba2 | |||
| b9e74254bd | |||
| b4405b200d | |||
| a8a3fdac54 | |||
| 4eddc622a7 | |||
| 4e42ada240 | |||
| 6266217655 | |||
| ea09d32e98 | |||
| 15958c88d3 | |||
| b80d7fb5ea | |||
| 1c3d25c6ff | |||
| c9f22b32c7 | |||
| c0524985bb | |||
| 83839c7ea7 | |||
| c1a1d4b4c3 | |||
| b5af7a905a | |||
| 96b1b2f55f | |||
| 9361ba5d70 | |||
| 4e9014311e | |||
| 232873fc70 | |||
| 0a61fca9c9 | |||
| 480bdc34dc | |||
| 716b475a13 | |||
| b1ec5ec034 | |||
| 470613c2b7 | |||
| 336c5b4154 | |||
| abdb9dc750 | |||
| ab98683973 | |||
| 186c24dbe6 | |||
| 9cdf6340a1 | |||
| 2855b1341c | |||
| cf28f99976 | |||
| a39d67dc47 | |||
| 7e75257f12 | |||
| df38449553 | |||
| 1385ef3bc5 | |||
| 7c23c11c51 | |||
| aeff2e12ec | |||
| 7274899671 | |||
| 6c00fd608f | |||
| df6a034c11 | |||
| acb109c290 | |||
| b227b00249 | |||
| 3ec9d6c18a | |||
| 86db8b95f0 | |||
| 4cfff8a35f | |||
| 962fedf48c | |||
| 04494414d1 | |||
| 0b83576e9b | |||
| ce4ef0f817 | |||
| f0b15c1c56 | |||
| f898e9fdfe | |||
| 8ee7804b0e | |||
| 923228194e | |||
| 8837b2e3f6 | |||
| f48c9dc1c2 | |||
| 74aa07a8cd | |||
| 5236e4860f | |||
| 3d174da1c3 | |||
| 8faa40f3a3 | |||
| 3e03275f28 | |||
| 7595cf8a25 | |||
| 2675d43adb | |||
| d9f4ce82e6 | |||
| 648a1845d8 | |||
| a87eff6d1c | |||
| dd6ad37e23 | |||
| c5e778b939 | |||
| a9ab81a77a | |||
| 2d40e1d8e5 | |||
| 7c8f367a5d | |||
| ff4cf558aa | |||
| 0e151e51bc | |||
| 60cc0031b0 | |||
| d24cac0998 | |||
| 45d110830c | |||
| ea3a9ae911 | |||
| 6bff8c0e7c | |||
| bc6b8802b8 | |||
| 252ef572d3 | |||
| 414d397e3f | |||
| 11b7b3789d | |||
| 871ac53717 | |||
| 64ea66b6e6 | |||
| 6d7c6759b3 | |||
| 4cea013570 | |||
| eb375c0587 | |||
| 7fd8a5a4bd | |||
| b3c90216bb | |||
| 10f345f956 | |||
| 956d2c36d0 | |||
| 55e688b6f2 | |||
| f017954027 | |||
| 7e992d11b1 |
@@ -2,5 +2,6 @@ Wen
|
||||
REGIST
|
||||
PullRequest
|
||||
cancelled
|
||||
indeces
|
||||
FOF
|
||||
NoO
|
||||
|
||||
@@ -93,14 +93,6 @@ jobs:
|
||||
const { owner, repo } = context.repo;
|
||||
const upstream = `${owner}/${repo}`;
|
||||
|
||||
const closed = context.payload.pull_request;
|
||||
if (closed) {
|
||||
if (closed.head.repo?.full_name === upstream) {
|
||||
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for await (const response of github.paginate.iterator(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
|
||||
Generated
+7
@@ -21,5 +21,12 @@
|
||||
</clean>
|
||||
</configuration>
|
||||
</target>
|
||||
<target id="f2590b2b-9b93-49f9-8510-da3f3724a2ae" name="replay" defaultType="TOOL">
|
||||
<configuration id="d475264f-6f4c-4092-9b4e-6773309f38b7" name="replay" toolchainName="Default">
|
||||
<build type="TOOL">
|
||||
<tool actionId="Tool_External Tools_uv build tools replay" />
|
||||
</build>
|
||||
</configuration>
|
||||
</target>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -20,4 +20,11 @@
|
||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||
</exec>
|
||||
</tool>
|
||||
<tool name="uv build tools replay" showInMainMenu="false" showInEditor="false" showInProject="false" showInSearchPopup="false" disabled="false" useConsole="true" showConsoleOnStdOut="false" showConsoleOnStdErr="false" synchronizeAfterRun="true">
|
||||
<exec>
|
||||
<option name="COMMAND" value="bash" />
|
||||
<option name="PARAMETERS" value="-c "source .venv/bin/activate && scons -u -j$(nproc) tools/replay/"" />
|
||||
<option name="WORKING_DIRECTORY" value="$ProjectFileDir$" />
|
||||
</exec>
|
||||
</tool>
|
||||
</toolSet>
|
||||
@@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="sunnypilot" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
|
||||
<configuration default="false" name="Build Debug" type="CLionExternalRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/selfdrive/ui" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="uv Scons Build Debug" CONFIG_NAME="uv Scons Build Debug" RUN_PATH="ui">
|
||||
<envs>
|
||||
<env name="QT_DBL_CLICK_DIST" value="150" />
|
||||
</envs>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Debug Route Controls" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="openpilot-special" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="FINGERPRINT" value="KIA_EV9" />
|
||||
<env name="SKIP_FW_QUERY" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/selfdrive/car" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/selfdrive/car/card.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="true" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Replay for controls + ui" type="Multirun" separateTabs="false" reuseTabsWithFailures="false" startOneByOne="true" markFailedProcess="true" hideSuccessProcess="false" delayTime="0.0">
|
||||
<runConfiguration name="replay for controls" type="Native Application" />
|
||||
<runConfiguration name="Build Debug" type="Custom Build Application" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="replay for controls" type="CLionNativeAppRunConfigurationType" focusToolWindowBeforeRun="true" PROGRAM_PARAMS=""$Prompt$" --block "sendcan,carState,carParams,carOutput,liveTracks,carParamsSP,carStateSP,bookmarkButton"" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="true" WORKING_DIR="file://$ProjectFileDir$/tools/replay" PASS_PARENT_ENVS_2="true" PROJECT_NAME="openpilot-special" TARGET_NAME="replay" CONFIG_NAME="replay" version="1" RUN_PATH="replay">
|
||||
<method v="2">
|
||||
<option name="CLION.COMPOUND.BUILD" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -166,11 +166,6 @@ env = Environment(
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
)
|
||||
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
|
||||
if arch == "Darwin":
|
||||
env["RPATHPREFIX"] = "-Wl,-rpath,"
|
||||
env["RPATHSUFFIX"] = ""
|
||||
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
|
||||
if arch != "larch64":
|
||||
env['_LIBFLAGS'] = _libflags
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="18.5"
|
||||
export AGNOS_VERSION="18.4"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 9a2ffb0c53...18965814e8
@@ -812,7 +812,6 @@ struct SelfdriveState {
|
||||
promptDistracted @8;
|
||||
|
||||
preAlert @9;
|
||||
complete @10;
|
||||
}
|
||||
|
||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||
@@ -2161,8 +2160,7 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
||||
|
||||
struct DriverMonitoringState {
|
||||
lockout @0 :Bool;
|
||||
lockoutCount @15 :Int8;
|
||||
lockoutMinutesRemaining @11 :Int8;
|
||||
lockoutRecoveryPercent @11 :Int8;
|
||||
alert3Count @12 :Int8;
|
||||
noResponseCount @13 :Int8;
|
||||
noResponseForceDecel @14 :Bool;
|
||||
|
||||
@@ -7,43 +7,12 @@ import os
|
||||
import capnp
|
||||
import time
|
||||
|
||||
from typing import Union
|
||||
from typing import Optional, List, Union, Dict
|
||||
|
||||
from openpilot.cereal import log
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
from openpilot.common.utils import MovingAverage
|
||||
|
||||
__all__ = (
|
||||
"NO_TRAVERSAL_LIMIT",
|
||||
"Context",
|
||||
"FrequencyTracker",
|
||||
"IpcError",
|
||||
"MultiplePublishersError",
|
||||
"Poller",
|
||||
"PubMaster",
|
||||
"PubSocket",
|
||||
"SocketEventHandle",
|
||||
"SubMaster",
|
||||
"SubSocket",
|
||||
"delete_fake_prefix",
|
||||
"drain_sock",
|
||||
"drain_sock_raw",
|
||||
"fake_event_handle",
|
||||
"get_fake_prefix",
|
||||
"log_from_bytes",
|
||||
"new_message",
|
||||
"pub_sock",
|
||||
"recv_one",
|
||||
"recv_one_or_none",
|
||||
"recv_one_retry",
|
||||
"recv_sock",
|
||||
"reset_context",
|
||||
"set_fake_prefix",
|
||||
"sub_sock",
|
||||
"toggle_fake_events",
|
||||
"wait_for_one_event",
|
||||
)
|
||||
|
||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||
|
||||
|
||||
@@ -53,8 +22,8 @@ def pub_sock(endpoint: str) -> PubSocket:
|
||||
return msgq.pub_sock(endpoint, segment_size)
|
||||
|
||||
|
||||
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: int | None = None) -> SubSocket:
|
||||
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
|
||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
||||
service = SERVICE_LIST.get(endpoint)
|
||||
segment_size = service.queue_size if service else 0
|
||||
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
|
||||
@@ -70,7 +39,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
|
||||
return msg
|
||||
|
||||
|
||||
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||
args = {
|
||||
'valid': False,
|
||||
'logMonoTime': int(time.monotonic() * 1e9),
|
||||
@@ -85,14 +54,14 @@ def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp
|
||||
return dat
|
||||
|
||||
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]:
|
||||
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Receive all message currently available on the queue"""
|
||||
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
|
||||
return [log_from_bytes(m) for m in msgs]
|
||||
|
||||
|
||||
# TODO: print when we drop packets?
|
||||
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
|
||||
dat = None
|
||||
|
||||
@@ -113,14 +82,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicSt
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive()
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
return dat
|
||||
|
||||
|
||||
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
||||
dat = sock.receive(non_blocking=True)
|
||||
if dat is not None:
|
||||
dat = log_from_bytes(dat)
|
||||
@@ -179,27 +148,27 @@ class FrequencyTracker:
|
||||
|
||||
|
||||
class SubMaster:
|
||||
def __init__(self, services: list[str], poll: str | None = None,
|
||||
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
|
||||
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
|
||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
||||
self.frame = -1
|
||||
self.services = services
|
||||
self.seen = dict.fromkeys(services, False)
|
||||
self.updated = dict.fromkeys(services, False)
|
||||
self.recv_time = dict.fromkeys(services, 0.0)
|
||||
self.recv_frame = dict.fromkeys(services, 0)
|
||||
self.seen = {s: False for s in services}
|
||||
self.updated = {s: False for s in services}
|
||||
self.recv_time = {s: 0. for s in services}
|
||||
self.recv_frame = {s: 0 for s in services}
|
||||
self.sock = {}
|
||||
self.data = {}
|
||||
self.logMonoTime = dict.fromkeys(services, 0)
|
||||
self.logMonoTime = {s: 0 for s in services}
|
||||
|
||||
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
|
||||
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
|
||||
self.static_freq_services = {s for s in services if not on_demand[s]}
|
||||
self.static_freq_services = set(s for s in services if not on_demand[s])
|
||||
self.alive = {s: on_demand[s] for s in services}
|
||||
self.freq_ok = {s: on_demand[s] for s in services}
|
||||
self.valid = {s: on_demand[s] for s in services}
|
||||
|
||||
self.freq_tracker: dict[str, FrequencyTracker] = {}
|
||||
self.freq_tracker: Dict[str, FrequencyTracker] = {}
|
||||
self.poller = Poller()
|
||||
polled_services = set([poll, ] if poll is not None else services)
|
||||
self.non_polled_services = set(services) - polled_services
|
||||
@@ -242,7 +211,7 @@ class SubMaster:
|
||||
msgs.append(recv_one_or_none(self.sock[s]))
|
||||
self.update_msgs(time.monotonic(), msgs)
|
||||
|
||||
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
|
||||
self.frame += 1
|
||||
self.updated = dict.fromkeys(self.services, False)
|
||||
for msg in msgs:
|
||||
@@ -265,21 +234,21 @@ class SubMaster:
|
||||
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
|
||||
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
|
||||
|
||||
def all_alive(self, service_list: list[str] | None = None) -> bool:
|
||||
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
|
||||
|
||||
def all_freq_ok(self, service_list: list[str] | None = None) -> bool:
|
||||
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
|
||||
|
||||
def all_valid(self, service_list: list[str] | None = None) -> bool:
|
||||
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
|
||||
|
||||
def all_checks(self, service_list: list[str] | None = None) -> bool:
|
||||
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
|
||||
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
|
||||
|
||||
|
||||
class PubMaster:
|
||||
def __init__(self, services: list[str]):
|
||||
def __init__(self, services: List[str]):
|
||||
self.sock = {}
|
||||
for s in services:
|
||||
self.sock[s] = pub_sock(s)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import capnp
|
||||
import multiprocessing
|
||||
import numbers
|
||||
@@ -5,6 +6,7 @@ import random
|
||||
import threading
|
||||
import time
|
||||
from openpilot.common.parameterized import parameterized
|
||||
import pytest
|
||||
|
||||
from openpilot.cereal import log
|
||||
from opendbc.car.structs import car
|
||||
@@ -22,6 +24,10 @@ def random_socks(num_socks=10):
|
||||
def random_bytes(length=1000):
|
||||
return bytes([random.randrange(0xFF) for _ in range(length)])
|
||||
|
||||
def zmq_sleep(t=1):
|
||||
if "ZMQ" in os.environ:
|
||||
time.sleep(t)
|
||||
|
||||
|
||||
# TODO: this should take any capnp struct and returrn a msg with random populated data
|
||||
def random_carstate():
|
||||
@@ -47,6 +53,16 @@ def delayed_send(delay, sock, dat):
|
||||
|
||||
|
||||
class TestMessaging:
|
||||
def setUp(self):
|
||||
# TODO: ZMQ tests are too slow; all sleeps will need to be
|
||||
# replaced with logic to block on the necessary condition
|
||||
if "ZMQ" in os.environ:
|
||||
pytest.skip()
|
||||
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep()
|
||||
|
||||
@parameterized.expand(events)
|
||||
def test_new_message(self, evt):
|
||||
try:
|
||||
@@ -73,6 +89,7 @@ class TestMessaging:
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msgs in queue
|
||||
msgs = func(sub_sock)
|
||||
@@ -93,6 +110,7 @@ class TestMessaging:
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=100)
|
||||
zmq_sleep()
|
||||
|
||||
# no wait and no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_sock(sub_sock)
|
||||
@@ -111,6 +129,7 @@ class TestMessaging:
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=1000)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket should timeout
|
||||
recvd = messaging.recv_one(sub_sock)
|
||||
@@ -123,10 +142,12 @@ class TestMessaging:
|
||||
assert isinstance(recvd, capnp._DynamicStructReader)
|
||||
assert_carstate(msg.carState, recvd.carState)
|
||||
|
||||
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
|
||||
def test_recv_one_or_none(self):
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock)
|
||||
zmq_sleep()
|
||||
|
||||
# no msg in queue, socket shouldn't block
|
||||
recvd = messaging.recv_one_or_none(sub_sock)
|
||||
@@ -144,13 +165,16 @@ class TestMessaging:
|
||||
sock_timeout = 0.1
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
|
||||
zmq_sleep()
|
||||
|
||||
# wait 5 socket timeouts and make sure it's still retrying
|
||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||
p.start()
|
||||
time.sleep(sock_timeout*5)
|
||||
assert p.is_alive()
|
||||
p.terminate()
|
||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
||||
if "ZMQ" not in os.environ:
|
||||
# wait 5 socket timeouts and make sure it's still retrying
|
||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||
p.start()
|
||||
time.sleep(sock_timeout*5)
|
||||
assert p.is_alive()
|
||||
p.terminate()
|
||||
|
||||
# wait 5 socket timeouts before sending
|
||||
msg = random_carstate()
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import random
|
||||
import time
|
||||
from typing import cast
|
||||
from collections.abc import Sized
|
||||
from typing import Sized, cast
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
|
||||
random_bytes, random_carstate, assert_carstate
|
||||
random_bytes, random_carstate, assert_carstate, \
|
||||
zmq_sleep
|
||||
from openpilot.cereal.services import SERVICE_LIST
|
||||
|
||||
|
||||
class TestSubMaster:
|
||||
|
||||
def setup_method(self):
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
def test_init(self):
|
||||
sm = messaging.SubMaster(events)
|
||||
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
|
||||
@@ -37,6 +42,7 @@ class TestSubMaster:
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
msg = random_carstate()
|
||||
pub_sock.send(msg.to_bytes())
|
||||
@@ -48,6 +54,7 @@ class TestSubMaster:
|
||||
sock = "carState"
|
||||
pub_sock = messaging.pub_sock(sock)
|
||||
sm = messaging.SubMaster([sock,])
|
||||
zmq_sleep()
|
||||
|
||||
for i in range(10):
|
||||
msg = messaging.new_message(sock)
|
||||
@@ -84,13 +91,21 @@ class TestSubMaster:
|
||||
|
||||
for service, (max_freq, min_freq) in checks.items():
|
||||
if max_freq is not None:
|
||||
assert min_freq is not None
|
||||
assert sm._check_avg_freq(service)
|
||||
assert sm.freq_tracker[service].max_freq == max_freq*1.2
|
||||
assert sm.freq_tracker[service].min_freq == min_freq*0.8
|
||||
else:
|
||||
assert not sm._check_avg_freq(service)
|
||||
|
||||
def test_alive(self):
|
||||
pass
|
||||
|
||||
def test_ignore_alive(self):
|
||||
pass
|
||||
|
||||
def test_valid(self):
|
||||
pass
|
||||
|
||||
# SubMaster should always conflate
|
||||
def test_conflate(self):
|
||||
sock = "carState"
|
||||
@@ -109,6 +124,11 @@ class TestSubMaster:
|
||||
|
||||
class TestPubMaster:
|
||||
|
||||
def setup_method(self):
|
||||
# ZMQ pub socket takes too long to die
|
||||
# sleep to prevent multiple publishers error between tests
|
||||
zmq_sleep(3)
|
||||
|
||||
def test_init(self):
|
||||
messaging.PubMaster(events)
|
||||
|
||||
@@ -116,6 +136,7 @@ class TestPubMaster:
|
||||
socks = random_socks()
|
||||
pm = messaging.PubMaster(socks)
|
||||
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
|
||||
zmq_sleep()
|
||||
|
||||
# PubMaster accepts either a capnp msg builder or bytes
|
||||
for capnp in [True, False]:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# TODO: this should be automatically determined using the capnp schema
|
||||
@@ -10,7 +11,7 @@ class QueueSize(IntEnum):
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self, should_log: bool, frequency: float, decimation: int | None = None,
|
||||
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
|
||||
queue_size: QueueSize = QueueSize.SMALL):
|
||||
self.should_log = should_log
|
||||
self.frequency = frequency
|
||||
@@ -124,7 +125,8 @@ def build_header():
|
||||
for k, v in SERVICE_LIST.items():
|
||||
should_log = "true" if v.should_log else "false"
|
||||
decimation = -1 if v.decimation is None else v.decimation
|
||||
h += f' {{ "{k}", {{"{k}", {should_log}, {v.frequency:f}, {decimation:d}, {v.queue_size:d}}}}},\n'
|
||||
h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
|
||||
(k, k, should_log, v.frequency, decimation, v.queue_size)
|
||||
h += "};\n"
|
||||
|
||||
h += "#endif\n"
|
||||
|
||||
@@ -38,7 +38,6 @@ class BaseApi:
|
||||
}
|
||||
if payload_extra is not None:
|
||||
payload.update(payload_extra)
|
||||
assert self.private_key is not None
|
||||
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
|
||||
if isinstance(token, bytes):
|
||||
token = token.decode('utf8')
|
||||
|
||||
@@ -42,26 +42,25 @@ def get_existing_chunks(path):
|
||||
class ChunkStream(io.RawIOBase):
|
||||
def __init__(self, paths):
|
||||
self._paths = iter(paths)
|
||||
self._f = None
|
||||
self._buf = memoryview(b'')
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def readinto(self, b):
|
||||
n = 0
|
||||
view = memoryview(b)
|
||||
while n < len(b):
|
||||
if self._f is None:
|
||||
if not self._buf:
|
||||
p = next(self._paths, None)
|
||||
if p is None:
|
||||
break
|
||||
self._f = open(p, 'rb')
|
||||
count = self._f.readinto(view[n:])
|
||||
if not count:
|
||||
self._f.close()
|
||||
self._f = None
|
||||
with open(p, 'rb') as f:
|
||||
self._buf = memoryview(f.read())
|
||||
continue
|
||||
n += count
|
||||
take = min(len(b) - n, len(self._buf))
|
||||
b[n:n + take] = self._buf[:take]
|
||||
self._buf = self._buf[take:]
|
||||
n += take
|
||||
return n
|
||||
|
||||
def open_file_chunked(path):
|
||||
|
||||
@@ -56,28 +56,28 @@
|
||||
},
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz",
|
||||
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
|
||||
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
||||
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
||||
"size": 17487872,
|
||||
"sparse": false,
|
||||
"full_check": true,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626"
|
||||
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz",
|
||||
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539",
|
||||
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
|
||||
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
|
||||
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
||||
"size": 4718592000,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134",
|
||||
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
|
||||
"alt": {
|
||||
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img",
|
||||
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
|
||||
"size": 4718592000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class StreamingDecompressor:
|
||||
def __init__(self, url: str) -> None:
|
||||
self.buf = b""
|
||||
|
||||
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': 'identity'}, timeout=60)
|
||||
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60)
|
||||
self.it = self.req.iter_content(chunk_size=1024 * 1024)
|
||||
self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO)
|
||||
self.eof = False
|
||||
|
||||
@@ -339,7 +339,7 @@ class Tici(HardwareBase):
|
||||
|
||||
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
|
||||
gpio_init(GPIO.SOM_ST_IO, True)
|
||||
gpio_set(GPIO.SOM_ST_IO, True)
|
||||
gpio_set(GPIO.SOM_ST_IO, 1)
|
||||
|
||||
# *** IRQ config ***
|
||||
|
||||
@@ -389,21 +389,21 @@ class Tici(HardwareBase):
|
||||
gpio_init(GPIO.STM_RST_N, True)
|
||||
gpio_init(GPIO.STM_BOOT0, True)
|
||||
|
||||
gpio_set(GPIO.STM_RST_N, True)
|
||||
gpio_set(GPIO.STM_BOOT0, False)
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
gpio_set(GPIO.STM_BOOT0, 0)
|
||||
time.sleep(0.01)
|
||||
gpio_set(GPIO.STM_RST_N, False)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
|
||||
def recover_internal_panda(self):
|
||||
gpio_init(GPIO.STM_RST_N, True)
|
||||
gpio_init(GPIO.STM_BOOT0, True)
|
||||
|
||||
gpio_set(GPIO.STM_RST_N, True)
|
||||
gpio_set(GPIO.STM_BOOT0, True)
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
gpio_set(GPIO.STM_BOOT0, 1)
|
||||
time.sleep(0.01)
|
||||
gpio_set(GPIO.STM_RST_N, False)
|
||||
gpio_set(GPIO.STM_RST_N, 0)
|
||||
time.sleep(0.01)
|
||||
gpio_set(GPIO.STM_BOOT0, False)
|
||||
gpio_set(GPIO.STM_BOOT0, 0)
|
||||
|
||||
def booted(self):
|
||||
# this normally boots within 8s, but on rare occasions takes 30+s
|
||||
|
||||
@@ -52,7 +52,7 @@ PPPD_CMD = [
|
||||
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
|
||||
"user", '""', "password", '""',
|
||||
]
|
||||
INITIAL_STATE: dict[str, object] = {
|
||||
INITIAL_STATE = {
|
||||
"seconds_since_boot": 0,
|
||||
"state": "INITIALIZING",
|
||||
"connected": False, "ip_address": "",
|
||||
|
||||
@@ -37,7 +37,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||
{"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}},
|
||||
{"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY | BACKUP, BOOL}},
|
||||
{"ExperimentalMode", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"ExperimentalModeConfirmed", {PERSISTENT | BACKUP, BOOL}},
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import numpy as np
|
||||
from collections.abc import Sequence
|
||||
|
||||
Gain = int | float | tuple[Sequence[float], Sequence[float]] | list[list[float]]
|
||||
from numbers import Number
|
||||
|
||||
class PIDController:
|
||||
def __init__(self, k_p: Gain, k_i: Gain, k_d: Gain = 0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
||||
self._k_p = ([0], [k_p]) if isinstance(k_p, (int, float)) else k_p
|
||||
self._k_i = ([0], [k_i]) if isinstance(k_i, (int, float)) else k_i
|
||||
self._k_d = ([0], [k_d]) if isinstance(k_d, (int, float)) else k_d
|
||||
def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
||||
self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p
|
||||
self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i
|
||||
self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d
|
||||
|
||||
self.set_limits(pos_limit, neg_limit)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
|
||||
self.log_files = self.get_existing_logfiles()
|
||||
log_indexes = [f.split(".")[-1] for f in self.log_files]
|
||||
self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1])
|
||||
self.last_rollover = 0.0
|
||||
self.last_rollover = None
|
||||
self.doRollover()
|
||||
|
||||
def _open(self):
|
||||
@@ -39,7 +39,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
|
||||
return stream
|
||||
|
||||
def get_existing_logfiles(self):
|
||||
log_files = []
|
||||
log_files = list()
|
||||
base_dir = os.path.dirname(self.base_filename)
|
||||
for fn in os.listdir(base_dir):
|
||||
fp = os.path.join(base_dir, fn)
|
||||
|
||||
@@ -112,14 +112,14 @@ class TestParams:
|
||||
def test_params_default_value(self):
|
||||
self.params.remove("LanguageSetting")
|
||||
self.params.remove("LongitudinalPersonality")
|
||||
self.params.remove("LiveParametersV2")
|
||||
self.params.remove("LiveParameters")
|
||||
|
||||
assert self.params.get("LanguageSetting") is None
|
||||
assert self.params.get("LanguageSetting", return_default=False) is None
|
||||
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
|
||||
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
|
||||
assert self.params.get("LiveParametersV2") is None
|
||||
assert self.params.get("LiveParametersV2", return_default=True) is None
|
||||
assert self.params.get("LiveParameters") is None
|
||||
assert self.params.get("LiveParameters", return_default=True) is None
|
||||
|
||||
def test_params_get_type(self):
|
||||
# json
|
||||
|
||||
@@ -52,7 +52,7 @@ _ar_ox_config = DeviceCameraConfig(CameraConfig(1928, 1208, 2648.0), _ar_ox_fish
|
||||
_os_config = DeviceCameraConfig(CameraConfig(2688 // 2, 1520 // 2, 1522.0 * 3 / 4), _os_fisheye, _os_fisheye)
|
||||
_neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig())
|
||||
|
||||
DEVICE_CAMERAS: dict[tuple[str, str], DeviceCameraConfig] = {
|
||||
DEVICE_CAMERAS = {
|
||||
# A "device camera" is defined by a device type and sensor
|
||||
|
||||
# sensor type was never set on eon/neo/two
|
||||
@@ -176,3 +176,4 @@ def img_from_device(pt_device):
|
||||
|
||||
pt_img = pt_view/pt_view[:, 2:3]
|
||||
return pt_img.reshape(input_shape)[:, :2]
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ SP_BRANCH_MIGRATIONS = {
|
||||
("tizi", "release3-staging"): "release-tizi-staging",
|
||||
("mici", "release3"): "release-mici",
|
||||
("mici", "release3-staging"): "release-mici-staging",
|
||||
("tici", "hkg-angle-steering-2025"): "hkg-angle-steering-2025-tici",
|
||||
("tici", "hkg-angle-steering-2025-prebuilt"): "hkg-angle-steering-2025-tici-prebuilt"
|
||||
}
|
||||
|
||||
BUILD_METADATA_FILENAME = "build.json"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3027da9834adf9c71177bc3086f8de3018ce22e4725b694d635723c5b3c860cd
|
||||
size 97120
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ca29ff46fbd6d00cc02596de8c9abfbf1cacdc6a7b2f98b27a74d24c790da004
|
||||
size 52374
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f47633b5082b911e79dd13fc2d1f4c2a9d44d2fe0860f3d390ffc4e3d772f7d
|
||||
size 97120
|
||||
oid sha256:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013
|
||||
size 48044
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:538271820d618046009bb456e930598f2736dd1a6174c11925ec3912581b05c1
|
||||
size 52374
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:634fad6590295ef2e73d5f7e5aecbb5dd245a0f7692f1e300c2ebac95aabb8e3
|
||||
size 73026
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a380fe0a022856b302841a0dd19b71eab318c6b08ec851f128942758a7f1631a
|
||||
size 97120
|
||||
oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a
|
||||
size 48044
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc9e67cfaba77e8e4f4049f0add9238660e4cf8f886c6915600d8f47a30c98db
|
||||
size 97120
|
||||
oid sha256:2c0af7f5fe57bb36ab96fae868e20feca763541c97a61b3a3a84a0e7fcb81163
|
||||
size 83350
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad19268e4aaaeac8dd21f6b26c16a121e7b3f50bba867748e7226727643ae682
|
||||
size 144642
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:412ef25d2fb103c1ebd55c667313a5921493305fb4e1f4e1dafc08d3b95d86ab
|
||||
size 73026
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:081d64ca28a84a59ff5e6aea4732865b8687585938b8a287bc7e6a49d0646508
|
||||
size 97120
|
||||
oid sha256:4af81cbf1d96a42cc351878b015298aee82874b46baaf1a615ca91ec36c0ced6
|
||||
size 83228
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1bb440f424c989c06e203bfba0b32d39aede8f6c78a9b335e5e778460202b601
|
||||
size 73026
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5a390831afca3bfc6ea3c2739b872ebf866e70df8ae30653f8587e5cd3993959
|
||||
size 68306
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:67e636d072703e6b1233a12344c0a6304fd43d64dbb31c66b71c2c8870a339c1
|
||||
size 153764
|
||||
@@ -3,7 +3,7 @@ import copy
|
||||
import os
|
||||
import pytest
|
||||
import random
|
||||
import unittest
|
||||
import unittest # noqa: TID251
|
||||
from collections import defaultdict, Counter
|
||||
import hypothesis.strategies as st
|
||||
from hypothesis import Phase, given, settings
|
||||
@@ -22,7 +22,6 @@ from openpilot.selfdrive.pandad import can_capnp_to_list
|
||||
from openpilot.selfdrive.test.helpers import read_segment_list
|
||||
from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
|
||||
from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source, internal_source, comma_api_source
|
||||
from openpilot.tools.lib.file_sources import Source
|
||||
from openpilot.tools.lib.route import SegmentName
|
||||
|
||||
SafetyModel = car.CarParams.SafetyModel
|
||||
@@ -132,7 +131,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
segment_range = f"{cls.test_route.route}/{seg}"
|
||||
|
||||
try:
|
||||
sources: list[Source] = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
|
||||
sources = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
|
||||
lr = LogReader(segment_range, sources=sources, sort_by_time=True)
|
||||
return cls.get_testing_data_from_logreader(lr)
|
||||
except (LogsUnavailable, AssertionError):
|
||||
@@ -258,7 +257,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
# Don't check relay malfunction on disabled routes (relay closed),
|
||||
# or before fingerprinting is done (elm327 and noOutput)
|
||||
if self.car_safety_mode_frame is not None and t / 1e4 > self.car_safety_mode_frame:
|
||||
if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame:
|
||||
self.assertFalse(self.safety.get_relay_malfunction())
|
||||
else:
|
||||
self.safety.set_relay_malfunction(False)
|
||||
|
||||
@@ -147,7 +147,6 @@ class Controls(ControlsExt):
|
||||
lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||
|
||||
actuators.curvature = self.desired_curvature
|
||||
assert self.calibrated_pose is not None
|
||||
steer, lateral_output, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
||||
self.steer_limited_by_safety, self.desired_curvature,
|
||||
self.calibrated_pose, curvature_limited, lat_delay)
|
||||
|
||||
@@ -15,9 +15,6 @@ MAX_LATERAL_JERK = 5.0 # m/s^3
|
||||
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
|
||||
|
||||
|
||||
def should_stop(v_ego: float, a_target: float) -> bool:
|
||||
return bool(v_ego < 0.25 and a_target < 0.1)
|
||||
|
||||
def clamp(val, min_val, max_val):
|
||||
clamped_val = float(np.clip(val, min_val, max_val))
|
||||
return clamped_val, clamped_val != val
|
||||
@@ -43,7 +40,7 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, b
|
||||
return float(new_curvature), limited_accel or limited_max_curv
|
||||
|
||||
|
||||
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
|
||||
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.3):
|
||||
if len(speeds) == len(t_idxs):
|
||||
v_now = speeds[0]
|
||||
a_now = accels[0]
|
||||
@@ -56,7 +53,8 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
|
||||
v_now = 0.0
|
||||
v_target = 0.0
|
||||
a_target = 0.0
|
||||
return a_target, should_stop(v_now, a_target)
|
||||
should_stop = (v_now < vEgoStopping and a_target < 0.1)
|
||||
return a_target, should_stop
|
||||
|
||||
def curv_from_psis(psi_target, psi_rate, vego, action_t):
|
||||
vego = np.clip(vego, MIN_SPEED, np.inf)
|
||||
|
||||
@@ -10,7 +10,7 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
|
||||
LongCtrlState = car.CarControl.Actuators.LongControlState
|
||||
|
||||
|
||||
def long_control_state_trans(CP_SP, active, long_control_state,
|
||||
def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
|
||||
should_stop, brake_pressed, cruise_standstill):
|
||||
# Gas Interceptor
|
||||
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
|
||||
@@ -26,17 +26,22 @@ def long_control_state_trans(CP_SP, active, long_control_state,
|
||||
if long_control_state == LongCtrlState.off:
|
||||
if not starting_condition:
|
||||
long_control_state = LongCtrlState.stopping
|
||||
elif CP.startingState:
|
||||
long_control_state = LongCtrlState.starting
|
||||
else:
|
||||
long_control_state = LongCtrlState.pid
|
||||
|
||||
elif long_control_state == LongCtrlState.stopping:
|
||||
if starting_condition:
|
||||
if starting_condition and CP.startingState:
|
||||
long_control_state = LongCtrlState.starting
|
||||
elif starting_condition:
|
||||
long_control_state = LongCtrlState.pid
|
||||
|
||||
elif long_control_state == LongCtrlState.pid:
|
||||
elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
|
||||
if should_stop:
|
||||
long_control_state = LongCtrlState.stopping
|
||||
|
||||
elif v_ego > CP.vEgoStarting:
|
||||
long_control_state = LongCtrlState.pid
|
||||
return long_control_state
|
||||
|
||||
class LongControl:
|
||||
@@ -57,7 +62,7 @@ class LongControl:
|
||||
self.pid.neg_limit = accel_limits[0]
|
||||
self.pid.pos_limit = accel_limits[1]
|
||||
|
||||
self.long_control_state = long_control_state_trans(self.CP_SP, active, self.long_control_state,
|
||||
self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo,
|
||||
should_stop, CS.brakePressed,
|
||||
CS.cruiseState.standstill)
|
||||
if self.long_control_state == LongCtrlState.off:
|
||||
@@ -68,8 +73,11 @@ class LongControl:
|
||||
output_accel = self.last_output_accel
|
||||
if output_accel > self.CP.stopAccel:
|
||||
output_accel = min(output_accel, 0.0)
|
||||
# TODO: can we just go straight to stopAccel?
|
||||
output_accel -= 1.0 * DT_CTRL # m/s^2/s while trying to stop
|
||||
output_accel -= self.CP.stoppingDecelRate * DT_CTRL
|
||||
self.reset()
|
||||
|
||||
elif self.long_control_state == LongCtrlState.starting:
|
||||
output_accel = self.CP.startAccel
|
||||
self.reset()
|
||||
|
||||
else: # LongCtrlState.pid
|
||||
|
||||
@@ -137,7 +137,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
|
||||
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
|
||||
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
|
||||
action_t=action_t)
|
||||
action_t=action_t, vEgoStopping=self.CP.vEgoStopping)
|
||||
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
|
||||
output_should_stop_e2e = sm['modelV2'].action.shouldStop
|
||||
|
||||
|
||||
@@ -1,43 +1,60 @@
|
||||
from openpilot.cereal import custom
|
||||
from opendbc.car.structs import car
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
|
||||
|
||||
|
||||
|
||||
|
||||
class TestLongControlStateTransition:
|
||||
|
||||
def test_stay_stopped(self):
|
||||
CP = car.CarParams.new_message()
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
current_state = LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.pid
|
||||
active = False
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.off
|
||||
|
||||
def test_engage():
|
||||
CP = car.CarParams.new_message()
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
current_state = LongCtrlState.off
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=True, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=True, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=True)
|
||||
assert next_state == LongCtrlState.stopping
|
||||
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.pid
|
||||
|
||||
def test_starting():
|
||||
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
|
||||
CP_SP = custom.CarParamsSP.new_message()
|
||||
active = True
|
||||
current_state = LongCtrlState.starting
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.starting
|
||||
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
|
||||
should_stop=False, brake_pressed=False, cruise_standstill=False)
|
||||
assert next_state == LongCtrlState.pid
|
||||
|
||||
@@ -7,6 +7,8 @@ from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
LA_ERR_STD = 1.0
|
||||
INPUT_NOISE_STD = 0.08
|
||||
V_EGO = 30.0
|
||||
@@ -56,17 +58,16 @@ def simulate_straight_road_msgs(est):
|
||||
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
|
||||
est.handle_log(t, which, msg)
|
||||
|
||||
class TestTorquedLatAccelOffset:
|
||||
def test_estimated_offset(self):
|
||||
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||
msg = est.get_msg()
|
||||
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
||||
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
||||
def test_estimated_offset():
|
||||
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||
msg = est.get_msg()
|
||||
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
||||
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
||||
|
||||
def test_straight_road_roll_bias(self):
|
||||
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||
simulate_straight_road_msgs(est)
|
||||
msg = est.get_msg()
|
||||
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
|
||||
def test_straight_road_roll_bias():
|
||||
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||
simulate_straight_road_msgs(est)
|
||||
msg = est.get_msg()
|
||||
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import numpy as np
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from functools import cache
|
||||
|
||||
@@ -69,8 +68,7 @@ class NPQueue:
|
||||
|
||||
|
||||
class PointBuckets:
|
||||
def __init__(self, x_bounds: list[tuple[float, float]], min_points: Sequence[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
|
||||
self._rng = np.random.default_rng()
|
||||
def __init__(self, x_bounds: list[tuple[float, float]], min_points: list[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
|
||||
self.x_bounds = x_bounds
|
||||
self.buckets = {bounds: NPQueue(maxlen=points_per_bucket, rowsize=rowsize) for bounds in x_bounds}
|
||||
self.buckets_min_points = dict(zip(x_bounds, min_points, strict=True))
|
||||
@@ -100,9 +98,9 @@ class PointBuckets:
|
||||
points = np.vstack([x.arr for x in self.buckets.values()])
|
||||
if num_points is None:
|
||||
return points
|
||||
return points[self._rng.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
|
||||
return points[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
|
||||
|
||||
def load_points(self, points: Sequence[Sequence[float]]) -> None:
|
||||
def load_points(self, points: list[list[float]]) -> None:
|
||||
for point in points:
|
||||
self.add_point(*point)
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class LocationEstimator:
|
||||
self.observations = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
|
||||
self.observation_errors = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
|
||||
|
||||
def reset(self, t: float | None, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
|
||||
def reset(self, t: float, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
|
||||
self.kf.init_state(x_initial, covs=P_initial, filter_time=t)
|
||||
|
||||
def _validate_sensor_source(self, source: log.SensorEventData.SensorSource):
|
||||
|
||||
@@ -200,6 +200,25 @@ def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: floa
|
||||
return current_valid
|
||||
|
||||
|
||||
# TODO: Remove this function after few releases (added in 0.9.9)
|
||||
def migrate_cached_vehicle_params_if_needed(params: Params):
|
||||
last_parameters_data_old = params.get("LiveParameters")
|
||||
last_parameters_data = params.get("LiveParametersV2")
|
||||
if last_parameters_data_old is None or last_parameters_data is not None:
|
||||
return
|
||||
|
||||
try:
|
||||
last_parameters_msg = messaging.new_message('liveParameters')
|
||||
last_parameters_msg.liveParameters.valid = True
|
||||
last_parameters_msg.liveParameters.steerRatio = last_parameters_data_old['steerRatio']
|
||||
last_parameters_msg.liveParameters.stiffnessFactor = last_parameters_data_old['stiffnessFactor']
|
||||
last_parameters_msg.liveParameters.angleOffsetAverageDeg = last_parameters_data_old['angleOffsetAverageDeg']
|
||||
params.put("LiveParametersV2", last_parameters_msg.to_bytes(), block=True)
|
||||
except Exception as e:
|
||||
cloudlog.error(f"Failed to perform parameter migration: {e}")
|
||||
params.remove("LiveParameters")
|
||||
|
||||
|
||||
def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: bool, debug: bool):
|
||||
last_parameters_data = params.get("LiveParametersV2")
|
||||
last_carparams_data = params.get("CarParamsPrevRoute")
|
||||
@@ -254,6 +273,8 @@ def main():
|
||||
params = Params()
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
|
||||
migrate_cached_vehicle_params_if_needed(params)
|
||||
|
||||
steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG)
|
||||
learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial)
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ class TestLagd:
|
||||
assert retrieve_initial_lag(params, CP) is None
|
||||
|
||||
def test_ncc(self):
|
||||
rng = np.random.default_rng()
|
||||
lag_frames = random.randint(1, 19)
|
||||
|
||||
desired_sig = np.sin(np.arange(0.0, 10.0, 0.1))
|
||||
@@ -92,15 +91,15 @@ class TestLagd:
|
||||
assert np.argmax(corr) == lag_frames
|
||||
|
||||
# add some noise
|
||||
desired_sig += rng.normal(0, 0.05, len(desired_sig))
|
||||
actual_sig += rng.normal(0, 0.05, len(actual_sig))
|
||||
desired_sig += np.random.normal(0, 0.05, len(desired_sig))
|
||||
actual_sig += np.random.normal(0, 0.05, len(actual_sig))
|
||||
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
|
||||
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
|
||||
|
||||
# mask out 40% of the values, and make them noise
|
||||
mask = rng.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
|
||||
desired_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask))
|
||||
actual_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask))
|
||||
mask = np.random.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
|
||||
desired_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
|
||||
actual_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
|
||||
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
|
||||
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ def get_select_fields_data(logs):
|
||||
def sig_smooth(signal):
|
||||
return masked_symmetric_moving_average(signal, np.ones_like(signal), 5, 1.0)
|
||||
def get_nested_keys(msg, keys):
|
||||
val = msg
|
||||
val = None
|
||||
for key in keys:
|
||||
val = getattr(val, key) if isinstance(key, str) else val[key]
|
||||
val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key]
|
||||
return val
|
||||
lp = [x.livePose for x in logs if x.which() == 'livePose']
|
||||
data = defaultdict(list)
|
||||
|
||||
@@ -2,7 +2,7 @@ import random
|
||||
import numpy as np
|
||||
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params
|
||||
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
|
||||
from openpilot.selfdrive.locationd.models.car_kf import CarKalman
|
||||
from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
|
||||
from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
|
||||
@@ -30,9 +30,38 @@ class TestParamsd:
|
||||
params.put("LiveParametersV2", msg.to_bytes(), block=True)
|
||||
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
|
||||
|
||||
migrate_cached_vehicle_params_if_needed(params) # this is not tested here but should not mess anything up or throw an error
|
||||
sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
|
||||
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
|
||||
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
|
||||
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
|
||||
np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape)
|
||||
np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std)
|
||||
|
||||
# TODO Remove this test after the support for old format is removed
|
||||
def test_read_saved_params_old_format(self):
|
||||
params = Params()
|
||||
|
||||
lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
|
||||
CP = next(m for m in lr if m.which() == "carParams").carParams
|
||||
|
||||
msg = get_random_live_parameters(CP)
|
||||
params.put("LiveParameters", msg.liveParameters.to_dict(), block=True)
|
||||
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
|
||||
params.remove("LiveParametersV2")
|
||||
|
||||
migrate_cached_vehicle_params_if_needed(params)
|
||||
sr, sf, offset, _ = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
|
||||
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
|
||||
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
|
||||
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
|
||||
assert params.get("LiveParametersV2") is not None
|
||||
|
||||
def test_read_saved_params_corrupted_old_format(self):
|
||||
params = Params()
|
||||
params.put("LiveParameters", {}, block=True)
|
||||
params.remove("LiveParametersV2")
|
||||
|
||||
migrate_cached_vehicle_params_if_needed(params)
|
||||
assert params.get("LiveParameters") is None
|
||||
assert params.get("LiveParametersV2") is None
|
||||
|
||||
@@ -2,25 +2,24 @@ from opendbc.car.structs import car
|
||||
from openpilot.selfdrive.locationd.torqued import TorqueEstimator
|
||||
|
||||
|
||||
class TestTorqued:
|
||||
def test_cal_percent(self):
|
||||
est = TorqueEstimator(car.CarParams())
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == 0
|
||||
def test_cal_percent():
|
||||
est = TorqueEstimator(car.CarParams())
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == 0
|
||||
|
||||
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
|
||||
est.filtered_points.buckets_min_points.values(), strict=True):
|
||||
for _ in range(int(min_pts)):
|
||||
est.filtered_points.add_point((low + high) / 2.0, 0.0)
|
||||
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
|
||||
est.filtered_points.buckets_min_points.values(), strict=True):
|
||||
for _ in range(int(min_pts)):
|
||||
est.filtered_points.add_point((low + high) / 2.0, 0.0)
|
||||
|
||||
# enough bucket points, but not enough total points
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
|
||||
# enough bucket points, but not enough total points
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
|
||||
|
||||
# add enough points to bucket with most capacity
|
||||
key = list(est.filtered_points.buckets)[0]
|
||||
for _ in range(est.min_points_total - len(est.filtered_points)):
|
||||
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
|
||||
# add enough points to bucket with most capacity
|
||||
key = list(est.filtered_points.buckets)[0]
|
||||
for _ in range(est.min_points_total - len(est.filtered_points)):
|
||||
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
|
||||
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == 100
|
||||
msg = est.get_msg()
|
||||
assert msg.liveTorqueParameters.calPerc == 100
|
||||
|
||||
@@ -62,14 +62,14 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||
self.lag = 0.0
|
||||
self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
|
||||
if decimated:
|
||||
self.min_bucket_points: list[float] = (MIN_BUCKET_POINTS / 10).tolist()
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS / 10
|
||||
self.min_points_total = MIN_POINTS_TOTAL_QLOG
|
||||
self.fit_points = FIT_POINTS_TOTAL_QLOG
|
||||
self.factor_sanity = FACTOR_SANITY_QLOG
|
||||
self.friction_sanity = FRICTION_SANITY_QLOG
|
||||
|
||||
else:
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS.tolist()
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS
|
||||
self.min_points_total = MIN_POINTS_TOTAL
|
||||
self.fit_points = FIT_POINTS_TOTAL
|
||||
self.factor_sanity = FACTOR_SANITY
|
||||
@@ -120,10 +120,9 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
||||
'latAccelOffset': cache_ltp.latAccelOffsetFiltered,
|
||||
'frictionCoefficient': cache_ltp.frictionCoefficientFiltered
|
||||
}
|
||||
cached_points: list[list[float]] = [list(point) for point in cache_ltp.points]
|
||||
initial_params['points'] = cached_points
|
||||
initial_params['points'] = cache_ltp.points
|
||||
self.decay = cache_ltp.decay
|
||||
self.filtered_points.load_points(cached_points)
|
||||
self.filtered_points.load_points(initial_params['points'])
|
||||
cloudlog.info("restored torque params from cache")
|
||||
except Exception:
|
||||
cloudlog.exception("failed to restore cached torque params")
|
||||
|
||||
@@ -13,7 +13,6 @@ from collections import namedtuple
|
||||
import numpy as np
|
||||
|
||||
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
||||
|
||||
def _patch_tinygrad_fetch_fw():
|
||||
import hashlib
|
||||
@@ -243,7 +242,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
|
||||
SEED = 42
|
||||
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
|
||||
input_queues, npy = make_queues(Device.DEFAULT)
|
||||
rng = np.random.default_rng(seed)
|
||||
np.random.seed(seed)
|
||||
Tensor.manual_seed(seed)
|
||||
|
||||
testing = test_val is not None or test_buffers is not None
|
||||
@@ -251,7 +250,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
|
||||
|
||||
for i in range(n_runs):
|
||||
for v in npy.values():
|
||||
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
|
||||
v[:] = np.random.randn(*v.shape).astype(v.dtype)
|
||||
Device.default.synchronize()
|
||||
random_inputs = make_random_inputs()
|
||||
st = time.perf_counter()
|
||||
@@ -312,9 +311,6 @@ if __name__ == "__main__":
|
||||
p.add_argument('--frame-skip', type=int, required=True)
|
||||
args = p.parse_args()
|
||||
|
||||
if 'USB+AMD' in os.environ.get('DEV', ''):
|
||||
wait_usbgpu_link()
|
||||
|
||||
model_path = read_file_chunked_to_disk(args.onnx)
|
||||
model_w, model_h = args.model_size
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_drivi
|
||||
from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
|
||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
|
||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
||||
|
||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||
@@ -95,7 +94,7 @@ class ModelState(ModelStateBase):
|
||||
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
|
||||
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||
self.full_frames: dict[str, Tensor] = {}
|
||||
self._blob_cache: dict[tuple[str, int], Tensor] = {}
|
||||
self._blob_cache: dict[int, Tensor] = {}
|
||||
self.parser = Parser()
|
||||
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
|
||||
self.run_policy = jits['run_policy']
|
||||
@@ -174,8 +173,6 @@ def main(demo=False):
|
||||
if use_extra_client:
|
||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||
|
||||
if USBGPU:
|
||||
wait_usbgpu_link()
|
||||
st = time.monotonic()
|
||||
cloudlog.warning("loading model")
|
||||
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
|
||||
|
||||
@@ -41,7 +41,7 @@ class Parser:
|
||||
raw = outs[name]
|
||||
outs[name] = sigmoid(raw)
|
||||
|
||||
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=()):
|
||||
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
|
||||
if self.check_missing(outs, name):
|
||||
return
|
||||
raw = outs[name]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.hardware.usb import CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID, usb_devices, controller, read_int
|
||||
|
||||
STABLE_SECONDS = 2.0
|
||||
STABLE_THRESHOLD = 5.0 # link errors per second
|
||||
|
||||
|
||||
def _chestnut_portli() -> Path | None:
|
||||
for device in usb_devices():
|
||||
if read_int(device / "idVendor", 16) == CHESTNUT_VENDOR_ID and \
|
||||
read_int(device / "idProduct", 16) == CHESTNUT_PRODUCT_ID:
|
||||
ctrl = controller(device)
|
||||
if ctrl is not None and (ctrl / "portli").exists():
|
||||
return ctrl / "portli"
|
||||
return None
|
||||
|
||||
|
||||
def wait_usbgpu_link(timeout: float = 30.0) -> None:
|
||||
portli = _chestnut_portli()
|
||||
if portli is None:
|
||||
return
|
||||
|
||||
t0 = time.monotonic()
|
||||
while time.monotonic() - t0 < timeout:
|
||||
start = read_int(portli, 0)
|
||||
time.sleep(STABLE_SECONDS)
|
||||
rate = (read_int(portli, 0) - start) / STABLE_SECONDS
|
||||
if rate <= STABLE_THRESHOLD:
|
||||
return
|
||||
cloudlog.warning(f"usbgpu link not stable: {rate:.0f} errors/s")
|
||||
cloudlog.error("usbgpu link never stabilized")
|
||||
@@ -41,7 +41,7 @@ class DRIVER_MONITOR_SETTINGS:
|
||||
# lockout specs
|
||||
self._MAX_ALERT_3 = 2
|
||||
self._MAX_NO_RESPONSE = 1
|
||||
self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]]
|
||||
self._LOCKOUT_TIME = int(1800 / DT_DMON)
|
||||
|
||||
self._TIMEOUT_RECOVERY_FACTOR_MAX = 5.
|
||||
self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25
|
||||
@@ -152,10 +152,7 @@ class DriverMonitoring:
|
||||
self.cnt_since_alert_3 = 0
|
||||
self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON)
|
||||
self.no_response_cnt = 0
|
||||
self.lockout_active = Params().get_bool("DriverTooDistracted")
|
||||
self.lockout_count = Params().get("DriverLockoutCount") or 0
|
||||
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(max(self.lockout_count - 1, 0), len(self.settings._LOCKOUT_TIMES) - 1)]
|
||||
self.lockout_time_elapsed = 0
|
||||
self.lockout_time = 0
|
||||
self.step_change = 0.
|
||||
self.active_policy = MonitoringPolicy.vision
|
||||
self.driver_interacting = False
|
||||
@@ -166,6 +163,7 @@ class DriverMonitoring:
|
||||
self.threshold_alert_2 = 0.
|
||||
self.dcam_uncertain_cnt = 0
|
||||
self.dcam_reset_cnt = 0
|
||||
self.too_distracted = Params().get_bool("DriverTooDistracted")
|
||||
|
||||
self._reset_awareness()
|
||||
self._set_policy(MonitoringPolicy.vision)
|
||||
@@ -312,20 +310,16 @@ class DriverMonitoring:
|
||||
self.driver_interacting = driver_engaged
|
||||
|
||||
if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE:
|
||||
if not self.lockout_active:
|
||||
self.lockout_count += 1
|
||||
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(self.lockout_count - 1, len(self.settings._LOCKOUT_TIMES) - 1)]
|
||||
Params().put("DriverLockoutCount", self.lockout_count)
|
||||
self.lockout_active = True
|
||||
self.too_distracted = True
|
||||
|
||||
if self.lockout_active:
|
||||
self.lockout_time_elapsed += 1
|
||||
if self.lockout_time_elapsed > self.lockout_duration:
|
||||
self.lockout_active = False
|
||||
if self.too_distracted:
|
||||
self.lockout_time += 1
|
||||
if self.lockout_time > self.settings._LOCKOUT_TIME:
|
||||
self.too_distracted = False
|
||||
self.alert_3_cnt = 0
|
||||
self.cnt_since_alert_3 = 0
|
||||
self.no_response_cnt = 0
|
||||
self.lockout_time_elapsed = 0
|
||||
self.lockout_time = 0
|
||||
|
||||
always_on_valid = self.always_on and not wrong_gear
|
||||
if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \
|
||||
@@ -385,10 +379,8 @@ class DriverMonitoring:
|
||||
dat = messaging.new_message('driverMonitoringState', valid=valid)
|
||||
dm = dat.driverMonitoringState
|
||||
|
||||
dm.lockout = self.lockout_active
|
||||
dm.lockoutCount = self.lockout_count
|
||||
if self.lockout_active:
|
||||
dm.lockoutMinutesRemaining = max(1, round((self.lockout_duration - self.lockout_time_elapsed) * DT_DMON / 60.))
|
||||
dm.lockout = self.too_distracted
|
||||
dm.lockoutRecoveryPercent = to_percent(self.lockout_time / self.settings._LOCKOUT_TIME)
|
||||
dm.alert3Count = self.alert_3_cnt
|
||||
dm.noResponseCount = self.no_response_cnt
|
||||
dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout
|
||||
|
||||
@@ -86,17 +86,21 @@ class TestMonitoring:
|
||||
# engaged, distracted past red and beyond the no-response window -> unavailability response + lockout
|
||||
def test_distracted_lockout(self):
|
||||
alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false)
|
||||
s = d_status.settings
|
||||
assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3
|
||||
assert d_status.lockout_active
|
||||
assert d_status.lockout_time_elapsed > 0
|
||||
assert d_status.lockout_count >= 1
|
||||
assert d_status.alert_3_cnt == 1
|
||||
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
|
||||
assert d_status.too_distracted
|
||||
assert d_status.lockout_time > 0
|
||||
|
||||
# no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout
|
||||
def test_invisible_lockout(self):
|
||||
_, d_status = self._run_seq(always_no_face, always_false, always_true, always_false)
|
||||
s = d_status.settings
|
||||
assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch
|
||||
assert d_status.lockout_active
|
||||
assert d_status.lockout_count >= 1
|
||||
assert d_status.alert_3_cnt == 1
|
||||
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
|
||||
assert d_status.too_distracted
|
||||
|
||||
# engaged, no face detected the whole time, no action
|
||||
def test_fully_invisible_driver(self):
|
||||
@@ -271,7 +275,7 @@ def test_run_step_engagement(selfdrive_enabled, lat_active, steering, gas,
|
||||
captured['op_engaged'] = op_engaged
|
||||
return orig(driver_engaged, op_engaged, lowspeed, wrong_gear)
|
||||
|
||||
object.__setattr__(dm, '_update_events', spy)
|
||||
dm._update_events = spy
|
||||
dm.run_step(sm, demo=False)
|
||||
assert captured['op_engaged'] == expected_op_engaged
|
||||
assert captured['driver_engaged'] == expected_driver_engaged
|
||||
|
||||
@@ -60,7 +60,7 @@ class TestPandad:
|
||||
|
||||
def test_in_reset(self):
|
||||
gpio_init(GPIO.STM_RST_N, True)
|
||||
gpio_set(GPIO.STM_RST_N, True)
|
||||
gpio_set(GPIO.STM_RST_N, 1)
|
||||
assert not Panda.list()
|
||||
self._run_test()
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ class TestBoarddSpi:
|
||||
|
||||
total_recv_count = 0
|
||||
total_sent_count = 0
|
||||
sent_msgs = {bus: [] for bus in range(3)}
|
||||
sent_msgs = {bus: list() for bus in range(3)}
|
||||
|
||||
st = time.monotonic()
|
||||
ts = {s: [] for s in socks.keys()}
|
||||
ts = {s: list() for s in socks.keys()}
|
||||
for _ in range(int(os.getenv("TEST_TIME", "20"))):
|
||||
# send some CAN messages
|
||||
if not JUNGLE_SPAM:
|
||||
|
||||
@@ -7,8 +7,9 @@ from opendbc.car.structs import car
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.git import get_short_branch
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.common.realtime import DT_CTRL, DT_DMON
|
||||
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
|
||||
from openpilot.selfdrive.monitoring.policy import DRIVER_MONITOR_SETTINGS
|
||||
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
|
||||
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
@@ -24,6 +25,7 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
AudibleAlert = log.SelfdriveState.AudibleAlert
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
DMON_LOCKOUT_TIME = DRIVER_MONITOR_SETTINGS()._LOCKOUT_TIME
|
||||
|
||||
# get event name from enum
|
||||
EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()}
|
||||
@@ -99,7 +101,7 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
|
||||
|
||||
def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||
if sm['driverMonitoringState'].lockout:
|
||||
mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining
|
||||
mins_left = max(1, round((100 - sm['driverMonitoringState'].lockoutRecoveryPercent) / 100 * DMON_LOCKOUT_TIME * DT_DMON / 60.))
|
||||
return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH)
|
||||
return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH)
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ ALL_STATES = tuple(State.schema.enumerants.values())
|
||||
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
|
||||
|
||||
|
||||
def make_event(event_types: list[str | None]):
|
||||
EVENTS[0] = {}
|
||||
def make_event(event_types):
|
||||
event = {}
|
||||
for ev in event_types:
|
||||
if ev is not None:
|
||||
EVENTS[0][ev] = NormalPermanentAlert("alert")
|
||||
event[ev] = NormalPermanentAlert("alert")
|
||||
EVENTS[0] = event
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from openpilot.common.utils import tabulate
|
||||
|
||||
DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496"
|
||||
MB = 1024 * 1024
|
||||
TABULATE_OPTS = {"tablefmt": "simple_grid", "stralign": "center", "numalign": "center"}
|
||||
TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center")
|
||||
|
||||
|
||||
def _get_procs():
|
||||
|
||||
@@ -297,7 +297,7 @@ def migrate_carOutput(msgs):
|
||||
co = messaging.new_message('carOutput')
|
||||
co.valid = msg.valid
|
||||
co.logMonoTime = msg.logMonoTime
|
||||
co.carOutput.actuatorsOutput = msg.carControl.deprecated.actuatorsOutput
|
||||
co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
|
||||
add_ops.append(as_reader(co))
|
||||
return [], add_ops, []
|
||||
|
||||
@@ -323,10 +323,10 @@ def migrate_pandaStates(msgs):
|
||||
safety_param = safety_param_migration[fingerprint].value
|
||||
elif len(CP.safetyConfigs):
|
||||
safety_param = CP.safetyConfigs[0].safetyParam
|
||||
if CP.safetyConfigs[0].deprecated.safetyParam != 0:
|
||||
safety_param = CP.safetyConfigs[0].deprecated.safetyParam
|
||||
if CP.safetyConfigs[0].safetyParamDEPRECATED != 0:
|
||||
safety_param = CP.safetyConfigs[0].safetyParamDEPRECATED
|
||||
else:
|
||||
safety_param = CP.deprecated.safetyParam
|
||||
safety_param = CP.safetyParamDEPRECATED
|
||||
|
||||
ops = []
|
||||
for index, msg in msgs:
|
||||
|
||||
@@ -216,7 +216,8 @@ class ProcessContainer:
|
||||
|
||||
def _start_process(self):
|
||||
if self.capture is not None:
|
||||
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) # ty: ignore[invalid-assignment] # intentional wrapper
|
||||
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher)
|
||||
self.process.prepare()
|
||||
self.process.start()
|
||||
|
||||
def start(
|
||||
@@ -634,10 +635,10 @@ def replay_process(
|
||||
fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None,
|
||||
captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False
|
||||
) -> list[capnp._DynamicStructReader]:
|
||||
if isinstance(cfg, ProcessConfig):
|
||||
cfgs = [cfg]
|
||||
else:
|
||||
if isinstance(cfg, Iterable):
|
||||
cfgs = list(cfg)
|
||||
else:
|
||||
cfgs = [cfg]
|
||||
|
||||
all_msgs = migrate_all(lr,
|
||||
manager_states=True,
|
||||
|
||||
@@ -204,10 +204,7 @@ class FaceAnimator:
|
||||
frames_back = round(rewind_elapsed / self._animation.frame_duration)
|
||||
frame_index = self._rewind_from - frames_back
|
||||
if frame_index <= 0:
|
||||
if self._next is None:
|
||||
self._rewinding = False
|
||||
return self._animation.frames[0]
|
||||
return self._switch_to_next(now, self._next)
|
||||
return self._switch_to_next(now)
|
||||
return self._animation.frames[frame_index]
|
||||
|
||||
# Play starting frames first (once)
|
||||
@@ -226,7 +223,7 @@ class FaceAnimator:
|
||||
|
||||
if self._next is not None:
|
||||
if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero):
|
||||
return self._switch_to_next(now, self._next)
|
||||
return self._switch_to_next(now)
|
||||
# No natural return to frame 0 — start rewinding
|
||||
if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD):
|
||||
self._rewinding = True
|
||||
@@ -235,8 +232,8 @@ class FaceAnimator:
|
||||
|
||||
return self._animation.frames[frame_index]
|
||||
|
||||
def _switch_to_next(self, now: float, animation: Animation) -> list[tuple[int, int]]:
|
||||
self._animation = animation
|
||||
def _switch_to_next(self, now: float) -> list[tuple[int, int]]:
|
||||
self._animation = self._next
|
||||
self._next = None
|
||||
self._rewinding = False
|
||||
self._seen_nonzero = False
|
||||
|
||||
@@ -34,11 +34,7 @@ class MainLayout(Widget):
|
||||
# Initialize layouts
|
||||
self._home_layout = HomeLayout()
|
||||
self._home_body_layout = BodyLayout()
|
||||
self._layouts: dict[MainState, Widget] = {
|
||||
MainState.HOME: self._home_layout,
|
||||
MainState.SETTINGS: SettingsLayout(),
|
||||
MainState.ONROAD: AugmentedRoadView(),
|
||||
}
|
||||
self._layouts = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
|
||||
|
||||
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._content_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
@@ -105,6 +105,7 @@ class DeviceLayout(Widget):
|
||||
|
||||
self._params.remove("CalibrationParams")
|
||||
self._params.remove("LiveTorqueParameters")
|
||||
self._params.remove("LiveParameters")
|
||||
self._params.remove("LiveParametersV2")
|
||||
self._params.remove("LiveDelay")
|
||||
self._params.put_bool("OnroadCycleRequested", True, block=True)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import pyray as rl
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from collections.abc import Callable
|
||||
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
||||
@@ -43,7 +43,7 @@ class PanelType(IntEnum):
|
||||
class PanelInfo:
|
||||
name: str
|
||||
instance: Widget
|
||||
button_rect: rl.Rectangle = field(default_factory=lambda: rl.Rectangle(0, 0, 0, 0))
|
||||
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
|
||||
class SettingsLayout(Widget):
|
||||
|
||||
@@ -68,7 +68,7 @@ class Sidebar(Widget, SidebarSP):
|
||||
def __init__(self):
|
||||
Widget.__init__(self)
|
||||
SidebarSP.__init__(self)
|
||||
self._net_type = NETWORK_TYPES[NetworkType.none]
|
||||
self._net_type = NETWORK_TYPES.get(NetworkType.none)
|
||||
self._net_strength = 0
|
||||
|
||||
self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
|
||||
|
||||
@@ -251,7 +251,7 @@ class MiciHomeLayout(Widget):
|
||||
self._egpu_icon.set_visible(ui_state.usbgpu and ui_state.usbgpu_compiled)
|
||||
self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_compiled)
|
||||
self._mic_icon.set_visible(ui_state.recording_audio)
|
||||
self._body_icon.set_visible(bool(ui_state.is_body))
|
||||
self._body_icon.set_visible(ui_state.is_body)
|
||||
|
||||
footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48)
|
||||
self._status_bar_layout.render(footer_rect)
|
||||
|
||||
@@ -148,4 +148,4 @@ class MiciMainLayout(Scroller):
|
||||
|
||||
def _on_body_changed(self):
|
||||
self._car_onroad_layout.set_visible(not ui_state.is_body)
|
||||
self._body_onroad_layout.set_visible(bool(ui_state.is_body))
|
||||
self._body_onroad_layout.set_visible(ui_state.is_body)
|
||||
|
||||
@@ -250,12 +250,12 @@ class MiciOffroadAlerts(Scroller):
|
||||
{alert_data.key: self.params.get(alert_data.key) for alert_data in self.sorted_alerts})
|
||||
time.sleep(REFRESH_INTERVAL)
|
||||
|
||||
def _refresh(self, pending_params: dict) -> int:
|
||||
def _refresh(self) -> int:
|
||||
"""Refresh alerts from params and return active count."""
|
||||
active_count = 0
|
||||
|
||||
# Handle UpdateAvailable alert specially
|
||||
update_available = pending_params["UpdateAvailable"]
|
||||
update_available = self._pending_params["UpdateAvailable"]
|
||||
update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None)
|
||||
|
||||
if update_alert_data:
|
||||
@@ -263,7 +263,7 @@ class MiciOffroadAlerts(Scroller):
|
||||
version_string = ""
|
||||
|
||||
# Get new version description and parse version and date
|
||||
new_desc = pending_params["UpdaterNewDescription"] or ""
|
||||
new_desc = self._pending_params["UpdaterNewDescription"] or ""
|
||||
if new_desc:
|
||||
# format: "version / branch / commit / date"
|
||||
parts = new_desc.split(" / ")
|
||||
@@ -284,7 +284,7 @@ class MiciOffroadAlerts(Scroller):
|
||||
continue # Skip, already handled above
|
||||
|
||||
text = ""
|
||||
alert_json = pending_params[alert_data.key]
|
||||
alert_json = self._pending_params[alert_data.key]
|
||||
|
||||
if alert_json:
|
||||
text = alert_json.get("text", "").replace("%1", alert_json.get("extra", ""))
|
||||
@@ -311,9 +311,8 @@ class MiciOffroadAlerts(Scroller):
|
||||
def _update_state(self):
|
||||
"""Periodically refresh alerts."""
|
||||
# Refresh alerts when thread updates params
|
||||
pending_params = self._pending_params
|
||||
if pending_params is not None:
|
||||
self._refresh(pending_params)
|
||||
if self._pending_params is not None:
|
||||
self._refresh()
|
||||
self._pending_params = None
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
|
||||
@@ -16,7 +16,7 @@ from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.ui_state import device, ui_state
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.html_render import HtmlRenderer
|
||||
from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ class DeviceLayoutMici(NavScroller):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._fcc_dialog: MiciFccModal | None = None
|
||||
self._fcc_dialog: HtmlModal | None = None
|
||||
|
||||
def power_off_callback():
|
||||
ui_state.params.put_bool("DoShutdown", True, block=True)
|
||||
@@ -172,6 +172,7 @@ class DeviceLayoutMici(NavScroller):
|
||||
params = ui_state.params
|
||||
params.remove("CalibrationParams")
|
||||
params.remove("LiveTorqueParameters")
|
||||
params.remove("LiveParameters")
|
||||
params.remove("LiveParametersV2")
|
||||
params.remove("LiveDelay")
|
||||
params.put_bool("OnroadCycleRequested", True, block=True)
|
||||
|
||||
@@ -219,7 +219,7 @@ class CameraView(Widget):
|
||||
[0.0, 0.0, 1.0]
|
||||
])
|
||||
|
||||
def _render(self, rect: rl.Rectangle, /):
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
if self._switching:
|
||||
self._handle_switch()
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ class ModelPoints:
|
||||
|
||||
@dataclass
|
||||
class LeadVehicle:
|
||||
glow: list[tuple[float, float]] = field(default_factory=list)
|
||||
chevron: list[tuple[float, float]] = field(default_factory=list)
|
||||
glow: list[float] = field(default_factory=list)
|
||||
chevron: list[float] = field(default_factory=list)
|
||||
fill_alpha: int = 0
|
||||
|
||||
|
||||
|
||||
@@ -41,81 +41,80 @@ def get_child_widgets(widget) -> list:
|
||||
return children
|
||||
|
||||
|
||||
class TestWidgetLeaks:
|
||||
@pytest.mark.skip(reason="segfaults")
|
||||
def test_dialogs_do_not_leak(self):
|
||||
import pyray as rl
|
||||
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
@pytest.mark.skip(reason="segfaults")
|
||||
def test_dialogs_do_not_leak():
|
||||
import pyray as rl
|
||||
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
|
||||
# mici dialogs
|
||||
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal
|
||||
# mici dialogs
|
||||
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow
|
||||
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal
|
||||
|
||||
# tici dialogs
|
||||
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog
|
||||
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow
|
||||
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.system.ui.widgets.html_render import HtmlModal
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
# tici dialogs
|
||||
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog
|
||||
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow
|
||||
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||
from openpilot.system.ui.widgets.html_render import HtmlModal
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
|
||||
gui_app.init_window("ref-test")
|
||||
gui_app.init_window("ref-test")
|
||||
|
||||
leaked_widgets = set()
|
||||
leaked_widgets = set()
|
||||
|
||||
for ctor in (
|
||||
# mici
|
||||
MiciDriverCameraDialog, MiciPairingDialog,
|
||||
lambda: MiciTrainingGuide(lambda: None),
|
||||
lambda: MiciOnboardingWindow(lambda: None),
|
||||
lambda: BigDialog("test", "test"),
|
||||
lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None),
|
||||
lambda: BigInputDialog("test"),
|
||||
lambda: MiciFccModal(text="test"),
|
||||
# tici
|
||||
TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard,
|
||||
lambda: ConfirmDialog("test", "ok"),
|
||||
lambda: MultiOptionDialog("test", ["a", "b"]),
|
||||
lambda: HtmlModal(text="test"),
|
||||
):
|
||||
widget = ctor()
|
||||
all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]]
|
||||
for ctor in (
|
||||
# mici
|
||||
MiciDriverCameraDialog, MiciPairingDialog,
|
||||
lambda: MiciTrainingGuide(lambda: None),
|
||||
lambda: MiciOnboardingWindow(lambda: None),
|
||||
lambda: BigDialog("test", "test"),
|
||||
lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None),
|
||||
lambda: BigInputDialog("test"),
|
||||
lambda: MiciFccModal(text="test"),
|
||||
# tici
|
||||
TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard,
|
||||
lambda: ConfirmDialog("test", "ok"),
|
||||
lambda: MultiOptionDialog("test", ["a", "b"]),
|
||||
lambda: HtmlModal(text="test"),
|
||||
):
|
||||
widget = ctor()
|
||||
all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]]
|
||||
|
||||
del widget
|
||||
del widget
|
||||
|
||||
for ref in all_refs:
|
||||
if ref() is not None:
|
||||
obj = ref()
|
||||
name = f"{type(obj).__module__}.{type(obj).__qualname__}"
|
||||
leaked_widgets.add(name)
|
||||
for ref in all_refs:
|
||||
if ref() is not None:
|
||||
obj = ref()
|
||||
name = f"{type(obj).__module__}.{type(obj).__qualname__}"
|
||||
leaked_widgets.add(name)
|
||||
|
||||
print(f"\n=== Widget {name} alive after del")
|
||||
print(" Referrers:")
|
||||
for r in gc.get_referrers(obj):
|
||||
if r is obj:
|
||||
continue
|
||||
print(f"\n=== Widget {name} alive after del")
|
||||
print(" Referrers:")
|
||||
for r in gc.get_referrers(obj):
|
||||
if r is obj:
|
||||
continue
|
||||
|
||||
if hasattr(r, '__self__') and r.__self__ is not obj:
|
||||
print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}")
|
||||
elif hasattr(r, '__func__'):
|
||||
print(f" method: {r.__name__}")
|
||||
else:
|
||||
print(f" {type(r).__module__}.{type(r).__qualname__}")
|
||||
del obj
|
||||
if hasattr(r, '__self__') and r.__self__ is not obj:
|
||||
print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}")
|
||||
elif hasattr(r, '__func__'):
|
||||
print(f" method: {r.__name__}")
|
||||
else:
|
||||
print(f" {type(r).__module__}.{type(r).__qualname__}")
|
||||
del obj
|
||||
|
||||
gui_app.close()
|
||||
gui_app.close()
|
||||
|
||||
unexpected = leaked_widgets - KNOWN_LEAKS
|
||||
assert not unexpected, f"New leaked widgets: {unexpected}"
|
||||
unexpected = leaked_widgets - KNOWN_LEAKS
|
||||
assert not unexpected, f"New leaked widgets: {unexpected}"
|
||||
|
||||
fixed = KNOWN_LEAKS - leaked_widgets
|
||||
assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}"
|
||||
fixed = KNOWN_LEAKS - leaked_widgets
|
||||
assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestWidgetLeaks().test_dialogs_do_not_leak()
|
||||
test_dialogs_do_not_leak()
|
||||
|
||||
@@ -375,7 +375,6 @@ class GreyBigButton(BigButton):
|
||||
class BigMultiParamToggle(BigMultiToggle):
|
||||
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None,
|
||||
select_callback: Callable | None = None):
|
||||
assert Params is not None
|
||||
super().__init__(text, options, toggle_callback, select_callback)
|
||||
self._param = param
|
||||
|
||||
@@ -393,7 +392,6 @@ class BigMultiParamToggle(BigMultiToggle):
|
||||
|
||||
class BigParamControl(BigToggle):
|
||||
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
|
||||
assert Params is not None
|
||||
super().__init__(text, "", toggle_callback=toggle_callback)
|
||||
self.param = param
|
||||
self.params = Params()
|
||||
@@ -411,7 +409,6 @@ class BigParamControl(BigToggle):
|
||||
class BigCircleParamControl(BigCircleToggle):
|
||||
def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None,
|
||||
icon_offset: tuple[int, int] = (0, 0)):
|
||||
assert Params is not None
|
||||
super().__init__(icon, toggle_callback, icon_offset=icon_offset)
|
||||
self._param = param
|
||||
self.params = Params()
|
||||
|
||||
@@ -39,8 +39,8 @@ class ModelPoints:
|
||||
|
||||
@dataclass
|
||||
class LeadVehicle:
|
||||
glow: list[tuple[float, float]] = field(default_factory=list)
|
||||
chevron: list[tuple[float, float]] = field(default_factory=list)
|
||||
glow: list[float] = field(default_factory=list)
|
||||
chevron: list[float] = field(default_factory=list)
|
||||
fill_alpha: int = 0
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ ALERT_RAMP_TIME = 4 # seconds to ramp to max volume for warningImmediate
|
||||
SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds
|
||||
FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES)
|
||||
|
||||
AMBIENT_DB = 26 # DB where MIN_VOLUME is applied
|
||||
AMBIENT_DB = 24 # DB where MIN_VOLUME is applied
|
||||
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
|
||||
|
||||
VOLUME_BASE = 20
|
||||
@@ -48,15 +48,14 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
|
||||
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
|
||||
AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME),
|
||||
|
||||
AudibleAlert.prompt: ("warning.wav", 1, MAX_VOLUME),
|
||||
AudibleAlert.promptRepeat: ("warning.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.promptDistracted: ("dm_warning.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME),
|
||||
AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME),
|
||||
|
||||
AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME),
|
||||
AudibleAlert.complete: ("complete.wav", 1, MAX_VOLUME),
|
||||
|
||||
AudibleAlert.warningSoft: ("critical.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.warningImmediate: ("dm_critical.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
|
||||
|
||||
**sound_list_sp,
|
||||
}
|
||||
@@ -90,7 +89,6 @@ class Soundd(QuietMode):
|
||||
self.ramp_start_time = 0.
|
||||
|
||||
self.selfdrive_timeout_alert = False
|
||||
self.pending_stop = False
|
||||
|
||||
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
|
||||
|
||||
@@ -129,10 +127,6 @@ class Soundd(QuietMode):
|
||||
self.current_sound_frame += frames_to_write
|
||||
current_sound_frame = self.current_sound_frame % len(sound_data)
|
||||
loops = self.current_sound_frame // len(sound_data)
|
||||
if self.pending_stop and current_sound_frame == 0:
|
||||
self.current_alert = AudibleAlert.none
|
||||
self.pending_stop = False
|
||||
break
|
||||
|
||||
return ret * self.current_volume
|
||||
|
||||
@@ -143,15 +137,6 @@ class Soundd(QuietMode):
|
||||
|
||||
def update_alert(self, new_alert):
|
||||
current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame >= len(self.loaded_sounds[self.current_alert])
|
||||
# let looping sounds finish the current loop instead of cutting off mid tone
|
||||
if new_alert == AudibleAlert.none and self.current_alert != AudibleAlert.none and sound_list[self.current_alert][1] is None:
|
||||
if current_alert_played_once:
|
||||
self.pending_stop = True
|
||||
else:
|
||||
self.current_alert = AudibleAlert.none
|
||||
self.current_sound_frame = 0
|
||||
return
|
||||
self.pending_stop = False
|
||||
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
|
||||
if new_alert == AudibleAlert.warningImmediate:
|
||||
self.ramp_start_volume = self.current_volume
|
||||
|
||||
@@ -238,7 +238,7 @@ class ModelsLayout(Widget):
|
||||
self.lagd_toggle.action_item.set_state(live_delay)
|
||||
self.delay_control.set_visible(not live_delay and advanced_controls)
|
||||
new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100
|
||||
if self.lane_turn_value_control.action_item is not None and self.lane_turn_value_control.action_item.value_change_step != new_step:
|
||||
if self.lane_turn_value_control.action_item.value_change_step != new_step:
|
||||
self.lane_turn_value_control.action_item.value_change_step = new_step
|
||||
|
||||
self._update_lagd_description(live_delay)
|
||||
|
||||
@@ -38,8 +38,8 @@ class NetworkUISP(NetworkUI):
|
||||
self.scan_button.set_text(tr("Scan"))
|
||||
self.scan_button.set_enabled(True)
|
||||
|
||||
def _render(self, _):
|
||||
super()._render(_)
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
super()._render(rect)
|
||||
|
||||
if self._current_panel == PanelType.WIFI:
|
||||
self.scan_button.set_position(self._rect.x, self._rect.y + 20)
|
||||
|
||||
@@ -37,7 +37,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
OP.PANEL_COLOR = rl.Color(10, 10, 10, 255)
|
||||
ICON_SIZE = 70
|
||||
|
||||
OP.PanelType = IntEnum( # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||
OP.PanelType = IntEnum(
|
||||
"PanelType",
|
||||
[es.name for es in OP.PanelType] + [
|
||||
"SUNNYLINK",
|
||||
@@ -180,18 +180,20 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
self._sidebar_scroller.render(nav_rect)
|
||||
return
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
|
||||
# Check close button
|
||||
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
|
||||
if self._close_callback:
|
||||
self._close_callback()
|
||||
return
|
||||
return True
|
||||
|
||||
# Check navigation buttons
|
||||
for panel_type, panel_info in self._panels.items():
|
||||
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid():
|
||||
self.set_current_panel(panel_type)
|
||||
return
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
|
||||
@@ -138,7 +138,7 @@ class ModelsLayoutMici(NavScroller):
|
||||
self._show_selection_view(btns, self._show_folders)
|
||||
|
||||
def _reset_main_view(self):
|
||||
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||
self._scroller._items = self.main_items
|
||||
self.set_back_callback(self.original_back_callback)
|
||||
self._scroller.scroll_panel.set_offset(0)
|
||||
self._scroller.scroll_to(0)
|
||||
|
||||
@@ -16,9 +16,6 @@ class SunnylinkConsentPage(NavScroller):
|
||||
def __init__(self, on_accept: Callable | None = None, on_decline: Callable | None = None):
|
||||
super().__init__()
|
||||
|
||||
assert on_accept is not None and callable(on_accept)
|
||||
assert on_decline is not None and callable(on_decline)
|
||||
|
||||
self._accept_button = BigConfirmationCircleButton("enable\nsunnylink", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64),
|
||||
on_accept, exit_on_confirm=False)
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer):
|
||||
self._draw_ahead_info(sign_rect)
|
||||
|
||||
def _draw_sign_main(self, rect, alpha=1.0):
|
||||
speed_limit_warning_enabled = ui_state.speed_limit_mode is not None and ui_state.speed_limit_mode >= SpeedLimitMode.warning
|
||||
speed_limit_warning_enabled = ui_state.speed_limit_mode >= SpeedLimitMode.warning
|
||||
has_limit = self.speed_limit_valid or self.speed_limit_last_valid
|
||||
is_overspeed = has_limit and round(self.speed_limit_final_last) < round(self.speed)
|
||||
|
||||
|
||||
@@ -224,7 +224,8 @@ class UIStateSP:
|
||||
|
||||
|
||||
class DeviceSP:
|
||||
def _set_awake(self, on: bool, _ui_state=None):
|
||||
@staticmethod
|
||||
def _set_awake(on: bool, _ui_state):
|
||||
if _ui_state.boot_offroad_mode == 1 and not on:
|
||||
_ui_state.params.put_bool("OffroadMode", True)
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
|
||||
params = Params()
|
||||
main_layout._alerts_layout._pending_params = ({"UpdaterNewDescription": params.get("UpdaterNewDescription")} |
|
||||
{alert_data.key: params.get(alert_data.key) for alert_data in main_layout._alerts_layout.sorted_alerts})
|
||||
main_layout._alerts_layout._update_state()
|
||||
main_layout._alerts_layout._refresh()
|
||||
|
||||
swipe_right(width, wait_after=WAIT_SHORT) # open alerts
|
||||
script.setup(setup_offroad_alerts_and_refresh) # show alerts
|
||||
|
||||
@@ -49,7 +49,7 @@ def patch_submaster(message_chunks):
|
||||
sm.recv_frame[service] = sm.frame
|
||||
sm.valid[service] = True
|
||||
sm.frame += 1
|
||||
ui_state.sm.update = mock_update # ty: ignore[invalid-assignment] # profiling hook
|
||||
ui_state.sm.update = mock_update
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -92,7 +92,7 @@ if __name__ == "__main__":
|
||||
vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H)
|
||||
vipc.start_listener()
|
||||
yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2
|
||||
yuv_data = np.random.default_rng().integers(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes()
|
||||
yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes()
|
||||
with cProfile.Profile() as pr:
|
||||
for _ in gui_app.render():
|
||||
if ui_state.sm.frame >= len(message_chunks):
|
||||
|
||||
@@ -2,8 +2,7 @@ import time
|
||||
from openpilot.selfdrive.test.helpers import with_processes
|
||||
|
||||
|
||||
class TestRaylibUi:
|
||||
@with_processes(["ui"])
|
||||
def test_raylib_ui(self):
|
||||
"""Test initialization of the UI widgets is successful."""
|
||||
time.sleep(1)
|
||||
@with_processes(["ui"])
|
||||
def test_raylib_ui():
|
||||
"""Test initialization of the UI widgets is successful."""
|
||||
time.sleep(1)
|
||||
|
||||
@@ -46,59 +46,61 @@ def load_po_text(po_path: Path) -> str:
|
||||
return po_path.read_text(encoding='utf-8')
|
||||
|
||||
|
||||
class TestTranslations:
|
||||
@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values()))
|
||||
def test_translation_file_exists(self, language_code: str):
|
||||
po_path = PO_DIR / f"app_{language_code}.po"
|
||||
assert po_path.exists(), f"missing translation file: {po_path}"
|
||||
@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values()))
|
||||
def test_translation_file_exists(language_code: str):
|
||||
po_path = PO_DIR / f"app_{language_code}.po"
|
||||
assert po_path.exists(), f"missing translation file: {po_path}"
|
||||
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_placeholders_are_preserved(self, po_path: Path):
|
||||
_, entries = parse_po(po_path)
|
||||
language = po_path.stem.removeprefix("app_")
|
||||
|
||||
for entry in entries:
|
||||
source_placeholders = extract_placeholders(entry.msgid)
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_placeholders_are_preserved(po_path: Path):
|
||||
_, entries = parse_po(po_path)
|
||||
language = po_path.stem.removeprefix("app_")
|
||||
|
||||
if entry.is_plural:
|
||||
plural_placeholders = extract_placeholders(entry.msgid_plural)
|
||||
message = (
|
||||
f"{language}: source plural placeholders do not match singular for "
|
||||
+ f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}"
|
||||
)
|
||||
assert plural_placeholders == source_placeholders, message
|
||||
for entry in entries:
|
||||
source_placeholders = extract_placeholders(entry.msgid)
|
||||
|
||||
for idx, msgstr in sorted(entry.msgstr_plural.items()):
|
||||
if not msgstr:
|
||||
continue
|
||||
if entry.is_plural:
|
||||
plural_placeholders = extract_placeholders(entry.msgid_plural)
|
||||
message = (
|
||||
f"{language}: source plural placeholders do not match singular for "
|
||||
+ f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}"
|
||||
)
|
||||
assert plural_placeholders == source_placeholders, message
|
||||
|
||||
translated_placeholders = extract_placeholders(msgstr)
|
||||
message = (
|
||||
f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: "
|
||||
+ f"expected {source_placeholders}, got {translated_placeholders}"
|
||||
)
|
||||
assert translated_placeholders == source_placeholders, message
|
||||
else:
|
||||
if not entry.msgstr:
|
||||
for idx, msgstr in sorted(entry.msgstr_plural.items()):
|
||||
if not msgstr:
|
||||
continue
|
||||
|
||||
translated_placeholders = extract_placeholders(entry.msgstr)
|
||||
translated_placeholders = extract_placeholders(msgstr)
|
||||
message = (
|
||||
f"{language}: translation changes placeholders for {entry.msgid!r}: "
|
||||
f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: "
|
||||
+ f"expected {source_placeholders}, got {translated_placeholders}"
|
||||
)
|
||||
assert translated_placeholders == source_placeholders, message
|
||||
else:
|
||||
if not entry.msgstr:
|
||||
continue
|
||||
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_refs_do_not_include_line_numbers(self, po_path: Path):
|
||||
for line in load_po_text(po_path).splitlines():
|
||||
assert not LINE_NUMBER_REF_RE.match(line), (
|
||||
f"{po_path.name}: line-number source reference found: {line}"
|
||||
translated_placeholders = extract_placeholders(entry.msgstr)
|
||||
message = (
|
||||
f"{language}: translation changes placeholders for {entry.msgid!r}: "
|
||||
+ f"expected {source_placeholders}, got {translated_placeholders}"
|
||||
)
|
||||
assert translated_placeholders == source_placeholders, message
|
||||
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_entities_are_valid(self, po_path: Path):
|
||||
matches = BAD_ENTITY_RE.findall(load_po_text(po_path))
|
||||
assert not matches, (
|
||||
f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}"
|
||||
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_refs_do_not_include_line_numbers(po_path: Path):
|
||||
for line in load_po_text(po_path).splitlines():
|
||||
assert not LINE_NUMBER_REF_RE.match(line), (
|
||||
f"{po_path.name}: line-number source reference found: {line}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||
def test_translation_entities_are_valid(po_path: Path):
|
||||
matches = BAD_ENTITY_RE.findall(load_po_text(po_path))
|
||||
assert not matches, (
|
||||
f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}"
|
||||
)
|
||||
|
||||
@@ -67,22 +67,22 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
|
||||
cur_field: str | None = None
|
||||
plural_idx = 0
|
||||
|
||||
def finish(entry: POEntry | None):
|
||||
nonlocal header
|
||||
if entry is None:
|
||||
def finish():
|
||||
nonlocal cur, header
|
||||
if cur is None:
|
||||
return
|
||||
if entry.msgid == "" and entry.msgstr:
|
||||
header = entry
|
||||
elif entry.msgid != "" or entry.is_plural:
|
||||
entries.append(entry)
|
||||
if cur.msgid == "" and cur.msgstr:
|
||||
header = cur
|
||||
elif cur.msgid != "" or cur.is_plural:
|
||||
entries.append(cur)
|
||||
cur = None
|
||||
|
||||
for raw in lines:
|
||||
line = raw.rstrip('\n')
|
||||
stripped = line.strip()
|
||||
|
||||
if not stripped:
|
||||
finish(cur)
|
||||
cur = None
|
||||
finish()
|
||||
cur_field = None
|
||||
continue
|
||||
|
||||
@@ -123,8 +123,6 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
|
||||
continue
|
||||
|
||||
if stripped.startswith('msgstr '):
|
||||
if cur is None:
|
||||
cur = POEntry()
|
||||
cur.msgstr = _parse_quoted(stripped[len('msgstr '):])
|
||||
cur_field = 'msgstr'
|
||||
continue
|
||||
@@ -140,7 +138,7 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
|
||||
elif cur_field == 'msgstr_plural':
|
||||
cur.msgstr_plural[plural_idx] += val
|
||||
|
||||
finish(cur)
|
||||
finish()
|
||||
return header, entries
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ POT_FILE = os.path.join(str(TRANSLATIONS_DIR), "app.pot")
|
||||
def update_translations():
|
||||
files = []
|
||||
for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR),
|
||||
os.walk(os.path.join(str(UI_DIR), "widgets")),
|
||||
os.walk(os.path.join(str(UI_DIR), "layouts")),
|
||||
os.walk(os.path.join(str(UI_DIR), "onroad"))):
|
||||
os.walk(os.path.join(UI_DIR, "widgets")),
|
||||
os.walk(os.path.join(UI_DIR, "layouts")),
|
||||
os.walk(os.path.join(UI_DIR, "onroad"))):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".py"):
|
||||
files.append(os.path.relpath(os.path.join(root, filename), BASEDIR))
|
||||
@@ -25,7 +25,7 @@ def update_translations():
|
||||
|
||||
# Generate/update translation files for each language
|
||||
for name in multilang.languages.values():
|
||||
po_file = os.path.join(str(TRANSLATIONS_DIR), f"app_{name}.po")
|
||||
po_file = os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")
|
||||
if os.path.exists(po_file):
|
||||
merge_po(po_file, POT_FILE)
|
||||
else:
|
||||
|
||||
@@ -314,9 +314,9 @@ class Device(DeviceSP):
|
||||
brightness = 0
|
||||
|
||||
if brightness != self._last_brightness:
|
||||
self._brightness_target = int(brightness)
|
||||
self._brightness_target = brightness
|
||||
self._brightness_event.set()
|
||||
self._last_brightness = int(brightness)
|
||||
self._last_brightness = brightness
|
||||
|
||||
def _update_wakefulness(self):
|
||||
# Handle interactive timeout
|
||||
@@ -337,9 +337,9 @@ class Device(DeviceSP):
|
||||
|
||||
self._set_awake(ui_state.ignition or not interaction_timeout or PC)
|
||||
|
||||
def _set_awake(self, on: bool, _ui_state=None):
|
||||
def _set_awake(self, on: bool):
|
||||
if on != self._awake:
|
||||
super()._set_awake(on, _ui_state or ui_state)
|
||||
DeviceSP._set_awake(on, ui_state)
|
||||
self._awake = on
|
||||
cloudlog.debug(f"setting display power {int(on)}")
|
||||
HARDWARE.set_display_power(on)
|
||||
|
||||
@@ -29,7 +29,7 @@ def make_event(event_types):
|
||||
event = {}
|
||||
for ev in event_types:
|
||||
event[ev] = NormalPermanentAlert("alert")
|
||||
EVENTS_SP[0] = event # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||
EVENTS_SP[0] = event
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ def main_thread():
|
||||
cloudlog.exception(f"mapd: failed to make {Paths.mapd_root()}")
|
||||
|
||||
while True:
|
||||
show_alert = bool(get_files_for_cleanup() and params.get_bool("OsmLocal"))
|
||||
show_alert = get_files_for_cleanup() and params.get_bool("OsmLocal")
|
||||
set_offroad_alert("Offroad_OSMUpdateRequired", show_alert, "This alert will be cleared when new maps are downloaded.")
|
||||
|
||||
update_osm_db()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user