feat(monitoring): 添加驾驶员分心检测灵敏度设置功能
新增 `DistractionDetectionLevel` 参数以控制驾驶员分心检测的灵敏度等级,并在 `dmonitoringd.py` 和 `helpers.py` 中实现不同等级对应的时间阈值配置。同时更新了相关逻辑以支持动态调整该参数。 fix(toyota): 支持 Toyota Wildlander PHEV 车型接入与控制 增加对 Toyota Wildlander PHEV 的指纹识别、车辆规格定义及接口适配,确保其在 TSS2 平台下的正常运行,并修正部分雷达ACC判断条件。 feat(ui): 优化 Dragonpilot 设置界面选项显示语言一致性 将 Dragonpilot 设置页面中的多个下拉选项文本进行国际化处理,统一使用翻译函数包裹,提升多语言兼容性。 chore(config): 更新 launch 脚本 API 地址并切换 shell 解释器 修改 `launch_openpilot.sh` 使用 `/usr/bin/bash` 作为解释器,并设置自定义 API 与 Athena 服务地址。 refactor(key): 实现 ECU 秘钥提取脚本并写入参数存储 创建 `key.py` 脚本用于通过 UDS 协议从 ECU 提取 SecOC 密钥,并将其保存至系统参数中供后续使用。 docs(vscode): 移除不再使用的终端配置项 清理 `.vscode/settings.json` 文件中过时的 terminal 配置内容。 feat(fonts): 新增中文字体资源文件 添加 `china.ttf` 字体文件以增强 UI 在中文环境下的渲染效果。 build(payload): 添加二进制负载文件 引入新的二进制 payload 文件用于辅助密钥提取流程。
This commit is contained in:
parent
d8075d43a0
commit
2270c6d7f1
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -4,12 +4,6 @@
|
|||||||
"editor.renderWhitespace": "trailing",
|
"editor.renderWhitespace": "trailing",
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"terminal.integrated.defaultProfile.linux": "dragonpilot",
|
"terminal.integrated.defaultProfile.linux": "dragonpilot",
|
||||||
"terminal.integrated.profiles.linux": {
|
|
||||||
"dragonpilot": {
|
|
||||||
"path": "bash",
|
|
||||||
"args": ["-c", "distrobox enter dp"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.git": true,
|
"**/.git": true,
|
||||||
"**/.venv": true,
|
"**/.venv": true,
|
||||||
|
|||||||
83
1.sh
Normal file
83
1.sh
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/env sh
|
||||||
|
|
||||||
|
persist_dir=/persist
|
||||||
|
target_dir=${persist_dir}/comma
|
||||||
|
# Change target dir from sunnylink to comma to make this no longer a test
|
||||||
|
|
||||||
|
|
||||||
|
# Function to remount /persist as read-only
|
||||||
|
cleanup() {
|
||||||
|
echo "Remounting ${persist_dir} as read-only..."
|
||||||
|
sudo mount -o remount,ro ${persist_dir}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check and backup existing keys
|
||||||
|
backup_keys() {
|
||||||
|
if [ -f "id_rsa" ] || [ -f "id_rsa.pub" ]; then
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
backup_base="id_rsa_backup_$timestamp"
|
||||||
|
backup_private="$backup_base"
|
||||||
|
backup_public="${backup_base}.pub"
|
||||||
|
|
||||||
|
# Ensure we're not overwriting an existing backup
|
||||||
|
counter=0
|
||||||
|
while [ -f "$backup_private" ] || [ -f "$backup_public" ]; do
|
||||||
|
counter=$((counter + 1))
|
||||||
|
backup_private="${backup_base}_$counter"
|
||||||
|
backup_public="${backup_base}_$counter.pub"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Backup the keys
|
||||||
|
cp id_rsa "$backup_private"
|
||||||
|
cp id_rsa.pub "$backup_public"
|
||||||
|
|
||||||
|
# Verify the backup
|
||||||
|
original_private_hash=$(sha256sum id_rsa | cut -d ' ' -f 1)
|
||||||
|
backup_private_hash=$(sha256sum "$backup_private" | cut -d ' ' -f 1)
|
||||||
|
original_public_hash=$(sha256sum id_rsa.pub | cut -d ' ' -f 1)
|
||||||
|
backup_public_hash=$(sha256sum "$backup_public" | cut -d ' ' -f 1)
|
||||||
|
|
||||||
|
if [ "$original_private_hash" = "$backup_private_hash" ] && [ "$original_public_hash" = "$backup_public_hash" ]; then
|
||||||
|
echo "Backup verified successfully."
|
||||||
|
# Safe to delete original keys after successful backup verification
|
||||||
|
else
|
||||||
|
echo "Backup verification failed. Aborting operation."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Existing keys backed up as $backup_private and $backup_public"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap any signal that exits the script to run cleanup function
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Remount /persist as read-write
|
||||||
|
sudo mount -o remount,rw ${persist_dir}
|
||||||
|
|
||||||
|
# Ensure the directory exists
|
||||||
|
mkdir -p ${target_dir}
|
||||||
|
cd ${target_dir}
|
||||||
|
|
||||||
|
# Check for and backup existing keys
|
||||||
|
#backup_keys
|
||||||
|
|
||||||
|
# Generate new keys
|
||||||
|
if ! ssh-keygen -t rsa -b 4096 -m PEM -f id_rsa -N ''; then
|
||||||
|
echo "Failed to generate new RSA keys. Exiting..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert the generated SSH public key to PEM format and store it temporarily
|
||||||
|
if ! openssl rsa -pubout -in id_rsa -out id_rsa.pub -outform PEM; then
|
||||||
|
echo "Failed to convert the public key to PEM format. Exiting..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display the public key
|
||||||
|
echo "Displaying the public key:"
|
||||||
|
cat id_rsa.pub
|
||||||
|
|
||||||
|
# Cleanup will be called automatically due to trap on EXIT
|
||||||
|
#echo "Operation completed successfully. System will reboot now."
|
||||||
|
#sudo reboot
|
||||||
@ -66,7 +66,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
{"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
{"JoystickDebugMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||||
{"LanguageSetting", {PERSISTENT, STRING, "en"}},
|
{"LanguageSetting", {PERSISTENT, STRING, "zh-CHS"}},
|
||||||
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
{"LastAthenaPingTime", {CLEAR_ON_MANAGER_START, INT}},
|
||||||
{"LastGPSPosition", {PERSISTENT, STRING}},
|
{"LastGPSPosition", {PERSISTENT, STRING}},
|
||||||
{"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}},
|
{"LastManagerExitReason", {CLEAR_ON_MANAGER_START, STRING}},
|
||||||
@ -160,4 +160,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"dp_vag_a0_sng", {PERSISTENT, BOOL, "0"}},
|
{"dp_vag_a0_sng", {PERSISTENT, BOOL, "0"}},
|
||||||
{"dp_vag_pq_steering_patch", {PERSISTENT, BOOL, "0"}},
|
{"dp_vag_pq_steering_patch", {PERSISTENT, BOOL, "0"}},
|
||||||
{"dp_vag_avoid_eps_lockout", {PERSISTENT, BOOL, "0"}},
|
{"dp_vag_avoid_eps_lockout", {PERSISTENT, BOOL, "0"}},
|
||||||
|
|
||||||
|
// 驾驶员监控灵敏度
|
||||||
|
{"DistractionDetectionLevel", {PERSISTENT, INT, "1"}},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -196,7 +196,7 @@ class DragonpilotLayout(Widget):
|
|||||||
description=lambda: tr("Display the statistics of lead car and/or radar tracking points.<br>Lead: Lead stats only<br>Radar: Radar tracking point stats only<br>All: Lead and Radar stats<br>NOTE: Radar option only works on certain vehicle models."),
|
description=lambda: tr("Display the statistics of lead car and/or radar tracking points.<br>Lead: Lead stats only<br>Radar: Radar tracking point stats only<br>All: Lead and Radar stats<br>NOTE: Radar option only works on certain vehicle models."),
|
||||||
initial_index=int(self._params.get("dp_ui_lead") or 0),
|
initial_index=int(self._params.get("dp_ui_lead") or 0),
|
||||||
callback=lambda val: self._params.put("dp_ui_lead", val),
|
callback=lambda val: self._params.put("dp_ui_lead", val),
|
||||||
options=["Off", "Lead", "Radar", "All"]
|
options=[tr("Off"), tr("Lead"), tr("Radar"), tr("All")]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _device_toggles(self):
|
def _device_toggles(self):
|
||||||
@ -227,7 +227,7 @@ class DragonpilotLayout(Widget):
|
|||||||
self._toggles["dp_ui_display_mode"] = text_spin_button_item(
|
self._toggles["dp_ui_display_mode"] = text_spin_button_item(
|
||||||
title=lambda: tr("Display Mode"),
|
title=lambda: tr("Display Mode"),
|
||||||
callback=lambda val: self._params.put("dp_ui_display_mode", val),
|
callback=lambda val: self._params.put("dp_ui_display_mode", val),
|
||||||
options=["Std.", "MAIN+", "OP+", "MAIN-", "OP-"],
|
options=[tr("Std."), tr("MAIN+"), tr("OP+"), tr("MAIN-"), tr("OP-")],
|
||||||
initial_index=int(self._params.get("dp_ui_display_mode") or 0),
|
initial_index=int(self._params.get("dp_ui_display_mode") or 0),
|
||||||
description=lambda: tr("Std.: Stock behavior.<br>MAIN+: ACC MAIN on = Display ON.<br>OP+: OP enabled = Display ON.<br>MAIN-: ACC MAIN on = Display OFF<br>OP-: OP enabled = Display OFF."),
|
description=lambda: tr("Std.: Stock behavior.<br>MAIN+: ACC MAIN on = Display ON.<br>OP+: OP enabled = Display ON.<br>MAIN-: ACC MAIN on = Display OFF<br>OP-: OP enabled = Display OFF."),
|
||||||
)
|
)
|
||||||
@ -238,7 +238,7 @@ class DragonpilotLayout(Widget):
|
|||||||
description=lambda: tr("Std.: Stock behaviour.<br>Warning: Only emits sound when there is a warning.<br>Off: Does not emit any sound at all."),
|
description=lambda: tr("Std.: Stock behaviour.<br>Warning: Only emits sound when there is a warning.<br>Off: Does not emit any sound at all."),
|
||||||
initial_index=int(self._params.get("dp_dev_audible_alert_mode") or 0),
|
initial_index=int(self._params.get("dp_dev_audible_alert_mode") or 0),
|
||||||
callback=lambda val: self._params.put("dp_dev_audible_alert_mode", val),
|
callback=lambda val: self._params.put("dp_dev_audible_alert_mode", val),
|
||||||
options=["Std.", "Warning", "Off"],
|
options=[tr("Std."), tr("Warning"), tr("Off")],
|
||||||
)
|
)
|
||||||
|
|
||||||
self._toggles["dp_dev_auto_shutdown_in"] = spin_button_item(
|
self._toggles["dp_dev_auto_shutdown_in"] = spin_button_item(
|
||||||
|
|||||||
234
key.py
Normal file
234
key.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from subprocess import check_output, CalledProcessError
|
||||||
|
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from panda import Panda
|
||||||
|
from opendbc.car.uds import UdsClient, ACCESS_TYPE, SESSION_TYPE, DATA_IDENTIFIER_TYPE, SERVICE_TYPE, ROUTINE_CONTROL_TYPE, NegativeResponseError
|
||||||
|
from opendbc.car.structs import CarParams
|
||||||
|
from opendbc.car.isotp import isotp_send
|
||||||
|
|
||||||
|
ADDR = 0x7a1
|
||||||
|
DEBUG = False
|
||||||
|
BUS = 0
|
||||||
|
|
||||||
|
SEED_KEY_SECRET = b'\xf0\x5f\x36\xb7\xd7\x8c\x03\xe2\x4a\xb4\xfa\xef\x2a\x57\xd0\x44'
|
||||||
|
|
||||||
|
# These are the key and IV used to encrypt the payload in build_payload.py
|
||||||
|
DID_201_KEY = b'\x00' * 16
|
||||||
|
DID_202_IV = b'\x00' * 16
|
||||||
|
|
||||||
|
# 车型版本
|
||||||
|
APPLICATION_VERSIONS = {
|
||||||
|
b'\x018965B4209000\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 RAV4 Prime
|
||||||
|
b'\x018965B4233100\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2023 RAV4 Prime
|
||||||
|
b'\x018965B4509100\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 Sienna
|
||||||
|
b'\x018965B4221000\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 WILDLANDER PHEV
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY_STRUCT_SIZE = 0x20
|
||||||
|
CHECKSUM_OFFSET = 0x1d
|
||||||
|
SECOC_KEY_SIZE = 0x10
|
||||||
|
SECOC_KEY_OFFSET = 0x0c
|
||||||
|
|
||||||
|
def get_key_struct(data, key_no):
|
||||||
|
return data[key_no * KEY_STRUCT_SIZE: (key_no + 1) * KEY_STRUCT_SIZE]
|
||||||
|
|
||||||
|
def verify_checksum(key_struct):
|
||||||
|
checksum = sum(key_struct[:CHECKSUM_OFFSET])
|
||||||
|
checksum = ~checksum & 0xff
|
||||||
|
return checksum == key_struct[CHECKSUM_OFFSET]
|
||||||
|
|
||||||
|
def get_secoc_key(key_struct):
|
||||||
|
return key_struct[SECOC_KEY_OFFSET:SECOC_KEY_OFFSET + SECOC_KEY_SIZE]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
check_output(["pidof", "boardd"])
|
||||||
|
print("boardd 正在运行,请在运行此脚本前先关闭 openpilot!(已中止)")
|
||||||
|
exit(1)
|
||||||
|
except CalledProcessError as e:
|
||||||
|
if e.returncode != 1: # 1 == no process found (boardd not running)
|
||||||
|
raise e
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
panda = Panda()
|
||||||
|
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||||||
|
|
||||||
|
uds_client = UdsClient(panda, ADDR, ADDR + 8, BUS, timeout=0.1, response_pending_timeout=0.1)
|
||||||
|
|
||||||
|
print("获取应用程序版本...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_version = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||||||
|
print(" - 应用软件标识(application)", app_version)
|
||||||
|
except NegativeResponseError:
|
||||||
|
print("无法读取应用软件标识。请循环点火。")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if app_version not in APPLICATION_VERSIONS:
|
||||||
|
print("应用程序版本异常!", app_version)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Mandatory flow of diagnostic sessions
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT)
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.PROGRAMMING)
|
||||||
|
|
||||||
|
# Get bootloader version
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT)
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||||||
|
bl_version = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||||||
|
print(" - 应用软件识别(引导加载程序)", bl_version)
|
||||||
|
|
||||||
|
if bl_version != APPLICATION_VERSIONS[app_version]:
|
||||||
|
print("引导加载程序版本异常!", bl_version)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Go back to programming session
|
||||||
|
uds_client.diagnostic_session_control(SESSION_TYPE.PROGRAMMING)
|
||||||
|
|
||||||
|
# Security Access - Request Seed
|
||||||
|
seed_payload = b"\x00" * 16
|
||||||
|
seed = uds_client.security_access(ACCESS_TYPE.REQUEST_SEED, data_record=seed_payload)
|
||||||
|
|
||||||
|
key = AES.new(SEED_KEY_SECRET, AES.MODE_ECB).decrypt(seed_payload)
|
||||||
|
key = AES.new(key, AES.MODE_ECB).encrypt(seed)
|
||||||
|
|
||||||
|
print("\n安全访问...")
|
||||||
|
|
||||||
|
print(" - SEED:", seed.hex())
|
||||||
|
print(" - KEY:", key.hex())
|
||||||
|
|
||||||
|
# Security Access - Send Key
|
||||||
|
uds_client.security_access(ACCESS_TYPE.SEND_KEY, key)
|
||||||
|
print(" - Key OK!")
|
||||||
|
|
||||||
|
print("\nPreparing to upload payload...")
|
||||||
|
|
||||||
|
# Write something to DID 203, not sure why but needed for state machine
|
||||||
|
uds_client.write_data_by_identifier(0x203, b"\x00" * 5)
|
||||||
|
|
||||||
|
# Write KEY and IV to DID 201/202, prerequisite for request download
|
||||||
|
print(" - Write data by identifier 0x201", DID_201_KEY.hex())
|
||||||
|
uds_client.write_data_by_identifier(0x201, DID_201_KEY)
|
||||||
|
|
||||||
|
print(" - Write data by identifier 0x202", DID_202_IV.hex())
|
||||||
|
uds_client.write_data_by_identifier(0x202, DID_202_IV)
|
||||||
|
|
||||||
|
# Request download to RAM
|
||||||
|
data = b"\x01" # [1] Format
|
||||||
|
data += b"\x46" # [2] 4 size bytes, 6 address bytes
|
||||||
|
data += b"\x01" # [3] memoryIdentifier
|
||||||
|
data += b"\x00" # [4]
|
||||||
|
data += struct.pack('!I', 0xfebf0000) # [5] Address
|
||||||
|
data += struct.pack('!I', 0x1000) # [9] Size
|
||||||
|
|
||||||
|
print("\nUpload payload...")
|
||||||
|
|
||||||
|
print(" - Request download")
|
||||||
|
resp = uds_client._uds_request(SERVICE_TYPE.REQUEST_DOWNLOAD, data=data)
|
||||||
|
|
||||||
|
# Upload payload
|
||||||
|
payload = open("payload.bin", "rb").read()
|
||||||
|
assert len(payload) == 0x1000
|
||||||
|
chunk_size = 0x400
|
||||||
|
for i in range(len(payload) // chunk_size):
|
||||||
|
print(f" - Transfer data {i}")
|
||||||
|
uds_client.transfer_data(i + 1, payload[i * chunk_size:(i + 1) * chunk_size])
|
||||||
|
|
||||||
|
uds_client.request_transfer_exit()
|
||||||
|
|
||||||
|
print("\nVerify payload...")
|
||||||
|
|
||||||
|
# Routine control 0x10f0
|
||||||
|
# [0] 0x31 (routine control)
|
||||||
|
# [1] 0x01 (start)
|
||||||
|
# [2] 0x10f0 (routine identifier)
|
||||||
|
# [4] 0x45 (format, 4 size bytes, 5 address bytes)
|
||||||
|
# [5] 0x0
|
||||||
|
# [6] mem addr
|
||||||
|
# [10] mem addr
|
||||||
|
data = b"\x45\x00"
|
||||||
|
data += struct.pack('!I', 0xfebf0000)
|
||||||
|
data += struct.pack('!I', 0x1000)
|
||||||
|
|
||||||
|
uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, 0x10f0, data)
|
||||||
|
print(" - Routine control 0x10f0 OK!")
|
||||||
|
|
||||||
|
print("\nTrigger payload...")
|
||||||
|
|
||||||
|
# Now we trigger the payload by trying to erase
|
||||||
|
# [0] 0x31 (routine control)
|
||||||
|
# [1] 0x01 (start)
|
||||||
|
# [2] 0xff00 (routine identifier)
|
||||||
|
# [4] 0x45 (format, 4 size bytes, 5 address bytes)
|
||||||
|
# [5] 0x0
|
||||||
|
# [6] mem addr
|
||||||
|
# [10] mem addr
|
||||||
|
data = b"\x45\x00"
|
||||||
|
data += struct.pack('!I', 0xe0000)
|
||||||
|
data += struct.pack('!I', 0x8000)
|
||||||
|
|
||||||
|
# Manually send so we don't get stuck waiting for the response
|
||||||
|
# uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, 0xff00, data)
|
||||||
|
erase = b"\x31\x01\xff\x00" + data
|
||||||
|
isotp_send(panda, erase, ADDR, bus=BUS)
|
||||||
|
|
||||||
|
print("\nDumping keys...")
|
||||||
|
start = 0xfebe6e34
|
||||||
|
end = 0xfebe6ff4
|
||||||
|
|
||||||
|
extracted = b""
|
||||||
|
|
||||||
|
with open(f'data_{start:08x}_{end:08x}.bin', 'wb') as f:
|
||||||
|
with tqdm(total=end-start) as pbar:
|
||||||
|
while start < end:
|
||||||
|
for addr, *_, data, bus in panda.can_recv():
|
||||||
|
if bus != BUS:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if data == b"\x03\x7f\x31\x78\x00\x00\x00\x00": # Skip response pending
|
||||||
|
continue
|
||||||
|
|
||||||
|
if addr != ADDR + 8:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
print(f"{data.hex()}")
|
||||||
|
|
||||||
|
ptr = struct.unpack("<I", data[:4])[0]
|
||||||
|
assert (ptr >> 8) == start & 0xffffff # Check lower 24 bits of address
|
||||||
|
|
||||||
|
extracted += data[4:]
|
||||||
|
f.write(data[4:])
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
start += 4
|
||||||
|
pbar.update(4)
|
||||||
|
|
||||||
|
key_1_ok = verify_checksum(get_key_struct(extracted, 1))
|
||||||
|
key_4_ok = verify_checksum(get_key_struct(extracted, 4))
|
||||||
|
|
||||||
|
if not key_1_ok or not key_4_ok:
|
||||||
|
print("SecOC key checksum verification failed!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
key_1 = get_secoc_key(get_key_struct(extracted, 1))
|
||||||
|
key_4 = get_secoc_key(get_key_struct(extracted, 4))
|
||||||
|
|
||||||
|
print("\nECU_MASTER_KEY ", key_1.hex())
|
||||||
|
print("SecOC Key (KEY_4)", key_4.hex())
|
||||||
|
|
||||||
|
try:
|
||||||
|
from openpilot.common.params import Params
|
||||||
|
params = Params()
|
||||||
|
params.put("SecOCKey", key_4.hex())
|
||||||
|
print("\nSecOC key written to param successfully!")
|
||||||
|
except Exception:
|
||||||
|
print("\nFailed to write SecOCKey param")
|
||||||
@ -1,3 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/bash
|
||||||
|
export API_HOST=https://api.konik.ai
|
||||||
|
export ATHENA_HOST=wss://athena.konik.ai
|
||||||
exec ./launch_chffrplus.sh
|
exec ./launch_chffrplus.sh
|
||||||
|
|||||||
@ -10,6 +10,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
|
|||||||
"TOYOTA_ALPHARD_TSS2" = "TOYOTA_SIENNA"
|
"TOYOTA_ALPHARD_TSS2" = "TOYOTA_SIENNA"
|
||||||
"TOYOTA_PRIUS_V" = "TOYOTA_PRIUS"
|
"TOYOTA_PRIUS_V" = "TOYOTA_PRIUS"
|
||||||
"TOYOTA_RAV4_PRIME" = "TOYOTA_RAV4_TSS2"
|
"TOYOTA_RAV4_PRIME" = "TOYOTA_RAV4_TSS2"
|
||||||
|
"TOYOTA_WILDLANDER_PHEV" = "TOYOTA_RAV4_TSS2"
|
||||||
"TOYOTA_SIENNA_4TH_GEN" = "TOYOTA_RAV4_TSS2"
|
"TOYOTA_SIENNA_4TH_GEN" = "TOYOTA_RAV4_TSS2"
|
||||||
"LEXUS_IS" = "LEXUS_NX"
|
"LEXUS_IS" = "LEXUS_NX"
|
||||||
"LEXUS_CTH" = "LEXUS_NX"
|
"LEXUS_CTH" = "LEXUS_NX"
|
||||||
|
|||||||
@ -208,6 +208,7 @@ class CarState(CarStateBase):
|
|||||||
self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"]
|
self.pcm_follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"]
|
||||||
|
|
||||||
buttonEvents = []
|
buttonEvents = []
|
||||||
|
prev_distance_button = self.distance_button
|
||||||
if self.CP.carFingerprint in TSS2_CAR:
|
if self.CP.carFingerprint in TSS2_CAR:
|
||||||
# lkas button is wired to the camera
|
# lkas button is wired to the camera
|
||||||
prev_lkas_button = self.lkas_button
|
prev_lkas_button = self.lkas_button
|
||||||
@ -218,9 +219,8 @@ class CarState(CarStateBase):
|
|||||||
buttonEvents.extend(create_button_events(1, 0, {1: ButtonType.lkas}) +
|
buttonEvents.extend(create_button_events(1, 0, {1: ButtonType.lkas}) +
|
||||||
create_button_events(0, 1, {1: ButtonType.lkas}))
|
create_button_events(0, 1, {1: ButtonType.lkas}))
|
||||||
|
|
||||||
if self.CP.carFingerprint not in (RADAR_ACC_CAR | SECOC_CAR):
|
if self.CP.carFingerprint not in RADAR_ACC_CAR:
|
||||||
# distance button is wired to the ACC module (camera or radar)
|
# distance button is wired to the ACC module (camera or radar)
|
||||||
prev_distance_button = self.distance_button
|
|
||||||
self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"]
|
self.distance_button = cp_acc.vl["ACC_CONTROL"]["DISTANCE"]
|
||||||
|
|
||||||
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
buttonEvents += create_button_events(self.distance_button, prev_distance_button, {1: ButtonType.gapAdjustCruise})
|
||||||
|
|||||||
@ -1068,6 +1068,29 @@ FW_VERSIONS = {
|
|||||||
b'\x028646F4210100\x00\x00\x00\x008646G3305000\x00\x00\x00\x00',
|
b'\x028646F4210100\x00\x00\x00\x008646G3305000\x00\x00\x00\x00',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
CAR.TOYOTA_WILDLANDER_PHEV: {
|
||||||
|
(Ecu.engine, 0x700, None): [
|
||||||
|
b'\x01896630R57001\x00\x00\x00\x00',
|
||||||
|
],
|
||||||
|
(Ecu.abs, 0x7b0, None): [
|
||||||
|
b'\x01F152642C4000\x00\x00\x00\x00',
|
||||||
|
],
|
||||||
|
(Ecu.eps, 0x7a1, None): [
|
||||||
|
b'\x018965B4221000\x00\x00\x00\x00',
|
||||||
|
],
|
||||||
|
(Ecu.fwdCamera, 0x750, 0x6d): [
|
||||||
|
b'\x028646F0R01000\x00\x00\x00\x008646G4202000\x00\x00\x00\x00',
|
||||||
|
],
|
||||||
|
(Ecu.fwdRadar, 0x750, 0xf): [
|
||||||
|
b'\x018821F3301400\x00\x00\x00\x00',
|
||||||
|
],
|
||||||
|
(Ecu.srs, 0x780, None): [
|
||||||
|
b'\x018917F0R18000\x00\x00\x00\x00'
|
||||||
|
],
|
||||||
|
(Ecu.hybrid, 0x7d2, None): [
|
||||||
|
b'\x02899830R24000\x00\x00\x00\x00899850R05000\x00\x00\x00\x00'
|
||||||
|
],
|
||||||
|
},
|
||||||
CAR.TOYOTA_RAV4_TSS2: {
|
CAR.TOYOTA_RAV4_TSS2: {
|
||||||
(Ecu.engine, 0x700, None): [
|
(Ecu.engine, 0x700, None): [
|
||||||
b'\x01896630R58000\x00\x00\x00\x00',
|
b'\x01896630R58000\x00\x00\x00\x00',
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class CarInterface(CarInterfaceBase):
|
|||||||
# https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf
|
# https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf
|
||||||
stop_and_go = candidate != CAR.TOYOTA_AVALON
|
stop_and_go = candidate != CAR.TOYOTA_AVALON
|
||||||
|
|
||||||
elif candidate in (CAR.TOYOTA_RAV4_TSS2, CAR.TOYOTA_RAV4_TSS2_2022, CAR.TOYOTA_RAV4_TSS2_2023, CAR.TOYOTA_RAV4_PRIME, CAR.TOYOTA_SIENNA_4TH_GEN):
|
elif candidate in (CAR.TOYOTA_RAV4_TSS2, CAR.TOYOTA_RAV4_TSS2_2022, CAR.TOYOTA_RAV4_TSS2_2023, CAR.TOYOTA_RAV4_PRIME, CAR.TOYOTA_SIENNA_4TH_GEN, CAR.TOYOTA_WILDLANDER_PHEV):
|
||||||
ret.lateralTuning.init('pid')
|
ret.lateralTuning.init('pid')
|
||||||
ret.lateralTuning.pid.kiBP = [0.0]
|
ret.lateralTuning.pid.kiBP = [0.0]
|
||||||
ret.lateralTuning.pid.kpBP = [0.0]
|
ret.lateralTuning.pid.kpBP = [0.0]
|
||||||
@ -169,9 +169,7 @@ class CarInterface(CarInterfaceBase):
|
|||||||
|
|
||||||
ret.openpilotLongitudinalControl = ret.enableDsu or \
|
ret.openpilotLongitudinalControl = ret.enableDsu or \
|
||||||
candidate in (TSS2_CAR - RADAR_ACC_CAR) or \
|
candidate in (TSS2_CAR - RADAR_ACC_CAR) or \
|
||||||
bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) or \
|
bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value)
|
||||||
sdsu_active or \
|
|
||||||
dsu_bypass
|
|
||||||
|
|
||||||
if dp_params & structs.DPFlags.ToyotaStockLon:
|
if dp_params & structs.DPFlags.ToyotaStockLon:
|
||||||
ret.openpilotLongitudinalControl = False
|
ret.openpilotLongitudinalControl = False
|
||||||
|
|||||||
@ -300,6 +300,10 @@ class CAR(Platforms):
|
|||||||
[ToyotaCommunityCarDocs("Toyota RAV4 Prime 2021-23", min_enable_speed=MIN_ACC_SPEED)],
|
[ToyotaCommunityCarDocs("Toyota RAV4 Prime 2021-23", min_enable_speed=MIN_ACC_SPEED)],
|
||||||
CarSpecs(mass=4372. * CV.LB_TO_KG, wheelbase=2.68, steerRatio=16.88, tireStiffnessFactor=0.5533),
|
CarSpecs(mass=4372. * CV.LB_TO_KG, wheelbase=2.68, steerRatio=16.88, tireStiffnessFactor=0.5533),
|
||||||
)
|
)
|
||||||
|
TOYOTA_WILDLANDER_PHEV = ToyotaSecOCPlatformConfig(
|
||||||
|
[ToyotaCarDocs("Toyota Wildlander PHEV 2021-23", min_enable_speed=MIN_ACC_SPEED)],
|
||||||
|
CarSpecs(mass=4155. * CV.LB_TO_KG, wheelbase=2.69, steerRatio=16.88, tireStiffnessFactor=0.5533),
|
||||||
|
)
|
||||||
TOYOTA_YARIS = ToyotaSecOCPlatformConfig(
|
TOYOTA_YARIS = ToyotaSecOCPlatformConfig(
|
||||||
[ToyotaCommunityCarDocs("Toyota Yaris (Non-US only) 2020, 2023", min_enable_speed=MIN_ACC_SPEED)],
|
[ToyotaCommunityCarDocs("Toyota Yaris (Non-US only) 2020, 2023", min_enable_speed=MIN_ACC_SPEED)],
|
||||||
CarSpecs(mass=1170, wheelbase=2.55, steerRatio=14.80, tireStiffnessFactor=0.5533),
|
CarSpecs(mass=1170, wheelbase=2.55, steerRatio=14.80, tireStiffnessFactor=0.5533),
|
||||||
|
|||||||
BIN
payload.bin
Normal file
BIN
payload.bin
Normal file
Binary file not shown.
BIN
selfdrive/assets/fonts/china.ttf
Normal file
BIN
selfdrive/assets/fonts/china.ttf
Normal file
Binary file not shown.
@ -12,7 +12,11 @@ def dmonitoringd_thread():
|
|||||||
pm = messaging.PubMaster(['driverMonitoringState'])
|
pm = messaging.PubMaster(['driverMonitoringState'])
|
||||||
sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2')
|
sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2')
|
||||||
|
|
||||||
DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM"))
|
DM = DriverMonitoring(
|
||||||
|
rhd_saved=params.get_bool("IsRhdDetected"),
|
||||||
|
always_on=params.get_bool("AlwaysOnDM"),
|
||||||
|
distraction_detection_level=int(params.get("DistractionDetectionLevel") or 1)
|
||||||
|
)
|
||||||
|
|
||||||
# 20Hz <- dmonitoringmodeld
|
# 20Hz <- dmonitoringmodeld
|
||||||
while True:
|
while True:
|
||||||
@ -22,8 +26,10 @@ def dmonitoringd_thread():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
valid = sm.all_checks()
|
valid = sm.all_checks()
|
||||||
if valid:
|
if DM.always_on and valid:
|
||||||
DM.run_step(sm)
|
DM.run_step(sm)
|
||||||
|
## 设置分心率
|
||||||
|
DM.set_distract_level_params()
|
||||||
|
|
||||||
# publish
|
# publish
|
||||||
dat = DM.get_state_packet(valid=valid)
|
dat = DM.get_state_packet(valid=valid)
|
||||||
@ -32,6 +38,7 @@ def dmonitoringd_thread():
|
|||||||
# load live always-on toggle
|
# load live always-on toggle
|
||||||
if sm['driverStateV2'].frameId % 40 == 1:
|
if sm['driverStateV2'].frameId % 40 == 1:
|
||||||
DM.always_on = params.get_bool("AlwaysOnDM")
|
DM.always_on = params.get_bool("AlwaysOnDM")
|
||||||
|
DM.distraction_detection_level = int(params.get("DistractionDetectionLevel") or 1)
|
||||||
|
|
||||||
# save rhd virtual toggle every 5 mins
|
# save rhd virtual toggle every 5 mins
|
||||||
if (sm['driverStateV2'].frameId % 6000 == 0 and
|
if (sm['driverStateV2'].frameId % 6000 == 0 and
|
||||||
|
|||||||
@ -124,7 +124,7 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib):
|
|||||||
|
|
||||||
|
|
||||||
class DriverMonitoring:
|
class DriverMonitoring:
|
||||||
def __init__(self, rhd_saved=False, settings=None, always_on=False):
|
def __init__(self, rhd_saved=False, settings=None, always_on=False, distraction_detection_level=None):
|
||||||
if settings is None:
|
if settings is None:
|
||||||
settings = DRIVER_MONITOR_SETTINGS()
|
settings = DRIVER_MONITOR_SETTINGS()
|
||||||
# init policy settings
|
# init policy settings
|
||||||
@ -139,6 +139,7 @@ class DriverMonitoring:
|
|||||||
self.ee1_calibrated = False
|
self.ee1_calibrated = False
|
||||||
|
|
||||||
self.always_on = always_on
|
self.always_on = always_on
|
||||||
|
self.distraction_detection_level = distraction_detection_level
|
||||||
self.distracted_types = []
|
self.distracted_types = []
|
||||||
self.driver_distracted = False
|
self.driver_distracted = False
|
||||||
self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON)
|
self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON)
|
||||||
@ -415,3 +416,21 @@ class DriverMonitoring:
|
|||||||
wrong_gear=sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park],
|
wrong_gear=sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park],
|
||||||
car_speed=sm['carState'].vEgo
|
car_speed=sm['carState'].vEgo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_distract_level_params(self):
|
||||||
|
if self.distraction_detection_level == 0:
|
||||||
|
self.settings._DISTRACTED_TIME = 8.0
|
||||||
|
self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL = 5.0
|
||||||
|
self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 4.0
|
||||||
|
elif self.distraction_detection_level == 1:
|
||||||
|
self.settings._DISTRACTED_TIME = 11.0
|
||||||
|
self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL = 8.
|
||||||
|
self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6.
|
||||||
|
elif self.distraction_detection_level == 2:
|
||||||
|
self.settings._DISTRACTED_TIME = 20.0
|
||||||
|
self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL = 10.0
|
||||||
|
self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 7.0
|
||||||
|
else:
|
||||||
|
self.settings._DISTRACTED_TIME = float('inf')
|
||||||
|
self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL = float('inf')
|
||||||
|
self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = float('inf')
|
||||||
|
|||||||
@ -148,7 +148,7 @@ EmptyAlert = Alert("" , "", AlertStatus.normal, AlertSize.none, Priority.LOWEST,
|
|||||||
|
|
||||||
class NoEntryAlert(Alert):
|
class NoEntryAlert(Alert):
|
||||||
def __init__(self, alert_text_2: str,
|
def __init__(self, alert_text_2: str,
|
||||||
alert_text_1: str = "openpilot Unavailable",
|
alert_text_1: str = "openpilot 不可用",
|
||||||
visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none):
|
visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none):
|
||||||
super().__init__(alert_text_1, alert_text_2, AlertStatus.normal,
|
super().__init__(alert_text_1, alert_text_2, AlertStatus.normal,
|
||||||
AlertSize.mid, Priority.LOW, visual_alert,
|
AlertSize.mid, Priority.LOW, visual_alert,
|
||||||
@ -157,7 +157,7 @@ class NoEntryAlert(Alert):
|
|||||||
|
|
||||||
class SoftDisableAlert(Alert):
|
class SoftDisableAlert(Alert):
|
||||||
def __init__(self, alert_text_2: str):
|
def __init__(self, alert_text_2: str):
|
||||||
super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2,
|
super().__init__("立即接管控制", alert_text_2,
|
||||||
AlertStatus.userPrompt, AlertSize.full,
|
AlertStatus.userPrompt, AlertSize.full,
|
||||||
Priority.MID, VisualAlert.steerRequired,
|
Priority.MID, VisualAlert.steerRequired,
|
||||||
AudibleAlert.warningSoft, 2.),
|
AudibleAlert.warningSoft, 2.),
|
||||||
@ -167,12 +167,12 @@ class SoftDisableAlert(Alert):
|
|||||||
class UserSoftDisableAlert(SoftDisableAlert):
|
class UserSoftDisableAlert(SoftDisableAlert):
|
||||||
def __init__(self, alert_text_2: str):
|
def __init__(self, alert_text_2: str):
|
||||||
super().__init__(alert_text_2),
|
super().__init__(alert_text_2),
|
||||||
self.alert_text_1 = "openpilot will disengage"
|
self.alert_text_1 = "openpilot 将解除控制"
|
||||||
|
|
||||||
|
|
||||||
class ImmediateDisableAlert(Alert):
|
class ImmediateDisableAlert(Alert):
|
||||||
def __init__(self, alert_text_2: str):
|
def __init__(self, alert_text_2: str):
|
||||||
super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2,
|
super().__init__("立即接管控制", alert_text_2,
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGHEST, VisualAlert.steerRequired,
|
Priority.HIGHEST, VisualAlert.steerRequired,
|
||||||
AudibleAlert.warningImmediate, 4.),
|
AudibleAlert.warningImmediate, 4.),
|
||||||
@ -194,7 +194,7 @@ class NormalPermanentAlert(Alert):
|
|||||||
|
|
||||||
|
|
||||||
class StartupAlert(Alert):
|
class StartupAlert(Alert):
|
||||||
def __init__(self, alert_text_1: str, alert_text_2: str = "Always keep hands on wheel and eyes on road", alert_status=AlertStatus.normal):
|
def __init__(self, alert_text_1: str, alert_text_2: str = "请始终将手放在方向盘上,眼睛注视道路", alert_status=AlertStatus.normal):
|
||||||
super().__init__(alert_text_1, alert_text_2,
|
super().__init__(alert_text_1, alert_text_2,
|
||||||
alert_status, AlertSize.mid,
|
alert_status, AlertSize.mid,
|
||||||
Priority.LOWER, VisualAlert.none, AudibleAlert.none, 5.),
|
Priority.LOWER, VisualAlert.none, AudibleAlert.none, 5.),
|
||||||
@ -232,25 +232,25 @@ def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM
|
|||||||
if "REPLAY" in os.environ:
|
if "REPLAY" in os.environ:
|
||||||
branch = "replay"
|
branch = "replay"
|
||||||
|
|
||||||
return StartupAlert("WARNING: This branch is not tested", branch, alert_status=AlertStatus.userPrompt)
|
return StartupAlert("警告:此分支未经测试", branch, alert_status=AlertStatus.userPrompt)
|
||||||
|
|
||||||
def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage")
|
return NoEntryAlert(f"请将时速提高至 {get_display_speed(CP.minEnableSpeed, metric)} 来启用")
|
||||||
|
|
||||||
|
|
||||||
def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
return Alert(
|
return Alert(
|
||||||
f"Steer Assist Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}",
|
f"转向在 {get_display_speed(CP.minSteerSpeed, metric)} 以下不可用",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
|
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, 0.4)
|
||||||
|
|
||||||
|
|
||||||
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
first_word = 'Recalibration' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else 'Calibration'
|
first_word = '重新校准' if sm['liveCalibration'].calStatus == log.LiveCalibrationData.Status.recalibrating else '校准'
|
||||||
return Alert(
|
return Alert(
|
||||||
f"{first_word} in Progress: {sm['liveCalibration'].calPerc:.0f}%",
|
f"{first_word}进行中:{sm['liveCalibration'].calPerc:.0f}%",
|
||||||
f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}",
|
f"请将时速提高至 {get_display_speed(MIN_SPEED_FILTER, metric)} 进行校准",
|
||||||
AlertStatus.normal, AlertSize.mid,
|
AlertStatus.normal, AlertSize.mid,
|
||||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
|
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2)
|
||||||
|
|
||||||
@ -258,8 +258,8 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
|
|||||||
def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE)
|
duration = FEEDBACK_MAX_DURATION - ((sm['audioFeedback'].blockNum + 1) * SAMPLE_BUFFER / SAMPLE_RATE)
|
||||||
return NormalPermanentAlert(
|
return NormalPermanentAlert(
|
||||||
"Recording Audio Feedback",
|
"正在录制音频反馈",
|
||||||
f"{round(duration)} second{'s' if round(duration) != 1 else ''} remaining. Press again to save early.",
|
f"剩余 {round(duration)} 秒。再次按下可提前保存。",
|
||||||
priority=Priority.LOW)
|
priority=Priority.LOW)
|
||||||
|
|
||||||
|
|
||||||
@ -267,57 +267,57 @@ def audio_feedback_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM
|
|||||||
|
|
||||||
def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def out_of_space_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
full_perc = round(100. - sm['deviceState'].freeSpacePercent)
|
full_perc = round(100. - sm['deviceState'].freeSpacePercent)
|
||||||
return NormalPermanentAlert("Out of Storage", f"{full_perc}% full")
|
return NormalPermanentAlert("存储空间不足", f"已用 {full_perc}%")
|
||||||
|
|
||||||
|
|
||||||
def posenet_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def posenet_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
mdl = sm['modelV2'].velocity.x[0] if len(sm['modelV2'].velocity.x) else math.nan
|
mdl = sm['modelV2'].velocity.x[0] if len(sm['modelV2'].velocity.x) else math.nan
|
||||||
err = CS.vEgo - mdl
|
err = CS.vEgo - mdl
|
||||||
msg = f"Speed Error: {err:.1f} m/s"
|
msg = f"速度误差: {err:.1f} 米/秒"
|
||||||
return NoEntryAlert(msg, alert_text_1="Posenet Speed Invalid")
|
return NoEntryAlert(msg, alert_text_1="Posenet速度无效")
|
||||||
|
|
||||||
|
|
||||||
def process_not_running_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def process_not_running_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning]
|
not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning]
|
||||||
msg = ', '.join(not_running)
|
msg = ', '.join(not_running)
|
||||||
return NoEntryAlert(msg, alert_text_1="Process Not Running")
|
return NoEntryAlert(msg, alert_text_1="进程未运行")
|
||||||
|
|
||||||
|
|
||||||
def comm_issue_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def comm_issue_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
bs = [s for s in sm.data.keys() if not sm.all_checks([s, ])]
|
bs = [s for s in sm.data.keys() if not sm.all_checks([s, ])]
|
||||||
msg = ', '.join(bs[:4]) # can't fit too many on one line
|
msg = ', '.join(bs[:4]) # can't fit too many on one line
|
||||||
return NoEntryAlert(msg, alert_text_1="Communication Issue Between Processes")
|
return NoEntryAlert(msg, alert_text_1="进程间通信问题")
|
||||||
|
|
||||||
|
|
||||||
def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
|
all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
|
||||||
bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])]
|
bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])]
|
||||||
return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams))
|
return NormalPermanentAlert("摄像头故障", ', '.join(bad_cams))
|
||||||
|
|
||||||
|
|
||||||
def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
rpy = sm['liveCalibration'].rpyCalib
|
rpy = sm['liveCalibration'].rpyCalib
|
||||||
yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan)
|
yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan)
|
||||||
pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan)
|
pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan)
|
||||||
angles = f"Remount Device (Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°)"
|
angles = f"请重新安装设备 (俯仰角: {pitch:.1f}°, 偏航角: {yaw:.1f}°)"
|
||||||
return NormalPermanentAlert("Calibration Invalid", angles)
|
return NormalPermanentAlert("校准无效", angles)
|
||||||
|
|
||||||
|
|
||||||
def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def paramsd_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
if not sm['liveParameters'].angleOffsetValid:
|
if not sm['liveParameters'].angleOffsetValid:
|
||||||
angle_offset_deg = sm['liveParameters'].angleOffsetDeg
|
angle_offset_deg = sm['liveParameters'].angleOffsetDeg
|
||||||
title = "Steering misalignment detected"
|
title = "转向系统未对准"
|
||||||
text = f"Angle offset too high (Offset: {angle_offset_deg:.1f}°)"
|
text = f"角度偏移过大 (偏移量: {angle_offset_deg:.1f}°)"
|
||||||
elif not sm['liveParameters'].steerRatioValid:
|
elif not sm['liveParameters'].steerRatioValid:
|
||||||
steer_ratio = sm['liveParameters'].steerRatio
|
steer_ratio = sm['liveParameters'].steerRatio
|
||||||
title = "Steer ratio mismatch"
|
title = "转向传动比不匹配"
|
||||||
text = f"Steering rack geometry may be off (Ratio: {steer_ratio:.1f})"
|
text = f"转向齿条几何可能不正确 (传动比: {steer_ratio:.1f})"
|
||||||
elif not sm['liveParameters'].stiffnessFactorValid:
|
elif not sm['liveParameters'].stiffnessFactorValid:
|
||||||
stiffness_factor = sm['liveParameters'].stiffnessFactor
|
stiffness_factor = sm['liveParameters'].stiffnessFactor
|
||||||
title = "Abnormal tire stiffness"
|
title = "轮胎刚度异常"
|
||||||
text = f"Check tires, pressure, or alignment (Factor: {stiffness_factor:.1f})"
|
text = f"请检查轮胎、胎压或定位 (系数: {stiffness_factor:.1f})"
|
||||||
else:
|
else:
|
||||||
return NoEntryAlert("paramsd Temporary Error")
|
return NoEntryAlert("paramsd 临时错误")
|
||||||
|
|
||||||
return NoEntryAlert(alert_text_1=title, alert_text_2=text)
|
return NoEntryAlert(alert_text_1=title, alert_text_2=text)
|
||||||
|
|
||||||
@ -325,34 +325,34 @@ def overheat_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
|
|||||||
cpu = max(sm['deviceState'].cpuTempC, default=0.)
|
cpu = max(sm['deviceState'].cpuTempC, default=0.)
|
||||||
gpu = max(sm['deviceState'].gpuTempC, default=0.)
|
gpu = max(sm['deviceState'].gpuTempC, default=0.)
|
||||||
temp = max((cpu, gpu, sm['deviceState'].memoryTempC))
|
temp = max((cpu, gpu, sm['deviceState'].memoryTempC))
|
||||||
return NormalPermanentAlert("System Overheated", f"{temp:.0f} °C")
|
return NormalPermanentAlert("系统过热", f"{temp:.0f} °C")
|
||||||
|
|
||||||
|
|
||||||
def low_memory_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def low_memory_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
return NormalPermanentAlert("Low Memory", f"{sm['deviceState'].memoryUsagePercent}% used")
|
return NormalPermanentAlert("内存不足", f"已用 {sm['deviceState'].memoryUsagePercent}%")
|
||||||
|
|
||||||
|
|
||||||
def high_cpu_usage_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def high_cpu_usage_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
x = max(sm['deviceState'].cpuUsagePercent, default=0.)
|
x = max(sm['deviceState'].cpuUsagePercent, default=0.)
|
||||||
return NormalPermanentAlert("High CPU Usage", f"{x}% used")
|
return NormalPermanentAlert("CPU使用率过高", f"已用 {x}%")
|
||||||
|
|
||||||
|
|
||||||
def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
return NormalPermanentAlert("Driving Model Lagging", f"{sm['modelV2'].frameDropPerc:.1f}% frames dropped")
|
return NormalPermanentAlert("驾驶模型滞后", f"已丢帧 {sm['modelV2'].frameDropPerc:.1f}%")
|
||||||
|
|
||||||
|
|
||||||
def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
text = "Enable Adaptive Cruise to Engage"
|
text = "启用自适应巡航以接合"
|
||||||
if CP.brand == "honda":
|
if CP.brand == "honda":
|
||||||
text = "Enable Main Switch to Engage"
|
text = "启用主开关以接合"
|
||||||
return NoEntryAlert(text)
|
return NoEntryAlert(text)
|
||||||
|
|
||||||
|
|
||||||
def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
gb = sm['carControl'].actuators.accel / 4.
|
gb = sm['carControl'].actuators.accel / 4.
|
||||||
steer = sm['carControl'].actuators.torque
|
steer = sm['carControl'].actuators.torque
|
||||||
vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%"
|
vals = f"油门: {round(gb * 100.)}%, 转向: {round(steer * 100.)}%"
|
||||||
return NormalPermanentAlert("Joystick Mode", vals)
|
return NormalPermanentAlert("操纵杆模式", vals)
|
||||||
|
|
||||||
|
|
||||||
def longitudinal_maneuver_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def longitudinal_maneuver_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
@ -367,18 +367,25 @@ def longitudinal_maneuver_alert(CP: car.CarParams, CS: car.CarState, sm: messagi
|
|||||||
|
|
||||||
def personality_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def personality_changed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
personality = str(personality).title()
|
personality = str(personality).title()
|
||||||
return NormalPermanentAlert(f"Driving Personality: {personality}", duration=1.5)
|
personality_en = ""
|
||||||
|
if personality == "Aggressive":
|
||||||
|
personality_en = "激进"
|
||||||
|
elif personality == "Standard":
|
||||||
|
personality_en = "标准"
|
||||||
|
elif personality == "Relaxed":
|
||||||
|
personality_en = "舒适"
|
||||||
|
return NormalPermanentAlert(f"驾驶风格: {personality_en}", duration=1.5)
|
||||||
|
|
||||||
|
|
||||||
def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
|
||||||
text = "Toggle stock LKAS on or off to engage"
|
text = "请切换原厂LKAS状态以启用"
|
||||||
if CP.brand == "tesla":
|
if CP.brand == "tesla":
|
||||||
text = "Switch to Traffic-Aware Cruise Control to engage"
|
text = "请切换到交通感知巡航控制以启用"
|
||||||
elif CP.brand == "mazda":
|
elif CP.brand == "mazda":
|
||||||
text = "Enable your car's LKAS to engage"
|
text = "请启用您的车辆LKAS以启用"
|
||||||
elif CP.brand == "nissan":
|
elif CP.brand == "nissan":
|
||||||
text = "Disable your car's stock LKAS to engage"
|
text = "请禁用您的车辆原厂LKAS以启用"
|
||||||
return NormalPermanentAlert("Invalid LKAS setting", text)
|
return NormalPermanentAlert("无效的LKAS设置", text)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -392,21 +399,21 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.joystickDebug: {
|
EventName.joystickDebug: {
|
||||||
ET.WARNING: joystick_alert,
|
ET.WARNING: joystick_alert,
|
||||||
ET.PERMANENT: NormalPermanentAlert("Joystick Mode"),
|
ET.PERMANENT: NormalPermanentAlert("操纵杆模式"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.longitudinalManeuver: {
|
EventName.longitudinalManeuver: {
|
||||||
ET.WARNING: longitudinal_maneuver_alert,
|
ET.WARNING: longitudinal_maneuver_alert,
|
||||||
ET.PERMANENT: NormalPermanentAlert("Longitudinal Maneuver Mode",
|
ET.PERMANENT: NormalPermanentAlert("纵向操作模式",
|
||||||
"Ensure road ahead is clear"),
|
"确保前方道路畅通"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.selfdriveInitializing: {
|
EventName.selfdriveInitializing: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("System Initializing"),
|
ET.NO_ENTRY: NoEntryAlert("系统初始化中"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.startup: {
|
EventName.startup: {
|
||||||
ET.PERMANENT: StartupAlert("Be ready to take over at any time")
|
ET.PERMANENT: StartupAlert("请随时准备接管您的车辆控制权")
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.startupMaster: {
|
EventName.startupMaster: {
|
||||||
@ -414,28 +421,28 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
EventName.startupNoControl: {
|
EventName.startupNoControl: {
|
||||||
ET.PERMANENT: StartupAlert("Dashcam mode"),
|
ET.PERMANENT: StartupAlert("仅行车记录仪模式"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Dashcam mode"),
|
ET.NO_ENTRY: NoEntryAlert("仅行车记录仪模式"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.startupNoCar: {
|
EventName.startupNoCar: {
|
||||||
ET.PERMANENT: StartupAlert("Dashcam mode for unsupported car"),
|
ET.PERMANENT: StartupAlert("不支持车辆的行车记录仪模式"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.startupNoSecOcKey: {
|
EventName.startupNoSecOcKey: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
|
ET.PERMANENT: NormalPermanentAlert("仅行车记录仪模式",
|
||||||
"Security Key Not Available",
|
"安全密钥不可用",
|
||||||
priority=Priority.HIGH),
|
priority=Priority.HIGH),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.dashcamMode: {
|
EventName.dashcamMode: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
|
ET.PERMANENT: NormalPermanentAlert("行车记录仪模式",
|
||||||
priority=Priority.LOWEST),
|
priority=Priority.LOWEST),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.invalidLkasSetting: {
|
EventName.invalidLkasSetting: {
|
||||||
ET.PERMANENT: invalid_lkas_setting_alert,
|
ET.PERMANENT: invalid_lkas_setting_alert,
|
||||||
ET.NO_ENTRY: NoEntryAlert("Invalid LKAS setting"),
|
ET.NO_ENTRY: NoEntryAlert("车道保持辅助系统(LKAS)设置无效"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.cruiseMismatch: {
|
EventName.cruiseMismatch: {
|
||||||
@ -446,40 +453,40 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# read-only mode. This can be solved by adding your fingerprint.
|
# read-only mode. This can be solved by adding your fingerprint.
|
||||||
# See https://github.com/commaai/openpilot/wiki/Fingerprinting for more information
|
# See https://github.com/commaai/openpilot/wiki/Fingerprinting for more information
|
||||||
EventName.carUnrecognized: {
|
EventName.carUnrecognized: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Dashcam Mode",
|
ET.PERMANENT: NormalPermanentAlert("行车记录仪模式",
|
||||||
"Car Unrecognized",
|
"车辆未识别",
|
||||||
priority=Priority.LOWEST),
|
priority=Priority.LOWEST),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.aeb: {
|
EventName.aeb: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"BRAKE!",
|
"刹车!",
|
||||||
"Emergency Braking: Risk of Collision",
|
"紧急制动:可能发生碰撞",
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
|
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("AEB: Risk of Collision"),
|
ET.NO_ENTRY: NoEntryAlert("AEB:可能发生碰撞"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.stockAeb: {
|
EventName.stockAeb: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"BRAKE!",
|
"刹车!",
|
||||||
"Stock AEB: Risk of Collision",
|
"原厂AEB:可能发生碰撞",
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
|
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 2.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Stock AEB: Risk of Collision"),
|
ET.NO_ENTRY: NoEntryAlert("原厂AEB:可能发生碰撞"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.fcw: {
|
EventName.fcw: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"BRAKE!",
|
"刹车!",
|
||||||
"Risk of Collision",
|
"可能发生碰撞",
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.warningSoft, 2.),
|
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.warningSoft, 2.),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.ldw: {
|
EventName.ldw: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Lane Departure Detected",
|
"监测偏离车道",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.),
|
Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.),
|
||||||
@ -489,7 +496,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.steerTempUnavailableSilent: {
|
EventName.steerTempUnavailableSilent: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Steering Assist Temporarily Unavailable",
|
"转向暂时不可用",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8),
|
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8),
|
||||||
@ -497,7 +504,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.preDriverDistracted: {
|
EventName.preDriverDistracted: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Pay Attention",
|
"请注意",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
||||||
@ -505,23 +512,23 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.promptDriverDistracted: {
|
EventName.promptDriverDistracted: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Pay Attention",
|
"请注意",
|
||||||
"Driver Distracted",
|
"驾驶员分心",
|
||||||
AlertStatus.userPrompt, AlertSize.mid,
|
AlertStatus.userPrompt, AlertSize.mid,
|
||||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
|
Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.driverDistracted: {
|
EventName.driverDistracted: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"DISENGAGE IMMEDIATELY",
|
"立即解除控制",
|
||||||
"Driver Distracted",
|
"驾驶员分心",
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
|
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.preDriverUnresponsive: {
|
EventName.preDriverUnresponsive: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Touch Steering Wheel: No Face Detected",
|
"触摸方向盘:未检测到面部",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1),
|
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1),
|
||||||
@ -529,31 +536,31 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.promptDriverUnresponsive: {
|
EventName.promptDriverUnresponsive: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Touch Steering Wheel",
|
"触摸方向盘",
|
||||||
"Driver Unresponsive",
|
"驾驶员无响应",
|
||||||
AlertStatus.userPrompt, AlertSize.mid,
|
AlertStatus.userPrompt, AlertSize.mid,
|
||||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
|
Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.driverUnresponsive: {
|
EventName.driverUnresponsive: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"DISENGAGE IMMEDIATELY",
|
"立即解除控制",
|
||||||
"Driver Unresponsive",
|
"驾驶员无响应",
|
||||||
AlertStatus.critical, AlertSize.full,
|
AlertStatus.critical, AlertSize.full,
|
||||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
|
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.manualRestart: {
|
EventName.manualRestart: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"TAKE CONTROL",
|
"接管控制",
|
||||||
"Resume Driving Manually",
|
"请手动继续驾驶",
|
||||||
AlertStatus.userPrompt, AlertSize.mid,
|
AlertStatus.userPrompt, AlertSize.mid,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.resumeRequired: {
|
EventName.resumeRequired: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Press Resume to Exit Standstill",
|
"按恢复键以解除停止状态",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
||||||
@ -565,7 +572,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.preLaneChangeLeft: {
|
EventName.preLaneChangeLeft: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Steer Left to Start Lane Change Once Safe",
|
"请确认安全后进行左转变道",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
||||||
@ -573,7 +580,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.preLaneChangeRight: {
|
EventName.preLaneChangeRight: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Steer Right to Start Lane Change Once Safe",
|
"请确认安全后进行右转变道",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
||||||
@ -581,7 +588,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.laneChangeBlocked: {
|
EventName.laneChangeBlocked: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Car Detected in Blindspot",
|
"盲点检测到车辆",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1),
|
Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1),
|
||||||
@ -589,7 +596,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.laneChange: {
|
EventName.laneChange: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Changing Lanes",
|
"正在变道",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1),
|
||||||
@ -597,41 +604,41 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.steerSaturated: {
|
EventName.steerSaturated: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Take Control",
|
"请接管控制",
|
||||||
"Turn Exceeds Steering Limit",
|
"转向超出限制",
|
||||||
AlertStatus.userPrompt, AlertSize.mid,
|
AlertStatus.userPrompt, AlertSize.mid,
|
||||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.),
|
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 2.),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Thrown when the fan is driven at >50% but is not rotating
|
# Thrown when the fan is driven at >50% but is not rotating
|
||||||
EventName.fanMalfunction: {
|
EventName.fanMalfunction: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Fan Malfunction", "Likely Hardware Issue"),
|
ET.PERMANENT: NormalPermanentAlert("风扇故障", "可能是硬件问题"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Camera is not outputting frames
|
# Camera is not outputting frames
|
||||||
EventName.cameraMalfunction: {
|
EventName.cameraMalfunction: {
|
||||||
ET.PERMANENT: camera_malfunction_alert,
|
ET.PERMANENT: camera_malfunction_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Camera Malfunction"),
|
ET.SOFT_DISABLE: soft_disable_alert("摄像头故障"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Camera Malfunction: Reboot Your Device"),
|
ET.NO_ENTRY: NoEntryAlert("摄像头故障:请重启设备"),
|
||||||
},
|
},
|
||||||
# Camera framerate too low
|
# Camera framerate too low
|
||||||
EventName.cameraFrameRate: {
|
EventName.cameraFrameRate: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Camera Frame Rate Low", "Reboot your Device"),
|
ET.PERMANENT: NormalPermanentAlert("摄像头帧率低", "请重启设备"),
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Camera Frame Rate Low"),
|
ET.SOFT_DISABLE: soft_disable_alert("摄像头帧率低"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Camera Frame Rate Low: Reboot Your Device"),
|
ET.NO_ENTRY: NoEntryAlert("摄像头帧率低:请重启设备"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Unused
|
# Unused
|
||||||
|
|
||||||
EventName.locationdTemporaryError: {
|
EventName.locationdTemporaryError: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("locationd Temporary Error"),
|
ET.NO_ENTRY: NoEntryAlert("locationd临时错误"),
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("locationd Temporary Error"),
|
ET.SOFT_DISABLE: soft_disable_alert("locationd临时错误"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.locationdPermanentError: {
|
EventName.locationdPermanentError: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("locationd Permanent Error"),
|
ET.NO_ENTRY: NoEntryAlert("locationd永久错误"),
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("locationd Permanent Error"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("locationd永久错误"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("locationd Permanent Error"),
|
ET.PERMANENT: NormalPermanentAlert("locationd永久错误"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# openpilot tries to learn certain parameters about your car by observing
|
# openpilot tries to learn certain parameters about your car by observing
|
||||||
@ -644,13 +651,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub
|
# bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub
|
||||||
EventName.paramsdTemporaryError: {
|
EventName.paramsdTemporaryError: {
|
||||||
ET.NO_ENTRY: paramsd_invalid_alert,
|
ET.NO_ENTRY: paramsd_invalid_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("paramsd Temporary Error"),
|
ET.SOFT_DISABLE: soft_disable_alert("paramsd 临时错误"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.paramsdPermanentError: {
|
EventName.paramsdPermanentError: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("paramsd Permanent Error"),
|
ET.NO_ENTRY: NoEntryAlert("paramsd永久错误"),
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("paramsd Permanent Error"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("paramsd永久错误"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("paramsd Permanent Error"),
|
ET.PERMANENT: NormalPermanentAlert("paramsd永久错误"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# ********** events that affect controls state transitions **********
|
# ********** events that affect controls state transitions **********
|
||||||
@ -669,12 +676,12 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.buttonCancel: {
|
EventName.buttonCancel: {
|
||||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Cancel Pressed"),
|
ET.NO_ENTRY: NoEntryAlert("取消按钮被按下"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.brakeHold: {
|
EventName.brakeHold: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Press Resume to Exit Brake Hold",
|
"按恢复键以解除制动保持",
|
||||||
"",
|
"",
|
||||||
AlertStatus.userPrompt, AlertSize.small,
|
AlertStatus.userPrompt, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2),
|
||||||
@ -682,23 +689,23 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.parkBrake: {
|
EventName.parkBrake: {
|
||||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Parking Brake Engaged"),
|
ET.NO_ENTRY: NoEntryAlert("停车制动已启用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.pedalPressed: {
|
EventName.pedalPressed: {
|
||||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Pedal Pressed",
|
ET.NO_ENTRY: NoEntryAlert("踏板被按下",
|
||||||
visual_alert=VisualAlert.brakePressed),
|
visual_alert=VisualAlert.brakePressed),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.steerDisengage: {
|
EventName.steerDisengage: {
|
||||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Steering Pressed"),
|
ET.NO_ENTRY: NoEntryAlert("方向盘被转动"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.preEnableStandstill: {
|
EventName.preEnableStandstill: {
|
||||||
ET.PRE_ENABLE: Alert(
|
ET.PRE_ENABLE: Alert(
|
||||||
"Release Brake to Engage",
|
"释放制动以启用",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.),
|
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.),
|
||||||
@ -726,27 +733,27 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
EventName.resumeBlocked: {
|
EventName.resumeBlocked: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("Press Set to Engage"),
|
ET.NO_ENTRY: NoEntryAlert("请按设定键以启用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.wrongCruiseMode: {
|
EventName.wrongCruiseMode: {
|
||||||
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Adaptive Cruise Disabled"),
|
ET.NO_ENTRY: NoEntryAlert("自适应巡航已禁用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.steerTempUnavailable: {
|
EventName.steerTempUnavailable: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Steering Assist Temporarily Unavailable"),
|
ET.SOFT_DISABLE: soft_disable_alert("转向暂时不可用"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"),
|
ET.NO_ENTRY: NoEntryAlert("转向暂时不可用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.steerTimeLimit: {
|
EventName.steerTimeLimit: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Vehicle Steering Time Limit"),
|
ET.SOFT_DISABLE: soft_disable_alert("车辆转向时间限制"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Vehicle Steering Time Limit"),
|
ET.NO_ENTRY: NoEntryAlert("车辆转向时间限制"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.outOfSpace: {
|
EventName.outOfSpace: {
|
||||||
ET.PERMANENT: out_of_space_alert,
|
ET.PERMANENT: out_of_space_alert,
|
||||||
ET.NO_ENTRY: NoEntryAlert("Out of Storage"),
|
ET.NO_ENTRY: NoEntryAlert("出库"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.belowEngageSpeed: {
|
EventName.belowEngageSpeed: {
|
||||||
@ -755,35 +762,35 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
|
|
||||||
EventName.sensorDataInvalid: {
|
EventName.sensorDataInvalid: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Sensor Data Invalid",
|
"传感器数据无效",
|
||||||
"Possible Hardware Issue",
|
"可能是硬件问题",
|
||||||
AlertStatus.normal, AlertSize.mid,
|
AlertStatus.normal, AlertSize.mid,
|
||||||
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.),
|
Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=1.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Sensor Data Invalid"),
|
ET.NO_ENTRY: NoEntryAlert("传感器数据无效"),
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Sensor Data Invalid"),
|
ET.SOFT_DISABLE: soft_disable_alert("传感器数据无效"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.noGps: {
|
EventName.noGps: {
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.tooDistracted: {
|
EventName.tooDistracted: {
|
||||||
ET.NO_ENTRY: NoEntryAlert("Distraction Level Too High"),
|
ET.NO_ENTRY: NoEntryAlert("注意力分散程度过高"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.excessiveActuation: {
|
EventName.excessiveActuation: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Excessive Actuation"),
|
ET.SOFT_DISABLE: soft_disable_alert("过度操作"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Excessive Actuation"),
|
ET.NO_ENTRY: NoEntryAlert("过度操作"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.overheat: {
|
EventName.overheat: {
|
||||||
ET.PERMANENT: overheat_alert,
|
ET.PERMANENT: overheat_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("System Overheated"),
|
ET.SOFT_DISABLE: soft_disable_alert("系统过热"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("System Overheated"),
|
ET.NO_ENTRY: NoEntryAlert("系统过热"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.wrongGear: {
|
EventName.wrongGear: {
|
||||||
ET.SOFT_DISABLE: user_soft_disable_alert("Gear not D"),
|
ET.SOFT_DISABLE: user_soft_disable_alert("挡位不在D挡"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Gear not D"),
|
ET.NO_ENTRY: NoEntryAlert("挡位不在D挡"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# This alert is thrown when the calibration angles are outside of the acceptable range.
|
# This alert is thrown when the calibration angles are outside of the acceptable range.
|
||||||
@ -793,40 +800,40 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# See https://comma.ai/setup for more information
|
# See https://comma.ai/setup for more information
|
||||||
EventName.calibrationInvalid: {
|
EventName.calibrationInvalid: {
|
||||||
ET.PERMANENT: calibration_invalid_alert,
|
ET.PERMANENT: calibration_invalid_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"),
|
ET.SOFT_DISABLE: soft_disable_alert("校准无效:重新安装设备并重新校准"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"),
|
ET.NO_ENTRY: NoEntryAlert("校准无效:重新安装设备并重新校准"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.calibrationIncomplete: {
|
EventName.calibrationIncomplete: {
|
||||||
ET.PERMANENT: calibration_incomplete_alert,
|
ET.PERMANENT: calibration_incomplete_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Calibration Incomplete"),
|
ET.SOFT_DISABLE: soft_disable_alert("校准未完成"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"),
|
ET.NO_ENTRY: NoEntryAlert("校准进行中"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.calibrationRecalibrating: {
|
EventName.calibrationRecalibrating: {
|
||||||
ET.PERMANENT: calibration_incomplete_alert,
|
ET.PERMANENT: calibration_incomplete_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Device Remount Detected: Recalibrating"),
|
ET.SOFT_DISABLE: soft_disable_alert("设备重新安装检测到:重新校准中"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Remount Detected: Recalibrating"),
|
ET.NO_ENTRY: NoEntryAlert("设备重新安装检测到:重新校准中"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.doorOpen: {
|
EventName.doorOpen: {
|
||||||
ET.SOFT_DISABLE: user_soft_disable_alert("Door Open"),
|
ET.SOFT_DISABLE: user_soft_disable_alert("车门开启"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Door Open"),
|
ET.NO_ENTRY: NoEntryAlert("车门开启"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.seatbeltNotLatched: {
|
EventName.seatbeltNotLatched: {
|
||||||
ET.SOFT_DISABLE: user_soft_disable_alert("Seatbelt Unlatched"),
|
ET.SOFT_DISABLE: user_soft_disable_alert("安全带未系"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Seatbelt Unlatched"),
|
ET.NO_ENTRY: NoEntryAlert("安全带未系"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.espDisabled: {
|
EventName.espDisabled: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Disabled"),
|
ET.SOFT_DISABLE: soft_disable_alert("电子稳定控制系统已禁用"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Disabled"),
|
ET.NO_ENTRY: NoEntryAlert("电子稳定控制系统已禁用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.lowBattery: {
|
EventName.lowBattery: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Low Battery"),
|
ET.SOFT_DISABLE: soft_disable_alert("电池电量低"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Low Battery"),
|
ET.NO_ENTRY: NoEntryAlert("电池电量低"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Different openpilot services communicate between each other at a certain
|
# Different openpilot services communicate between each other at a certain
|
||||||
@ -834,41 +841,41 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# is thrown. This can mean a service crashed, did not broadcast a message for
|
# is thrown. This can mean a service crashed, did not broadcast a message for
|
||||||
# ten times the regular interval, or the average interval is more than 10% too high.
|
# ten times the regular interval, or the average interval is more than 10% too high.
|
||||||
EventName.commIssue: {
|
EventName.commIssue: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Communication Issue Between Processes"),
|
ET.SOFT_DISABLE: soft_disable_alert("进程间通信问题"),
|
||||||
ET.NO_ENTRY: comm_issue_alert,
|
ET.NO_ENTRY: comm_issue_alert,
|
||||||
},
|
},
|
||||||
EventName.commIssueAvgFreq: {
|
EventName.commIssueAvgFreq: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Low Communication Rate Between Processes"),
|
ET.SOFT_DISABLE: soft_disable_alert("进程间通信速率低"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Low Communication Rate Between Processes"),
|
ET.NO_ENTRY: NoEntryAlert("进程间通信速率低"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.selfdrivedLagging: {
|
EventName.selfdrivedLagging: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("System Lagging"),
|
ET.SOFT_DISABLE: soft_disable_alert("系统滞后"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Selfdrive Process Lagging: Reboot Your Device"),
|
ET.NO_ENTRY: NoEntryAlert("自驾车进程滞后:请重启设备"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Thrown when manager detects a service exited unexpectedly while driving
|
# Thrown when manager detects a service exited unexpectedly while driving
|
||||||
EventName.processNotRunning: {
|
EventName.processNotRunning: {
|
||||||
ET.NO_ENTRY: process_not_running_alert,
|
ET.NO_ENTRY: process_not_running_alert,
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Process Not Running"),
|
ET.SOFT_DISABLE: soft_disable_alert("进程未运行"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.radarFault: {
|
EventName.radarFault: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Radar Error: Restart the Car"),
|
ET.SOFT_DISABLE: soft_disable_alert("雷达错误:请重启车辆"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"),
|
ET.NO_ENTRY: NoEntryAlert("雷达错误:请重启车辆"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.radarTempUnavailable: {
|
EventName.radarTempUnavailable: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Radar Temporarily Unavailable"),
|
ET.SOFT_DISABLE: soft_disable_alert("雷达暂时不可用"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Radar Temporarily Unavailable"),
|
ET.NO_ENTRY: NoEntryAlert("雷达暂时不可用"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Every frame from the camera should be processed by the model. If modeld
|
# Every frame from the camera should be processed by the model. If modeld
|
||||||
# is not processing frames fast enough they have to be dropped. This alert is
|
# is not processing frames fast enough they have to be dropped. This alert is
|
||||||
# thrown when over 20% of frames are dropped.
|
# thrown when over 20% of frames are dropped.
|
||||||
EventName.modeldLagging: {
|
EventName.modeldLagging: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Driving Model Lagging"),
|
ET.SOFT_DISABLE: soft_disable_alert("驾驶模型滞后"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Driving Model Lagging"),
|
ET.NO_ENTRY: NoEntryAlert("驾驶模型滞后"),
|
||||||
ET.PERMANENT: modeld_lagging_alert,
|
ET.PERMANENT: modeld_lagging_alert,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -878,45 +885,45 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# usually means the model has trouble understanding the scene. This is used
|
# usually means the model has trouble understanding the scene. This is used
|
||||||
# as a heuristic to warn the driver.
|
# as a heuristic to warn the driver.
|
||||||
EventName.posenetInvalid: {
|
EventName.posenetInvalid: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Posenet Speed Invalid"),
|
ET.SOFT_DISABLE: soft_disable_alert("Posenet速度无效"),
|
||||||
ET.NO_ENTRY: posenet_invalid_alert,
|
ET.NO_ENTRY: posenet_invalid_alert,
|
||||||
},
|
},
|
||||||
|
|
||||||
# When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we
|
# When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we
|
||||||
# alert the driver the device might have fallen from the windshield.
|
# alert the driver the device might have fallen from the windshield.
|
||||||
EventName.deviceFalling: {
|
EventName.deviceFalling: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"),
|
ET.SOFT_DISABLE: soft_disable_alert("设备从支架掉落"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"),
|
ET.NO_ENTRY: NoEntryAlert("设备从支架掉落"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.lowMemory: {
|
EventName.lowMemory: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"),
|
ET.SOFT_DISABLE: soft_disable_alert("内存不足:请重启设备"),
|
||||||
ET.PERMANENT: low_memory_alert,
|
ET.PERMANENT: low_memory_alert,
|
||||||
ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"),
|
ET.NO_ENTRY: NoEntryAlert("内存不足:请重启设备"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.accFaulted: {
|
EventName.accFaulted: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("巡航故障:请重启车辆"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
|
ET.PERMANENT: NormalPermanentAlert("巡航故障:重启车辆以启用"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"),
|
ET.NO_ENTRY: NoEntryAlert("巡航故障:请重启车辆"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.espActive: {
|
EventName.espActive: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Active"),
|
ET.SOFT_DISABLE: soft_disable_alert("电子稳定控制系统激活中"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Active"),
|
ET.NO_ENTRY: NoEntryAlert("电子稳定控制系统激活中"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.controlsMismatch: {
|
EventName.controlsMismatch: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("控制不匹配"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"),
|
ET.NO_ENTRY: NoEntryAlert("控制不匹配"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# Sometimes the USB stack on the device can get into a bad state
|
# Sometimes the USB stack on the device can get into a bad state
|
||||||
# causing the connection to the panda to be lost
|
# causing the connection to the panda to be lost
|
||||||
EventName.usbError: {
|
EventName.usbError: {
|
||||||
ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"),
|
ET.SOFT_DISABLE: soft_disable_alert("USB错误:请重启设备"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device"),
|
ET.PERMANENT: NormalPermanentAlert("USB错误:请重启设备"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"),
|
ET.NO_ENTRY: NoEntryAlert("USB错误:请重启设备"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# This alert can be thrown for the following reasons:
|
# This alert can be thrown for the following reasons:
|
||||||
@ -924,45 +931,45 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# - CAN data is received, but some message are not received at the right frequency
|
# - CAN data is received, but some message are not received at the right frequency
|
||||||
# If you're not writing a new car port, this is usually cause by faulty wiring
|
# If you're not writing a new car port, this is usually cause by faulty wiring
|
||||||
EventName.canError: {
|
EventName.canError: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN总线错误"),
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"CAN Error: Check Connections",
|
"CAN总线错误:请检查连接",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"),
|
ET.NO_ENTRY: NoEntryAlert("CAN总线错误:请检查连接"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.canBusMissing: {
|
EventName.canBusMissing: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Bus Disconnected"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN总线断开连接"),
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"CAN Bus Disconnected: Likely Faulty Cable",
|
"CAN总线断开连接:可能电缆故障",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.small,
|
AlertStatus.normal, AlertSize.small,
|
||||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
|
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("CAN Bus Disconnected: Check Connections"),
|
ET.NO_ENTRY: NoEntryAlert("CAN总线断开连接:请检查连接"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.steerUnavailable: {
|
EventName.steerUnavailable: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("LKAS Fault: Restart the Car"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("LKAS故障:请重启车辆"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("LKAS Fault: Restart the car to engage"),
|
ET.PERMANENT: NormalPermanentAlert("LKAS故障:重启车辆以启用"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("LKAS Fault: Restart the Car"),
|
ET.NO_ENTRY: NoEntryAlert("LKAS故障:请重启车辆"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.reverseGear: {
|
EventName.reverseGear: {
|
||||||
ET.PERMANENT: Alert(
|
ET.PERMANENT: Alert(
|
||||||
"Reverse\nGear",
|
"倒车中",
|
||||||
"",
|
"",
|
||||||
AlertStatus.normal, AlertSize.full,
|
AlertStatus.normal, AlertSize.full,
|
||||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
|
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2, creation_delay=0.5),
|
||||||
ET.USER_DISABLE: ImmediateDisableAlert("Reverse Gear"),
|
ET.USER_DISABLE: ImmediateDisableAlert("倒档"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Reverse Gear"),
|
ET.NO_ENTRY: NoEntryAlert("倒档"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# On cars that use stock ACC the car can decide to cancel ACC for various reasons.
|
# On cars that use stock ACC the car can decide to cancel ACC for various reasons.
|
||||||
# When this happens we can no long control the car so the user needs to be warned immediately.
|
# When this happens we can no long control the car so the user needs to be warned immediately.
|
||||||
EventName.cruiseDisabled: {
|
EventName.cruiseDisabled: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Is Off"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("巡航已关闭"),
|
||||||
},
|
},
|
||||||
|
|
||||||
# When the relay in the harness box opens the CAN bus between the LKAS camera
|
# When the relay in the harness box opens the CAN bus between the LKAS camera
|
||||||
@ -970,15 +977,15 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# are received on the car side this usually means the relay hasn't opened correctly
|
# are received on the car side this usually means the relay hasn't opened correctly
|
||||||
# and this alert is thrown.
|
# and this alert is thrown.
|
||||||
EventName.relayMalfunction: {
|
EventName.relayMalfunction: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Relay Malfunction"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("线束继电器故障"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("Harness Relay Malfunction", "Check Hardware"),
|
ET.PERMANENT: NormalPermanentAlert("线束继电器故障", "检查硬件"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"),
|
ET.NO_ENTRY: NoEntryAlert("线束继电器故障"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.speedTooLow: {
|
EventName.speedTooLow: {
|
||||||
ET.IMMEDIATE_DISABLE: Alert(
|
ET.IMMEDIATE_DISABLE: Alert(
|
||||||
"openpilot Canceled",
|
"openpilot已取消",
|
||||||
"Speed too low",
|
"速度过低",
|
||||||
AlertStatus.normal, AlertSize.mid,
|
AlertStatus.normal, AlertSize.mid,
|
||||||
Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.),
|
Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.),
|
||||||
},
|
},
|
||||||
@ -986,17 +993,17 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
# When the car is driving faster than most cars in the training data, the model outputs can be unpredictable.
|
# When the car is driving faster than most cars in the training data, the model outputs can be unpredictable.
|
||||||
EventName.speedTooHigh: {
|
EventName.speedTooHigh: {
|
||||||
ET.WARNING: Alert(
|
ET.WARNING: Alert(
|
||||||
"Speed Too High",
|
"速度过高",
|
||||||
"Model uncertain at this speed",
|
"在此速度下模型不稳定",
|
||||||
AlertStatus.userPrompt, AlertSize.mid,
|
AlertStatus.userPrompt, AlertSize.mid,
|
||||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 4.),
|
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 4.),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Slow down to engage"),
|
ET.NO_ENTRY: NoEntryAlert("减速以进行接合"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.vehicleSensorsInvalid: {
|
EventName.vehicleSensorsInvalid: {
|
||||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Vehicle Sensors Invalid"),
|
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("车辆传感器无效"),
|
||||||
ET.PERMANENT: NormalPermanentAlert("Vehicle Sensors Calibrating", "Drive to Calibrate"),
|
ET.PERMANENT: NormalPermanentAlert("车辆传感器校准中", "行驶以校准"),
|
||||||
ET.NO_ENTRY: NoEntryAlert("Vehicle Sensors Calibrating"),
|
ET.NO_ENTRY: NoEntryAlert("车辆传感器校准中"),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.personalityChanged: {
|
EventName.personalityChanged: {
|
||||||
@ -1004,7 +1011,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
EventName.userBookmark: {
|
EventName.userBookmark: {
|
||||||
ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5),
|
ET.PERMANENT: NormalPermanentAlert("书签已保存", duration=1.5),
|
||||||
},
|
},
|
||||||
|
|
||||||
EventName.audioFeedback: {
|
EventName.audioFeedback: {
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class DeveloperLayout(Widget):
|
|||||||
# Build items and keep references for callbacks/state updates
|
# Build items and keep references for callbacks/state updates
|
||||||
self._adb_toggle = toggle_item(
|
self._adb_toggle = toggle_item(
|
||||||
lambda: tr("Enable ADB"),
|
lambda: tr("Enable ADB"),
|
||||||
description=lambda: DESCRIPTIONS["enable_adb"],
|
description=lambda: tr(DESCRIPTIONS["enable_adb"]),
|
||||||
initial_state=self._params.get_bool("AdbEnabled"),
|
initial_state=self._params.get_bool("AdbEnabled"),
|
||||||
callback=self._on_enable_adb,
|
callback=self._on_enable_adb,
|
||||||
enabled=ui_state.is_offroad,
|
enabled=ui_state.is_offroad,
|
||||||
@ -56,7 +56,7 @@ class DeveloperLayout(Widget):
|
|||||||
initial_state=self._params.get_bool("SshEnabled"),
|
initial_state=self._params.get_bool("SshEnabled"),
|
||||||
callback=self._on_enable_ssh,
|
callback=self._on_enable_ssh,
|
||||||
)
|
)
|
||||||
self._ssh_keys = ssh_key_item(lambda: tr("SSH Keys"), description=lambda: DESCRIPTIONS["ssh_key"])
|
self._ssh_keys = ssh_key_item(lambda: tr("SSH Keys"), description=lambda: tr(DESCRIPTIONS["ssh_key"]))
|
||||||
|
|
||||||
self._joystick_toggle = toggle_item(
|
self._joystick_toggle = toggle_item(
|
||||||
lambda: tr("Joystick Debug Mode"),
|
lambda: tr("Joystick Debug Mode"),
|
||||||
@ -75,7 +75,7 @@ class DeveloperLayout(Widget):
|
|||||||
|
|
||||||
self._alpha_long_toggle = toggle_item(
|
self._alpha_long_toggle = toggle_item(
|
||||||
lambda: tr("openpilot Longitudinal Control (Alpha)"),
|
lambda: tr("openpilot Longitudinal Control (Alpha)"),
|
||||||
description=lambda: DESCRIPTIONS["alpha_longitudinal"],
|
description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]),
|
||||||
initial_state=self._params.get_bool("AlphaLongitudinalEnabled"),
|
initial_state=self._params.get_bool("AlphaLongitudinalEnabled"),
|
||||||
callback=self._on_alpha_long_enabled,
|
callback=self._on_alpha_long_enabled,
|
||||||
enabled=lambda: not ui_state.engaged,
|
enabled=lambda: not ui_state.engaged,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import math
|
import math
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ from openpilot.system.ui.widgets.html_render import HtmlModal
|
|||||||
from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item
|
from openpilot.system.ui.widgets.list_view import text_item, button_item, dual_button_item
|
||||||
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
|
||||||
from openpilot.system.ui.widgets.scroller import Scroller
|
from openpilot.system.ui.widgets.scroller import Scroller
|
||||||
|
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||||
|
|
||||||
# Description constants
|
# Description constants
|
||||||
DESCRIPTIONS = {
|
DESCRIPTIONS = {
|
||||||
@ -27,6 +29,8 @@ DESCRIPTIONS = {
|
|||||||
'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."),
|
'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."),
|
||||||
'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"),
|
'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"),
|
||||||
}
|
}
|
||||||
|
DATA_PARAMS_D_SECOCKEY_PATH = "/data/params/d/SecOCKey"
|
||||||
|
CACHE_PARAMS_SECOCKEY_PATH = "/cache/params/SecOCKey"
|
||||||
|
|
||||||
|
|
||||||
class DeviceLayout(Widget):
|
class DeviceLayout(Widget):
|
||||||
@ -46,6 +50,9 @@ class DeviceLayout(Widget):
|
|||||||
self._dp_vehicle_selector_make_dialog: MultiOptionDialog | None = None
|
self._dp_vehicle_selector_make_dialog: MultiOptionDialog | None = None
|
||||||
self._dp_vehicle_selector_model_dialog: MultiOptionDialog | None = None
|
self._dp_vehicle_selector_model_dialog: MultiOptionDialog | None = None
|
||||||
|
|
||||||
|
self._keyboard = Keyboard(max_text_size=64, min_text_size=8, show_password_toggle=True)
|
||||||
|
self._secoc_key = self._params.get("SecOCKey") or self._read_key_from_files()
|
||||||
|
|
||||||
items = self._initialize_items()
|
items = self._initialize_items()
|
||||||
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
||||||
|
|
||||||
@ -71,6 +78,8 @@ class DeviceLayout(Widget):
|
|||||||
text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))),
|
text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))),
|
||||||
text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))),
|
text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))),
|
||||||
self._pair_device_btn,
|
self._pair_device_btn,
|
||||||
|
button_item(lambda: tr("SecOCKey Install"), lambda: tr("INSTALL"), lambda: self._secoc_key, callback=self._install_secockey, enabled=ui_state.is_offroad),
|
||||||
|
self._reset_calib_btn,
|
||||||
button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']),
|
button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']),
|
||||||
callback=self._show_driver_camera, enabled=ui_state.is_offroad and "LITE" not in os.environ),
|
callback=self._show_driver_camera, enabled=ui_state.is_offroad and "LITE" not in os.environ),
|
||||||
self._reset_calib_btn,
|
self._reset_calib_btn,
|
||||||
@ -101,7 +110,7 @@ class DeviceLayout(Widget):
|
|||||||
self._select_language_dialog = None
|
self._select_language_dialog = None
|
||||||
|
|
||||||
self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language],
|
self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language],
|
||||||
option_font_weight=FontWeight.UNIFONT)
|
option_font_weight=FontWeight.CHINA)
|
||||||
gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection)
|
gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection)
|
||||||
|
|
||||||
def _show_driver_camera(self):
|
def _show_driver_camera(self):
|
||||||
@ -299,3 +308,87 @@ class DeviceLayout(Widget):
|
|||||||
dialog = ConfirmDialog(tr("Are you sure you want to switch?"), tr("CONFIRM"))
|
dialog = ConfirmDialog(tr("Are you sure you want to switch?"), tr("CONFIRM"))
|
||||||
gui_app.set_modal_overlay(dialog, callback=on_off_road)
|
gui_app.set_modal_overlay(dialog, callback=on_off_road)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_key_valid(key: str) -> bool:
|
||||||
|
"""Checks if the key is a valid 32-character lowercase hexadecimal string."""
|
||||||
|
if not isinstance(key, str):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(key) != 32:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pattern = r"^[0-9a-f]{32}$"
|
||||||
|
return bool(re.match(pattern, key))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_key_to_file(file_path: str, key: str) -> None:
|
||||||
|
"""Writes the key to the specified file path."""
|
||||||
|
try:
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write(key)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error writing key to file {file_path}: {e}")
|
||||||
|
|
||||||
|
def _read_key_from_file(self, file_path: str) -> str | None:
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
key = f.read().strip()
|
||||||
|
if self._is_key_valid(key):
|
||||||
|
return key
|
||||||
|
else:
|
||||||
|
# Key is invalid, delete the file
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
print(f"Deleted invalid key file: {file_path} which contained {key}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting invalid key file {file_path}: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading key file {file_path}: {e}")
|
||||||
|
return None # Return None on any error
|
||||||
|
|
||||||
|
def _read_key_from_files(self) -> str | None:
|
||||||
|
"""Reads the key from the appropriate file(s) based on the AGNOS environment."""
|
||||||
|
data_params_d_secockey = self._read_key_from_file(DATA_PARAMS_D_SECOCKEY_PATH)
|
||||||
|
cache_params_secockey = self._read_key_from_file(CACHE_PARAMS_SECOCKEY_PATH)
|
||||||
|
existing_key = cache_params_secockey or data_params_d_secockey
|
||||||
|
|
||||||
|
if not existing_key:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Write the existing key to missing files
|
||||||
|
if data_params_d_secockey != existing_key:
|
||||||
|
self._write_key_to_file(DATA_PARAMS_D_SECOCKEY_PATH, existing_key)
|
||||||
|
if cache_params_secockey != existing_key:
|
||||||
|
self._write_key_to_file(CACHE_PARAMS_SECOCKEY_PATH, existing_key)
|
||||||
|
|
||||||
|
return existing_key
|
||||||
|
|
||||||
|
def _install_secockey(self):
|
||||||
|
def enter_secoc_key(result):
|
||||||
|
key = self._keyboard.text
|
||||||
|
|
||||||
|
if key == "":
|
||||||
|
gui_app.set_modal_overlay(alert_dialog(tr("Key cannot be empty.")))
|
||||||
|
return False
|
||||||
|
if len(key) != 32:
|
||||||
|
gui_app.set_modal_overlay(alert_dialog(tr("Key must be exactly 32 characters long. Current length: {} characters.").format(len(key))))
|
||||||
|
return False
|
||||||
|
if not self._is_key_valid(key):
|
||||||
|
gui_app.set_modal_overlay(alert_dialog(tr("Invalid key format. Key must contain only hexadecimal characters (0-9, a-f).")))
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._secoc_key = key
|
||||||
|
self._params.put("SecOCKey", key)
|
||||||
|
self._write_key_to_file(DATA_PARAMS_D_SECOCKEY_PATH, key)
|
||||||
|
self._write_key_to_file(CACHE_PARAMS_SECOCKEY_PATH, key)
|
||||||
|
gui_app.set_modal_overlay(alert_dialog(tr("Success!\nRestart comma to have openpilot use the key")))
|
||||||
|
|
||||||
|
self._keyboard.reset(min_text_size=0)
|
||||||
|
self._keyboard.set_text(self._secoc_key or "")
|
||||||
|
self._keyboard.set_title(tr("Enter your Car Security Key"), tr("Archived key: \"{}\"").format(self._secoc_key))
|
||||||
|
gui_app.set_modal_overlay(self._keyboard, enter_secoc_key)
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ from enum import IntEnum
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
|
||||||
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
||||||
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
|
|
||||||
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
|
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
|
||||||
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
||||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||||
@ -65,9 +64,8 @@ class SettingsLayout(Widget):
|
|||||||
PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)),
|
PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager)),
|
||||||
PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()),
|
PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout()),
|
||||||
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
|
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
|
||||||
PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()),
|
|
||||||
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
|
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
|
||||||
PanelType.DRAGONPILOT: PanelInfo("dp", DragonpilotLayout()),
|
PanelType.DRAGONPILOT: PanelInfo(tr_noop("Dp"), DragonpilotLayout()),
|
||||||
}
|
}
|
||||||
|
|
||||||
self._font_medium = gui_app.font(FontWeight.MEDIUM)
|
self._font_medium = gui_app.font(FontWeight.MEDIUM)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class SoftwareLayout(Widget):
|
|||||||
self._version_item,
|
self._version_item,
|
||||||
self._download_btn,
|
self._download_btn,
|
||||||
self._install_btn,
|
self._install_btn,
|
||||||
# self._branch_btn, # rick - disable this for now
|
self._branch_btn,
|
||||||
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
|
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
|
||||||
], line_separator=True, spacing=0)
|
], line_separator=True, spacing=0)
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,14 @@ DESCRIPTIONS = {
|
|||||||
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
|
"Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line " +
|
||||||
"without a turn signal activated while driving over 31 mph (50 km/h)."
|
"without a turn signal activated while driving over 31 mph (50 km/h)."
|
||||||
),
|
),
|
||||||
"AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."),
|
"AlwaysOnDM": tr_noop("The driver monitoring system can be toggled on/off, but long-term activation is recommended"),
|
||||||
|
"DistractionDetectionLevel": tr_noop(
|
||||||
|
"Set how sensitive the driver distraction detection should be. " +
|
||||||
|
"Strict: Very sensitive, warns on minor distractions. " +
|
||||||
|
"Moderate: Balanced between sensitivity and false positives. " +
|
||||||
|
"Lenient: Only alerts on clear distractions. " +
|
||||||
|
"Off: Disable Driver Distraction Detection and Control."
|
||||||
|
),
|
||||||
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
|
'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."),
|
||||||
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
|
"IsMetric": tr_noop("Display speed in km/h instead of mph."),
|
||||||
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."),
|
"RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."),
|
||||||
@ -109,7 +116,7 @@ class TogglesLayout(Widget):
|
|||||||
|
|
||||||
self._long_personality_setting = multiple_button_item(
|
self._long_personality_setting = multiple_button_item(
|
||||||
lambda: tr("Driving Personality"),
|
lambda: tr("Driving Personality"),
|
||||||
DESCRIPTIONS["LongitudinalPersonality"],
|
tr(DESCRIPTIONS["LongitudinalPersonality"]),
|
||||||
buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")],
|
buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")],
|
||||||
button_width=255,
|
button_width=255,
|
||||||
callback=self._set_longitudinal_personality,
|
callback=self._set_longitudinal_personality,
|
||||||
@ -117,6 +124,16 @@ class TogglesLayout(Widget):
|
|||||||
icon="speed_limit.png"
|
icon="speed_limit.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._distraction_detection_level = multiple_button_item(
|
||||||
|
lambda: tr("Distraction Detection Level"),
|
||||||
|
tr(DESCRIPTIONS["DistractionDetectionLevel"]),
|
||||||
|
buttons=[lambda: tr("Strict"), lambda: tr("Moderate"), lambda: tr("Lenient")],
|
||||||
|
button_width=255,
|
||||||
|
callback=self._set_distraction_detection_level,
|
||||||
|
selected_index=self._params.get("DistractionDetectionLevel", return_default=True),
|
||||||
|
icon="monitoring.png"
|
||||||
|
)
|
||||||
|
|
||||||
self._toggles = {}
|
self._toggles = {}
|
||||||
self._locked_toggles = set()
|
self._locked_toggles = set()
|
||||||
|
|
||||||
@ -156,6 +173,12 @@ class TogglesLayout(Widget):
|
|||||||
if param == "DisengageOnAccelerator":
|
if param == "DisengageOnAccelerator":
|
||||||
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
|
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
|
||||||
|
|
||||||
|
if param == "AlwaysOnDM":
|
||||||
|
# 根据AlwaysOnDM状态动态显示/隐藏分心检测级别
|
||||||
|
self._toggles["DistractionDetectionLevel"] = self._distraction_detection_level
|
||||||
|
# 初始设置可见性
|
||||||
|
self._update_distraction_detection_visibility()
|
||||||
|
|
||||||
self._update_experimental_mode_icon()
|
self._update_experimental_mode_icon()
|
||||||
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
|
||||||
|
|
||||||
@ -228,6 +251,13 @@ class TogglesLayout(Widget):
|
|||||||
def _render(self, rect):
|
def _render(self, rect):
|
||||||
self._scroller.render(rect)
|
self._scroller.render(rect)
|
||||||
|
|
||||||
|
def _update_distraction_detection_visibility(self):
|
||||||
|
"""根据AlwaysOnDM状态更新分心检测级别的可见性"""
|
||||||
|
always_on_dm_enabled = self._params.get_bool("AlwaysOnDM")
|
||||||
|
if "DistractionDetectionLevel" in self._toggles:
|
||||||
|
# 设置分心检测级别的可见性
|
||||||
|
self._toggles["DistractionDetectionLevel"].set_visible(always_on_dm_enabled)
|
||||||
|
|
||||||
def _update_experimental_mode_icon(self):
|
def _update_experimental_mode_icon(self):
|
||||||
icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png"
|
icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png"
|
||||||
self._toggles["ExperimentalMode"].set_icon(icon)
|
self._toggles["ExperimentalMode"].set_icon(icon)
|
||||||
@ -261,5 +291,12 @@ class TogglesLayout(Widget):
|
|||||||
if self._toggle_defs[param][3]:
|
if self._toggle_defs[param][3]:
|
||||||
self._params.put_bool("OnroadCycleRequested", True)
|
self._params.put_bool("OnroadCycleRequested", True)
|
||||||
|
|
||||||
|
# 如果切换的是AlwaysOnDM,更新分心检测级别的可见性
|
||||||
|
if param == "AlwaysOnDM":
|
||||||
|
self._update_distraction_detection_visibility()
|
||||||
|
|
||||||
def _set_longitudinal_personality(self, button_index: int):
|
def _set_longitudinal_personality(self, button_index: int):
|
||||||
self._params.put("LongitudinalPersonality", button_index)
|
self._params.put("LongitudinalPersonality", button_index)
|
||||||
|
|
||||||
|
def _set_distraction_detection_level(self, button_index: int):
|
||||||
|
self._params.put("DistractionDetectionLevel", button_index)
|
||||||
|
|||||||
@ -68,9 +68,9 @@ class Sidebar(Widget):
|
|||||||
self._net_type = NETWORK_TYPES.get(NetworkType.none)
|
self._net_type = NETWORK_TYPES.get(NetworkType.none)
|
||||||
self._net_strength = 0
|
self._net_strength = 0
|
||||||
|
|
||||||
self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
|
self._temp_status = MetricData(tr_noop("TEMP"), "38°C", Colors.GOOD)
|
||||||
|
self._cpu_status = MetricData(tr_noop("CPU"), "10%", Colors.GOOD)
|
||||||
self._panda_status = MetricData(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD)
|
self._panda_status = MetricData(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD)
|
||||||
self._connect_status = MetricData(tr_noop("CONNECT"), tr_noop("OFFLINE"), Colors.WARNING)
|
|
||||||
self._recording_audio = False
|
self._recording_audio = False
|
||||||
|
|
||||||
self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height)
|
self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height)
|
||||||
@ -110,8 +110,7 @@ class Sidebar(Widget):
|
|||||||
self._recording_audio = ui_state.recording_audio
|
self._recording_audio = ui_state.recording_audio
|
||||||
self._update_network_status(device_state)
|
self._update_network_status(device_state)
|
||||||
self._update_temperature_status(device_state)
|
self._update_temperature_status(device_state)
|
||||||
if not ui_state.dp_dev_disable_connect:
|
self._update_cpu_status(device_state)
|
||||||
self._update_connection_status(device_state)
|
|
||||||
self._update_panda_status()
|
self._update_panda_status()
|
||||||
|
|
||||||
def _update_network_status(self, device_state):
|
def _update_network_status(self, device_state):
|
||||||
@ -121,26 +120,28 @@ class Sidebar(Widget):
|
|||||||
|
|
||||||
def _update_temperature_status(self, device_state):
|
def _update_temperature_status(self, device_state):
|
||||||
thermal_status = device_state.thermalStatus
|
thermal_status = device_state.thermalStatus
|
||||||
|
max_temp = device_state.maxTempC
|
||||||
|
|
||||||
if thermal_status == ThermalStatus.green:
|
if thermal_status == ThermalStatus.green:
|
||||||
self._temp_status.update(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
|
self._temp_status.update(tr_noop("TEMP"), f"{max_temp:.1f}°C", Colors.GOOD)
|
||||||
elif thermal_status == ThermalStatus.yellow:
|
elif thermal_status == ThermalStatus.yellow:
|
||||||
self._temp_status.update(tr_noop("TEMP"), tr_noop("OK"), Colors.WARNING)
|
self._temp_status.update(tr_noop("TEMP"), f"{max_temp:.1f}°C", Colors.WARNING)
|
||||||
else:
|
else:
|
||||||
self._temp_status.update(tr_noop("TEMP"), tr_noop("HIGH"), Colors.DANGER)
|
self._temp_status.update(tr_noop("TEMP"), f"{max_temp:.1f}°C", Colors.DANGER)
|
||||||
|
|
||||||
def _update_connection_status(self, device_state):
|
def _update_cpu_status(self, device_state):
|
||||||
last_ping = device_state.lastAthenaPingTime
|
cpu_temp = max(device_state.cpuTempC, default=0.)
|
||||||
if last_ping == 0:
|
|
||||||
self._connect_status.update(tr_noop("CONNECT"), tr_noop("OFFLINE"), Colors.WARNING)
|
if cpu_temp >= 85:
|
||||||
elif time.monotonic_ns() - last_ping < 80_000_000_000: # 80 seconds in nanoseconds
|
self._cpu_status.update(tr_noop("CPU"), f"{cpu_temp:.1f}%", Colors.DANGER)
|
||||||
self._connect_status.update(tr_noop("CONNECT"), tr_noop("ONLINE"), Colors.GOOD)
|
elif cpu_temp >= 65:
|
||||||
|
self._cpu_status.update(tr_noop("CPU"), f"{cpu_temp:.1f}%", Colors.WARNING)
|
||||||
else:
|
else:
|
||||||
self._connect_status.update(tr_noop("CONNECT"), tr_noop("ERROR"), Colors.DANGER)
|
self._cpu_status.update(tr_noop("CPU"), f"{cpu_temp:.1f}%", Colors.GOOD)
|
||||||
|
|
||||||
def _update_panda_status(self):
|
def _update_panda_status(self):
|
||||||
if ui_state.panda_type == log.PandaState.PandaType.unknown:
|
if ui_state.panda_type == log.PandaState.PandaType.unknown:
|
||||||
self._panda_status.update(tr_noop("NO"), tr_noop("PANDA"), Colors.DANGER)
|
self._panda_status.update(tr_noop("PANDA"), tr_noop("NO"), Colors.DANGER)
|
||||||
else:
|
else:
|
||||||
self._panda_status.update(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD)
|
self._panda_status.update(tr_noop("VEHICLE"), tr_noop("ONLINE"), Colors.GOOD)
|
||||||
|
|
||||||
@ -201,9 +202,7 @@ class Sidebar(Widget):
|
|||||||
rl.draw_text_ex(self._font_regular, tr(self._net_type), text_pos, FONT_SIZE, 0, Colors.WHITE)
|
rl.draw_text_ex(self._font_regular, tr(self._net_type), text_pos, FONT_SIZE, 0, Colors.WHITE)
|
||||||
|
|
||||||
def _draw_metrics(self, rect: rl.Rectangle):
|
def _draw_metrics(self, rect: rl.Rectangle):
|
||||||
metrics = [(self._temp_status, 338), (self._panda_status, 496)]
|
metrics = [(self._temp_status, 338), (self._cpu_status, 496), (self._panda_status, 654)]
|
||||||
if not ui_state.dp_dev_disable_connect:
|
|
||||||
metrics.append((self._connect_status, 654))
|
|
||||||
|
|
||||||
for metric, y_offset in metrics:
|
for metric, y_offset in metrics:
|
||||||
self._draw_metric(rect, metric, rect.y + y_offset)
|
self._draw_metric(rect, metric, rect.y + y_offset)
|
||||||
|
|||||||
@ -136,7 +136,7 @@ class AugmentedRoadView(CameraView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _draw_border(self, rect: rl.Rectangle):
|
def _draw_border(self, rect: rl.Rectangle):
|
||||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, rl.BLACK)
|
||||||
border_roundness = 0.12
|
border_roundness = 0.12
|
||||||
border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
|
border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
|
||||||
if ui_state.dp_alka_active and ui_state.status == UIStatus.DISENGAGED:
|
if ui_state.dp_alka_active and ui_state.status == UIStatus.DISENGAGED:
|
||||||
@ -153,19 +153,6 @@ class AugmentedRoadView(CameraView):
|
|||||||
if self._dp_indicator_show_right:
|
if self._dp_indicator_show_right:
|
||||||
rl.draw_rectangle(int(rect.x + rect.width-UI_BORDER_SIZE), indicator_y, UI_BORDER_SIZE, indicator_height, self._dp_indicator_color_right)
|
rl.draw_rectangle(int(rect.x + rect.width-UI_BORDER_SIZE), indicator_y, UI_BORDER_SIZE, indicator_height, self._dp_indicator_color_right)
|
||||||
|
|
||||||
# black bg around colored border
|
|
||||||
black_bg_thickness = UI_BORDER_SIZE
|
|
||||||
black_bg_rect = rl.Rectangle(
|
|
||||||
border_rect.x - UI_BORDER_SIZE,
|
|
||||||
border_rect.y - UI_BORDER_SIZE,
|
|
||||||
border_rect.width + 2 * UI_BORDER_SIZE,
|
|
||||||
border_rect.height + 2 * UI_BORDER_SIZE,
|
|
||||||
)
|
|
||||||
edge_offset = (black_bg_rect.height - border_rect.height) / 2 # distance between rect edges
|
|
||||||
roundness_out = (border_roundness * border_rect.height + 2 * edge_offset) / max(1.0, black_bg_rect.height)
|
|
||||||
rl.draw_rectangle_rounded_lines_ex(black_bg_rect, roundness_out, 10, black_bg_thickness, rl.BLACK)
|
|
||||||
rl.end_scissor_mode()
|
|
||||||
|
|
||||||
def _switch_stream_if_needed(self, sm):
|
def _switch_stream_if_needed(self, sm):
|
||||||
if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
|
if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
|
||||||
v_ego = sm['carState'].vEgo
|
v_ego = sm['carState'].vEgo
|
||||||
|
|||||||
@ -114,6 +114,7 @@ class CameraView(Widget):
|
|||||||
# which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough
|
# which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough
|
||||||
# and only clears internal buffers, not the message queue.
|
# and only clears internal buffers, not the message queue.
|
||||||
self.frame = None
|
self.frame = None
|
||||||
|
self.available_streams.clear()
|
||||||
if self.client:
|
if self.client:
|
||||||
del self.client
|
del self.client
|
||||||
self.client = VisionIpcClient(self._name, self._stream_type, conflate=True)
|
self.client = VisionIpcClient(self._name, self._stream_type, conflate=True)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import xml.etree.ElementTree as ET
|
import re
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -12,7 +12,7 @@ import requests
|
|||||||
TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent
|
TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent
|
||||||
TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json"
|
TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json"
|
||||||
|
|
||||||
OPENAI_MODEL = "gpt-4"
|
OPENAI_MODEL = "deepseek-chat"
|
||||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
||||||
OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \
|
OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \
|
||||||
"The following sentence or word is in the GUI of a software called openpilot, translate it accordingly."
|
"The following sentence or word is in the GUI of a software called openpilot, translate it accordingly."
|
||||||
@ -25,8 +25,8 @@ def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
|
|||||||
language_dict = json.load(fp)
|
language_dict = json.load(fp)
|
||||||
|
|
||||||
for filename in language_dict.values():
|
for filename in language_dict.values():
|
||||||
path = TRANSLATIONS_DIR / f"{filename}.ts"
|
path = TRANSLATIONS_DIR / f"app_{filename}.po"
|
||||||
language = path.stem
|
language = filename
|
||||||
|
|
||||||
if languages is None or language in languages:
|
if languages is None or language in languages:
|
||||||
files[language] = path
|
files[language] = path
|
||||||
@ -36,7 +36,7 @@ def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
|
|||||||
|
|
||||||
def translate_phrase(text: str, language: str) -> str:
|
def translate_phrase(text: str, language: str) -> str:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
"https://api.openai.com/v1/chat/completions",
|
"https://api.deepseek.com/chat/completions",
|
||||||
json={
|
json={
|
||||||
"model": OPENAI_MODEL,
|
"model": OPENAI_MODEL,
|
||||||
"messages": [
|
"messages": [
|
||||||
@ -68,39 +68,343 @@ def translate_phrase(text: str, language: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def translate_file(path: pathlib.Path, language: str, all_: bool) -> None:
|
def translate_file(path: pathlib.Path, language: str, all_: bool) -> None:
|
||||||
tree = ET.parse(path)
|
# Read the PO file
|
||||||
|
with path.open("r", encoding="utf-8") as fp:
|
||||||
|
lines = fp.readlines()
|
||||||
|
|
||||||
root = tree.getroot()
|
# Process each line to find translation entries
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
|
||||||
for context in root.findall("./context"):
|
# Look for msgid line
|
||||||
name = context.find("name")
|
if line.startswith('msgid'):
|
||||||
if name is None:
|
# Check for empty msgid (header) - this is the start of multi-line msgid
|
||||||
raise ValueError("name not found")
|
if line == 'msgid ""':
|
||||||
|
# This is a multi-line msgid entry
|
||||||
|
msgid_text = ""
|
||||||
|
j = i + 1 # Start from the next line
|
||||||
|
|
||||||
print(f"Context: {name.text}")
|
# Collect all the quoted lines that form the msgid
|
||||||
|
while j < len(lines) and lines[j].strip().startswith('"'):
|
||||||
|
msgid_text += lines[j].strip().strip('"')
|
||||||
|
j += 1
|
||||||
|
|
||||||
for message in context.findall("./message"):
|
# Skip header entry (empty msgid)
|
||||||
source = message.find("source")
|
if not msgid_text:
|
||||||
translation = message.find("translation")
|
i = j
|
||||||
|
continue
|
||||||
|
|
||||||
if source is None or translation is None:
|
# Look for the corresponding msgstr or msgid_plural
|
||||||
raise ValueError("source or translation not found")
|
k = j
|
||||||
|
has_plural = False
|
||||||
|
while k < len(lines) and not lines[k].strip().startswith('msgstr'):
|
||||||
|
if lines[k].strip().startswith('msgid_plural'):
|
||||||
|
has_plural = True
|
||||||
|
k += 1
|
||||||
|
|
||||||
if not all_ and translation.attrib.get("type") != "unfinished":
|
if k < len(lines):
|
||||||
|
# Handle plural forms
|
||||||
|
if has_plural:
|
||||||
|
# This is a plural entry, need to handle msgstr[0], msgstr[1], etc.
|
||||||
|
msgstr_texts = []
|
||||||
|
m = k
|
||||||
|
|
||||||
|
# Find all msgstr[n] entries
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('msgstr['):
|
||||||
|
msgstr_match = re.match(r'msgstr\[(\d+)\]\s*"(.*)"', lines[m].strip())
|
||||||
|
if msgstr_match:
|
||||||
|
msgstr_texts.append(msgstr_match.group(2))
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
# Check if we should translate this entry
|
||||||
|
should_translate = False
|
||||||
|
if all_:
|
||||||
|
should_translate = True
|
||||||
|
else:
|
||||||
|
# Only translate if all msgstr entries are empty or contain only whitespace
|
||||||
|
should_translate = all(not text.strip() for text in msgstr_texts)
|
||||||
|
|
||||||
|
if should_translate:
|
||||||
|
# Translate both singular and plural forms
|
||||||
|
singular_text = msgid_text
|
||||||
|
# Find the plural form
|
||||||
|
plural_text = ""
|
||||||
|
p = j
|
||||||
|
while p < k and not lines[p].strip().startswith('msgid_plural'):
|
||||||
|
p += 1
|
||||||
|
if p < k:
|
||||||
|
# Extract plural text
|
||||||
|
plural_match = re.match(r'msgid_plural\s*"(.*)"', lines[p].strip())
|
||||||
|
if plural_match:
|
||||||
|
plural_text = plural_match.group(1)
|
||||||
|
|
||||||
|
# Translate both forms
|
||||||
|
singular_translation = translate_phrase(singular_text, language)
|
||||||
|
plural_translation = translate_phrase(plural_text, language)
|
||||||
|
|
||||||
|
print(f"Translating plural entry:")
|
||||||
|
print(f"Singular: {singular_text}")
|
||||||
|
print(f"Plural: {plural_text}")
|
||||||
|
print(f"Singular translation: {singular_translation}")
|
||||||
|
print(f"Plural translation: {plural_translation}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Update msgstr[0] and msgstr[1]
|
||||||
|
m = k
|
||||||
|
idx = 0
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('msgstr['):
|
||||||
|
if idx == 0:
|
||||||
|
lines[m] = f'msgstr[0] "{singular_translation}"\n'
|
||||||
|
elif idx == 1:
|
||||||
|
lines[m] = f'msgstr[1] "{plural_translation}"\n'
|
||||||
|
idx += 1
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
i = m
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
i = k + len(msgstr_texts)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Extract msgstr text (handle multi-line msgstr)
|
||||||
|
msgstr_text = ""
|
||||||
|
m = k
|
||||||
|
|
||||||
|
# Check if this is a multi-line msgstr
|
||||||
|
if lines[m].strip() == 'msgstr ""':
|
||||||
|
# Multi-line msgstr - collect all quoted lines
|
||||||
|
m += 1
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('"'):
|
||||||
|
msgstr_text += lines[m].strip().strip('"')
|
||||||
|
m += 1
|
||||||
|
else:
|
||||||
|
# Single-line msgstr
|
||||||
|
msgstr_match = re.match(r'msgstr\s+"(.+)"', lines[m].strip())
|
||||||
|
if msgstr_match:
|
||||||
|
msgstr_text = msgstr_match.group(1)
|
||||||
|
|
||||||
|
# Check if we should translate this entry
|
||||||
|
should_translate = False
|
||||||
|
if all_:
|
||||||
|
should_translate = True
|
||||||
|
else:
|
||||||
|
# Only translate if msgstr is empty or contains only whitespace
|
||||||
|
if not msgstr_text.strip():
|
||||||
|
should_translate = True
|
||||||
|
|
||||||
|
if should_translate:
|
||||||
|
# Translate the phrase
|
||||||
|
llm_translation = translate_phrase(msgid_text, language)
|
||||||
|
|
||||||
|
print(f"Translating entry:")
|
||||||
|
print(f"Source: {msgid_text}")
|
||||||
|
print(f"LLM translation: {llm_translation}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Update the msgstr line
|
||||||
|
if lines[k].strip() == 'msgstr ""':
|
||||||
|
# Multi-line msgstr - replace with multi-line format
|
||||||
|
# Remove existing msgstr lines
|
||||||
|
m = k + 1
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('"'):
|
||||||
|
lines[m] = ""
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
# Add new multi-line msgstr
|
||||||
|
lines[k] = 'msgstr ""\n'
|
||||||
|
# Split translation into lines of reasonable length
|
||||||
|
translation_lines = []
|
||||||
|
current_line = ""
|
||||||
|
for word in llm_translation.split():
|
||||||
|
if len(current_line + word) > 60: # Reasonable line length
|
||||||
|
translation_lines.append(f'"{current_line}"\n')
|
||||||
|
current_line = word
|
||||||
|
else:
|
||||||
|
if current_line:
|
||||||
|
current_line += " " + word
|
||||||
|
else:
|
||||||
|
current_line = word
|
||||||
|
if current_line:
|
||||||
|
translation_lines.append(f'"{current_line}"\n')
|
||||||
|
|
||||||
|
# Insert the translation lines
|
||||||
|
for idx, trans_line in enumerate(translation_lines):
|
||||||
|
lines.insert(k + 1 + idx, trans_line)
|
||||||
|
else:
|
||||||
|
# Single-line msgstr - replace it
|
||||||
|
lines[k] = f'msgstr "{llm_translation}"\n'
|
||||||
|
|
||||||
|
i = k + 1
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
i = k + 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
i = j
|
||||||
continue
|
continue
|
||||||
|
|
||||||
llm_translation = translate_phrase(cast(str, source.text), language)
|
# Single-line msgid
|
||||||
|
msgid_match = re.match(r'msgid\s+"(.+)"', line)
|
||||||
|
if msgid_match:
|
||||||
|
msgid_text = msgid_match.group(1)
|
||||||
|
|
||||||
print(f"Source: {source.text}\n" +
|
# Skip header entry (empty msgid)
|
||||||
f"Current translation: {translation.text}\n" +
|
if not msgid_text:
|
||||||
f"LLM translation: {llm_translation}")
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
translation.text = llm_translation
|
# Look for the corresponding msgstr or msgid_plural
|
||||||
|
j = i + 1
|
||||||
|
has_plural = False
|
||||||
|
while j < len(lines) and not lines[j].strip().startswith('msgstr'):
|
||||||
|
if lines[j].strip().startswith('msgid_plural'):
|
||||||
|
has_plural = True
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
if j < len(lines):
|
||||||
|
# Handle plural forms
|
||||||
|
if has_plural:
|
||||||
|
# This is a plural entry, need to handle msgstr[0], msgstr[1], etc.
|
||||||
|
msgstr_texts = []
|
||||||
|
m = j
|
||||||
|
|
||||||
|
# Find all msgstr[n] entries
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('msgstr['):
|
||||||
|
msgstr_match = re.match(r'msgstr\[(\d+)\]\s*"(.*)"', lines[m].strip())
|
||||||
|
if msgstr_match:
|
||||||
|
msgstr_texts.append(msgstr_match.group(2))
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
# Check if we should translate this entry
|
||||||
|
should_translate = False
|
||||||
|
if all_:
|
||||||
|
should_translate = True
|
||||||
|
else:
|
||||||
|
# Only translate if all msgstr entries are empty or contain only whitespace
|
||||||
|
should_translate = all(not text.strip() for text in msgstr_texts)
|
||||||
|
|
||||||
|
if should_translate:
|
||||||
|
# Translate both singular and plural forms
|
||||||
|
singular_text = msgid_text
|
||||||
|
# Find plural form
|
||||||
|
plural_text = ""
|
||||||
|
p = i + 1
|
||||||
|
while p < j and not lines[p].strip().startswith('msgid_plural'):
|
||||||
|
p += 1
|
||||||
|
if p < j:
|
||||||
|
# Extract plural text
|
||||||
|
plural_match = re.match(r'msgid_plural\s*"(.*)"', lines[p].strip())
|
||||||
|
if plural_match:
|
||||||
|
plural_text = plural_match.group(1)
|
||||||
|
|
||||||
|
# Translate both forms
|
||||||
|
singular_translation = translate_phrase(singular_text, language)
|
||||||
|
plural_translation = translate_phrase(plural_text, language)
|
||||||
|
|
||||||
|
print(f"Translating plural entry:")
|
||||||
|
print(f"Singular: {singular_text}")
|
||||||
|
print(f"Plural: {plural_text}")
|
||||||
|
print(f"Singular translation: {singular_translation}")
|
||||||
|
print(f"Plural translation: {plural_translation}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Update msgstr[0] and msgstr[1]
|
||||||
|
m = j
|
||||||
|
idx = 0
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('msgstr['):
|
||||||
|
if idx == 0:
|
||||||
|
lines[m] = f'msgstr[0] "{singular_translation}"\n'
|
||||||
|
elif idx == 1:
|
||||||
|
lines[m] = f'msgstr[1] "{plural_translation}"\n'
|
||||||
|
idx += 1
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
i = m
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
i = j + len(msgstr_texts)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Extract msgstr text (handle multi-line msgstr)
|
||||||
|
msgstr_text = ""
|
||||||
|
m = j
|
||||||
|
|
||||||
|
# Check if this is a multi-line msgstr
|
||||||
|
if lines[m].strip() == 'msgstr ""':
|
||||||
|
# Multi-line msgstr - collect all quoted lines
|
||||||
|
m += 1
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('"'):
|
||||||
|
msgstr_text += lines[m].strip().strip('"')
|
||||||
|
m += 1
|
||||||
|
else:
|
||||||
|
# Single-line msgstr
|
||||||
|
msgstr_match = re.match(r'msgstr\s+"(.+)"', lines[m].strip())
|
||||||
|
if msgstr_match:
|
||||||
|
msgstr_text = msgstr_match.group(1)
|
||||||
|
|
||||||
|
# Check if we should translate this entry
|
||||||
|
should_translate = False
|
||||||
|
if all_:
|
||||||
|
should_translate = True
|
||||||
|
else:
|
||||||
|
# Only translate if msgstr is empty or contains only whitespace
|
||||||
|
if not msgstr_text.strip():
|
||||||
|
should_translate = True
|
||||||
|
|
||||||
|
if should_translate:
|
||||||
|
# Translate the phrase
|
||||||
|
llm_translation = translate_phrase(msgid_text, language)
|
||||||
|
|
||||||
|
print(f"Translating entry:")
|
||||||
|
print(f"Source: {msgid_text}")
|
||||||
|
print(f"LLM translation: {llm_translation}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Update the msgstr line
|
||||||
|
if lines[j].strip() == 'msgstr ""':
|
||||||
|
# Multi-line msgstr - replace with multi-line format
|
||||||
|
# Remove existing msgstr lines
|
||||||
|
m = j + 1
|
||||||
|
while m < len(lines) and lines[m].strip().startswith('"'):
|
||||||
|
lines[m] = ""
|
||||||
|
m += 1
|
||||||
|
|
||||||
|
# Add new multi-line msgstr
|
||||||
|
lines[j] = 'msgstr ""\n'
|
||||||
|
# Split translation into lines of reasonable length
|
||||||
|
translation_lines = []
|
||||||
|
current_line = ""
|
||||||
|
for word in llm_translation.split():
|
||||||
|
if len(current_line + word) > 60: # Reasonable line length
|
||||||
|
translation_lines.append(f'"{current_line}"\n')
|
||||||
|
current_line = word
|
||||||
|
else:
|
||||||
|
if current_line:
|
||||||
|
current_line += " " + word
|
||||||
|
else:
|
||||||
|
current_line = word
|
||||||
|
if current_line:
|
||||||
|
translation_lines.append(f'"{current_line}"\n')
|
||||||
|
|
||||||
|
# Insert the translation lines
|
||||||
|
for idx, trans_line in enumerate(translation_lines):
|
||||||
|
lines.insert(j + 1 + idx, trans_line)
|
||||||
|
else:
|
||||||
|
# Single-line msgstr - replace it with single-line format
|
||||||
|
lines[j] = f'msgstr "{llm_translation}"\n'
|
||||||
|
|
||||||
|
i = j + 1
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
i = j + 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Write the updated PO file back with original formatting preserved
|
||||||
with path.open("w", encoding="utf-8") as fp:
|
with path.open("w", encoding="utf-8") as fp:
|
||||||
fp.write('<?xml version="1.0" encoding="utf-8"?>\n' +
|
fp.writelines(lines)
|
||||||
'<!DOCTYPE TS>\n' +
|
|
||||||
ET.tostring(root, encoding="utf-8").decode())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@ -27,8 +27,8 @@ class SetupWidget(Widget):
|
|||||||
def _render(self, rect: rl.Rectangle):
|
def _render(self, rect: rl.Rectangle):
|
||||||
if not ui_state.prime_state.is_paired():
|
if not ui_state.prime_state.is_paired():
|
||||||
self._render_registration(rect)
|
self._render_registration(rect)
|
||||||
else:
|
# else:
|
||||||
self._render_firehose_prompt(rect)
|
# self._render_firehose_prompt(rect)
|
||||||
|
|
||||||
def _render_registration(self, rect: rl.Rectangle):
|
def _render_registration(self, rect: rl.Rectangle):
|
||||||
"""Render registration prompt."""
|
"""Render registration prompt."""
|
||||||
|
|||||||
@ -52,11 +52,14 @@ class FontWeight(StrEnum):
|
|||||||
EXTRA_BOLD = "Inter-ExtraBold.ttf"
|
EXTRA_BOLD = "Inter-ExtraBold.ttf"
|
||||||
BLACK = "Inter-Black.ttf"
|
BLACK = "Inter-Black.ttf"
|
||||||
UNIFONT = "unifont.otf"
|
UNIFONT = "unifont.otf"
|
||||||
|
CHINA = "china.ttf"
|
||||||
|
|
||||||
|
|
||||||
def font_fallback(font: rl.Font) -> rl.Font:
|
def font_fallback(font: rl.Font) -> rl.Font:
|
||||||
"""Fall back to unifont for languages that require it."""
|
"""Fall back to unifont for languages that require it."""
|
||||||
if multilang.requires_unifont():
|
if multilang.requires_china():
|
||||||
|
return gui_app.font(FontWeight.CHINA)
|
||||||
|
elif multilang.requires_unifont():
|
||||||
return gui_app.font(FontWeight.UNIFONT)
|
return gui_app.font(FontWeight.UNIFONT)
|
||||||
return font
|
return font
|
||||||
|
|
||||||
@ -140,7 +143,12 @@ class GuiApplication:
|
|||||||
self._fonts: dict[FontWeight, rl.Font] = {}
|
self._fonts: dict[FontWeight, rl.Font] = {}
|
||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
self._scale = SCALE
|
|
||||||
|
if PC and os.getenv("SCALE") is None:
|
||||||
|
self._scale = self._calculate_auto_scale()
|
||||||
|
else:
|
||||||
|
self._scale = SCALE
|
||||||
|
|
||||||
self._scaled_width = int(self._width * self._scale)
|
self._scaled_width = int(self._width * self._scale)
|
||||||
self._scaled_height = int(self._height * self._scale)
|
self._scaled_height = int(self._height * self._scale)
|
||||||
self._render_texture: rl.RenderTexture | None = None
|
self._render_texture: rl.RenderTexture | None = None
|
||||||
@ -460,5 +468,17 @@ class GuiApplication:
|
|||||||
cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.")
|
cloudlog.error(f"FPS dropped critically below {fps}. Shutting down UI.")
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
def _calculate_auto_scale(self) -> float:
|
||||||
|
# Create temporary window to query monitor info
|
||||||
|
rl.init_window(1, 1, "")
|
||||||
|
w, h = rl.get_monitor_width(0), rl.get_monitor_height(0)
|
||||||
|
rl.close_window()
|
||||||
|
|
||||||
|
if w == 0 or h == 0 or (w >= self._width and h >= self._height):
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
# Apply 0.95 factor for window decorations/taskbar margin
|
||||||
|
return max(0.3, min(w / self._width, h / self._height) * 0.95)
|
||||||
|
|
||||||
|
|
||||||
gui_app = GuiApplication(2160, 1080)
|
gui_app = GuiApplication(2160, 1080)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import pyray as rl
|
|||||||
|
|
||||||
from openpilot.system.ui.lib.application import FONT_DIR
|
from openpilot.system.ui.lib.application import FONT_DIR
|
||||||
|
|
||||||
|
_emoji_font: ImageFont.FreeTypeFont | None = None
|
||||||
_cache: dict[str, rl.Texture] = {}
|
_cache: dict[str, rl.Texture] = {}
|
||||||
|
|
||||||
EMOJI_REGEX = re.compile(
|
EMOJI_REGEX = re.compile(
|
||||||
@ -32,6 +33,13 @@ EMOJI_REGEX = re.compile(
|
|||||||
flags=re.UNICODE
|
flags=re.UNICODE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _load_emoji_font() -> ImageFont.FreeTypeFont | None:
|
||||||
|
global _emoji_font
|
||||||
|
if _emoji_font is None:
|
||||||
|
_emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109)
|
||||||
|
return _emoji_font
|
||||||
|
|
||||||
|
|
||||||
def find_emoji(text):
|
def find_emoji(text):
|
||||||
return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)]
|
return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)]
|
||||||
|
|
||||||
@ -39,8 +47,7 @@ def emoji_tex(emoji):
|
|||||||
if emoji not in _cache:
|
if emoji not in _cache:
|
||||||
img = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
|
img = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
font = ImageFont.truetype(FONT_DIR.joinpath("NotoColorEmoji.ttf"), 109)
|
draw.text((0, 0), emoji, font=_load_emoji_font(), embedded_color=True)
|
||||||
draw.text((0, 0), emoji, font=font, embedded_color=True)
|
|
||||||
with io.BytesIO() as buffer:
|
with io.BytesIO() as buffer:
|
||||||
img.save(buffer, format="PNG")
|
img.save(buffer, format="PNG")
|
||||||
l = buffer.tell()
|
l = buffer.tell()
|
||||||
|
|||||||
@ -17,6 +17,9 @@ LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json")
|
|||||||
UNIFONT_LANGUAGES = [
|
UNIFONT_LANGUAGES = [
|
||||||
"ar",
|
"ar",
|
||||||
"th",
|
"th",
|
||||||
|
]
|
||||||
|
|
||||||
|
CHINA_LANGUAGES = [
|
||||||
"zh-CHT",
|
"zh-CHT",
|
||||||
"zh-CHS",
|
"zh-CHS",
|
||||||
"ko",
|
"ko",
|
||||||
@ -37,6 +40,10 @@ class Multilang:
|
|||||||
def language(self) -> str:
|
def language(self) -> str:
|
||||||
return self._language
|
return self._language
|
||||||
|
|
||||||
|
def requires_china(self) -> bool:
|
||||||
|
"""Certain languages require china to render their glyphs."""
|
||||||
|
return self._language in CHINA_LANGUAGES
|
||||||
|
|
||||||
def requires_unifont(self) -> bool:
|
def requires_unifont(self) -> bool:
|
||||||
"""Certain languages require unifont to render their glyphs."""
|
"""Certain languages require unifont to render their glyphs."""
|
||||||
return self._language in UNIFONT_LANGUAGES
|
return self._language in UNIFONT_LANGUAGES
|
||||||
|
|||||||
@ -26,7 +26,7 @@ def clamp(value, min_value, max_value):
|
|||||||
class Spinner(Widget):
|
class Spinner(Widget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._comma_texture = gui_app.texture("../../dragonpilot/selfdrive/assets/images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
|
self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
|
||||||
self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
|
self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
|
||||||
self._rotation = 0.0
|
self._rotation = 0.0
|
||||||
self._progress: int | None = None
|
self._progress: int | None = None
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class HtmlElement:
|
|||||||
font_weight: FontWeight
|
font_weight: FontWeight
|
||||||
margin_top: int
|
margin_top: int
|
||||||
margin_bottom: int
|
margin_bottom: int
|
||||||
line_height: float = 0.9 # matches Qt visually, unsure why not default 1.2
|
line_height: float = 1.3 # matches Qt visually, unsure why not default 1.2
|
||||||
indent_level: int = 0
|
indent_level: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -137,7 +137,7 @@ class Keyboard(Widget):
|
|||||||
|
|
||||||
def _render(self, rect: rl.Rectangle):
|
def _render(self, rect: rl.Rectangle):
|
||||||
rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN)
|
rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN)
|
||||||
self._title.render(rl.Rectangle(rect.x, rect.y, rect.width, 95))
|
self._title.render(rl.Rectangle(rect.x, rect.y - 20, rect.width, 95))
|
||||||
self._sub_title.render(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60))
|
self._sub_title.render(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60))
|
||||||
self._cancel_button.render(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125))
|
self._cancel_button.render(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125))
|
||||||
|
|
||||||
|
|||||||
@ -336,6 +336,8 @@ class ListItem(Widget):
|
|||||||
# do callback first in case receiver changes description
|
# do callback first in case receiver changes description
|
||||||
if self.description_visible and self.description_opened_callback is not None:
|
if self.description_visible and self.description_opened_callback is not None:
|
||||||
self.description_opened_callback()
|
self.description_opened_callback()
|
||||||
|
# Call _update_state to catch any description changes
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
content_width = int(self._rect.width - ITEM_PADDING * 2)
|
content_width = int(self._rect.width - ITEM_PADDING * 2)
|
||||||
self._rect.height = self.get_item_height(self._font, content_width)
|
self._rect.height = self.get_item_height(self._font, content_width)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user