diff --git a/1.sh b/1.sh new file mode 100644 index 000000000..cc7881d44 --- /dev/null +++ b/1.sh @@ -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 \ No newline at end of file diff --git a/_check_out.txt b/_check_out.txt new file mode 100644 index 000000000..74853fb7e --- /dev/null +++ b/_check_out.txt @@ -0,0 +1 @@ +start \ No newline at end of file diff --git a/_d.txt b/_d.txt new file mode 100644 index 000000000..c4ca451a9 --- /dev/null +++ b/_d.txt @@ -0,0 +1 @@ +b'[\r\n {' \ No newline at end of file diff --git a/launch_openpilot.sh b/launch_openpilot.sh index d6e3424c3..f1aed8d48 100755 --- a/launch_openpilot.sh +++ b/launch_openpilot.sh @@ -1,3 +1,13 @@ #!/usr/bin/env bash +export ATHENA_HOST='ws://athena.mr-one.cn' +export API_HOST='http://res.mr-one.cn' +# Skip onboarding on startup +echo -n "2" > /data/params/d/HasAcceptedTerms +echo -n "1.0" > /data/params/d/HasAcceptedTermsSP +echo -n "0.2.0" > /data/params/d/CompletedTrainingVersion +echo -n "1.0" > /data/params/d/CompletedSunnylinkConsentVersion # Sunnylink 同意 +echo -n "1" > /data/params/d/IsMetric + + exec ./launch_chffrplus.sh diff --git a/openpilot/common/hardware/comma/agnos.json b/openpilot/common/hardware/comma/agnos.json index ae1cbbcd3..20955a767 100644 --- a/openpilot/common/hardware/comma/agnos.json +++ b/openpilot/common/hardware/comma/agnos.json @@ -1,84 +1,84 @@ [ { - "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb.img.xz", + "full_check": true, + "has_ab": true, "hash": "e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb", "hash_raw": "e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb", + "name": "xbl", + "ondevice_hash": "bea7f1a24428c3ededf672fa4fc78baf180cfbd8aafb77c974655b38517283e3", "size": 3282256, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "bea7f1a24428c3ededf672fa4fc78baf180cfbd8aafb77c974655b38517283e3" + "url": "https://commadist.azureedge.net/agnosupdate/xbl-e8acf2a9cc7f0ce84cb803bfea9477f765c0d7b4daf26048e59651b9e6a7bfbb.img.xz" }, { - "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85.img.xz", + "full_check": true, + "has_ab": true, "hash": "758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85", "hash_raw": "758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85", + "name": "xbl_config", + "ondevice_hash": "fb18cde08a98a168961ecd357e92474823046752b94e112f59fe51a6acd7197d", "size": 98124, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "fb18cde08a98a168961ecd357e92474823046752b94e112f59fe51a6acd7197d" + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-758552ecf92b5569677197783bf0ccb73d7f961685308e45d3276ac9dd974f85.img.xz" }, { + "full_check": true, + "has_ab": true, + "hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6", + "hash_raw": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6", "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35.img.xz", - "hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35", - "hash_raw": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35", + "ondevice_hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6", "size": 274432, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "29fd7ed1c012e599420764840f9f11286d34dbff4adaf102a447f06d8c5e0b35" + "url": "https://commadist.azureedge.net/agnosupdate/abl-32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6.img.xz" }, { - "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1.img.xz", + "full_check": true, + "has_ab": true, "hash": "78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1", "hash_raw": "78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1", + "name": "aop", + "ondevice_hash": "6c9135446bd3fc075fcee59b887a12e49029ab1f98ed8d6d1e32c73569d47de3", "size": 184364, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "6c9135446bd3fc075fcee59b887a12e49029ab1f98ed8d6d1e32c73569d47de3" + "url": "https://commadist.azureedge.net/agnosupdate/aop-78b2287ca219a0811b3004c523fa0f4749e4d1fd92be3aba61699305b7943ad1.img.xz" }, { - "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc.img.xz", + "full_check": true, + "has_ab": true, "hash": "f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc", "hash_raw": "f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc", + "name": "devcfg", + "ondevice_hash": "2a67971602012c1b43544964709da13c322786b456a8e78568b117e8b1540ce3", "size": 40336, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "2a67971602012c1b43544964709da13c322786b456a8e78568b117e8b1540ce3" + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-f71df3a86958c093ba3969254c4db025187eef9385427f1ade946742939b43cc.img.xz" }, { + "full_check": true, + "has_ab": true, + "hash": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4", + "hash_raw": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4", "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd.img.xz", - "hash": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd", - "hash_raw": "b30f5eef65ec3878f3aa3dcaf2cc95c09e2c1e661cd3a38e94da37dee76f68bd", - "size": 46897152, + "ondevice_hash": "492ae27f569e8db457c79d0e358a7a6297d1a1c685c2b1ae6deba7315d3a6cb0", + "size": 18515968, "sparse": false, - "full_check": true, - "has_ab": true, - "ondevice_hash": "6650e4c46df99ae6dfd6ee895a34b8a2a3cc490a8ce18e16cc3c451c3f822b6e" + "url": "https://commadist.azureedge.net/agnosupdate/boot-0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4.img.xz" }, { - "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img.xz", - "hash": "b134fd04e9da27fa1d359ea0f2742c216fa21a08b5c47e9be22ab3b0563d9b9b", - "hash_raw": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3", - "size": 4718592000, - "sparse": true, - "full_check": false, - "has_ab": true, - "ondevice_hash": "91242772af771ae96fe2eebc105f2b80a7e1dbaaf6003c2574b62d51b806f468", "alt": { "hash": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3", - "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img", - "size": 4718592000 - } + "size": 4718592000, + "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img" + }, + "full_check": false, + "has_ab": true, + "hash": "b134fd04e9da27fa1d359ea0f2742c216fa21a08b5c47e9be22ab3b0563d9b9b", + "hash_raw": "5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3", + "name": "system", + "ondevice_hash": "91242772af771ae96fe2eebc105f2b80a7e1dbaaf6003c2574b62d51b806f468", + "size": 4718592000, + "sparse": true, + "url": "https://commadist.azureedge.net/agnosupdate/system-5b6ce7965904a157fd3a134ccfcb854f9ca5c1cc2a26b7cb80a4fa4e1cc4aaa3.img.xz" } -] +] \ No newline at end of file diff --git a/openpilot/common/hardware/comma/amplifier.py b/openpilot/common/hardware/comma/amplifier.py index 09436e6ff..c918c6634 100755 --- a/openpilot/common/hardware/comma/amplifier.py +++ b/openpilot/common/hardware/comma/amplifier.py @@ -94,7 +94,7 @@ class Amplifier: def set_configs(self, configs: list[AmpConfig]) -> bool: # retry in case panda is using the amp - tries = 15 + tries = 1 backoff = 0. for i in range(tries): try: diff --git a/openpilot/common/hardware/comma/hardware.h b/openpilot/common/hardware/comma/hardware.h index 6292183d9..8189a32a9 100644 --- a/openpilot/common/hardware/comma/hardware.h +++ b/openpilot/common/hardware/comma/hardware.h @@ -22,6 +22,7 @@ public: static cereal::InitData::DeviceType get_device_type() { static const std::map device_map = { {"tizi", cereal::InitData::DeviceType::TIZI}, + {"tici", cereal::InitData::DeviceType::TIZI}, {"mici", cereal::InitData::DeviceType::MICI} }; static const auto it = device_map.find(get_name()); diff --git a/openpilot/selfdrive/c3_client.py b/openpilot/selfdrive/c3_client.py new file mode 100644 index 000000000..e68f24c0b --- /dev/null +++ b/openpilot/selfdrive/c3_client.py @@ -0,0 +1,783 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +C3 Client — 部署到 C3 设备上 +与 C3 Director 服务端通信 +支持远程指令执行、tmux 故障诊断、心跳保活 + +通过 openpilot 的 PythonProcess 自动启动: + PythonProcess("c3-client", "selfdrive.c3_client", always_run), + +零外部依赖(内嵌纯 Python 版 WebSocket 客户端,纯标准库实现) +""" + +import asyncio +import base64 +import json +import os +import random +import struct +import subprocess +import sys +import traceback +import urllib.parse +from datetime import datetime + +# openpilot 基础模块 +# 注意:新版 sunnypilot 顶层包根在 git 根下 openpilot/ 子目录,import 写法 openpilot.xxx +# (不要写成 openpilot.openpilot.xxx) +# system.hardware 版本易变,HARDWARE 在函数内 try-import 兜底;仅 Params 为必需项 +from openpilot.common.params import Params + + +# ================= 内嵌 WebSocket 客户端(纯标准库) ================= + +class _WebSocketError(Exception): + pass + + +class _WebSocketClient: + """异步 WebSocket 客户端(RFC 6455),仅依赖 Python 标准库""" + + def __init__(self, url, ping_interval=20, ping_timeout=15): + parsed = urllib.parse.urlparse(url) + if parsed.scheme not in ("ws",): + raise ValueError(f"不支持的协议: {parsed.scheme}") + self.host = parsed.hostname + self.port = parsed.port or 80 + self.path = parsed.path or "/" + if parsed.query: + self.path += "?" + parsed.query + + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self._reader = None + self._writer = None + self._closed = False + self._recv_queue = asyncio.Queue() + self._pong_event = asyncio.Event() + + async def connect(self): + """建立 TCP + WebSocket 握手""" + try: + self._reader, self._writer = await asyncio.wait_for( + asyncio.open_connection(self.host, self.port), timeout=10 + ) + + # 生成 Sec-WebSocket-Key + rand_bytes = bytes(random.randint(0, 255) for _ in range(16)) + ws_key = base64.b64encode(rand_bytes).decode() + + request = ( + f"GET {self.path} HTTP/1.1\r\n" + f"Host: {self.host}:{self.port}\r\n" + f"Upgrade: websocket\r\n" + f"Connection: Upgrade\r\n" + f"Sec-WebSocket-Key: {ws_key}\r\n" + f"Sec-WebSocket-Version: 13\r\n" + f"\r\n" + ) + self._writer.write(request.encode()) + await self._writer.drain() + + # 读取响应头 + response = b"" + while b"\r\n\r\n" not in response: + chunk = await asyncio.wait_for(self._reader.read(4096), timeout=10) + if not chunk: + raise _WebSocketError("连接关闭") + response += chunk + + header_text = response.decode("utf-8", errors="replace") + status_line = header_text.split("\r\n")[0] + if "101" not in status_line: + raise _WebSocketError(f"握手失败: {status_line}") + + self._recv_task = asyncio.create_task(self._recv_loop()) + self._ping_task = asyncio.create_task(self._ping_loop()) + except asyncio.TimeoutError: + raise _WebSocketError("连接或握手超时") + + async def _recv_loop(self): + """持续读取 WebSocket 帧""" + try: + while not self._closed: + frame = await self._read_frame() + if frame is None: + break + + opcode = frame[0] & 0x0F + payload = frame[1] + + if opcode in (0x0, 0x1): # 继续帧 / 文本帧 + await self._recv_queue.put(("text", payload.decode("utf-8", errors="replace"))) + elif opcode == 0x8: # 关闭帧 + await self._send_close() + self._closed = True + break + elif opcode == 0x9: # Ping → 自动 Pong + await self._send_frame(0xA, payload) + elif opcode == 0xA: # Pong + self._pong_event.set() + except (asyncio.CancelledError, ConnectionError): + pass + except Exception as e: + print(f"[C3] WebSocket 接收异常: {e}") + finally: + self._closed = True + + async def _read_frame(self): + """读取一个 WebSocket 帧""" + header = await self._read_exact(2) + if not header: + return None + + first_byte = header[0] + second_byte = header[1] + length = second_byte & 0x7F + + if length == 126: + ext = await self._read_exact(2) + length = struct.unpack("!H", ext)[0] + elif length == 127: + ext = await self._read_exact(8) + length = struct.unpack("!Q", ext)[0] + + mask_key = await self._read_exact(4) if (second_byte & 0x80) else None + payload = await self._read_exact(length) + + if mask_key: + payload = bytes(b ^ mask_key[i % 4] for i, b in enumerate(payload)) + + return (first_byte, payload) + + async def _read_exact(self, n): + """精确读取 n 字节""" + data = b"" + while len(data) < n: + chunk = await self._reader.read(n - len(data)) + if not chunk: + raise _WebSocketError("连接断开") + data += chunk + return data + + async def send(self, text): + """发送文本消息""" + payload = text.encode("utf-8") if isinstance(text, str) else text + await self._send_frame(0x1, payload) + + async def _send_frame(self, opcode, payload): + """发送 WebSocket 帧(客户端必须 mask)""" + header = bytearray() + header.append(0x80 | opcode) # FIN + opcode + + length = len(payload) + if length < 126: + header.append(0x80 | length) + elif length < 65536: + header.append(0x80 | 126) + header.extend(struct.pack("!H", length)) + else: + header.append(0x80 | 127) + header.extend(struct.pack("!Q", length)) + + mask_key = bytes(random.randint(0, 255) for _ in range(4)) + header.extend(mask_key) + header.extend(bytes(b ^ mask_key[i % 4] for i, b in enumerate(payload))) + + self._writer.write(bytes(header)) + await self._writer.drain() + + async def _send_close(self): + try: + self._writer.write(bytes([0x88, 0x00])) + await self._writer.drain() + except Exception: + pass + + async def _ping_loop(self): + try: + while not self._closed: + await asyncio.sleep(self.ping_interval) + self._pong_event.clear() + await self._send_frame(0x9, b"") + try: + await asyncio.wait_for(self._pong_event.wait(), timeout=self.ping_timeout) + except asyncio.TimeoutError: + print("[C3] Ping 超时") + break + except asyncio.CancelledError: + pass + except Exception as e: + print(f"[C3] Ping 循环异常: {e}") + finally: + self._closed = True + + async def recv(self): + """接收一条消息""" + while not self._closed: + msg_type, text = await self._recv_queue.get() + if msg_type == "text": + return text + raise ConnectionError("连接已关闭") + + def __aiter__(self): + return self._async_iterator() + + async def _async_iterator(self): + while not self._closed: + try: + yield await self.recv() + except ConnectionError: + break + + async def close(self): + self._closed = True + if self._writer: + await self._send_close() + try: + self._writer.close() + await self._writer.wait_closed() + except Exception: + pass + for attr in ("_recv_task", "_ping_task"): + if hasattr(self, attr): + getattr(self, attr).cancel() + + async def __aenter__(self): + await self.connect() + return self + + async def __aexit__(self, *args): + await self.close() + + +# ================= 配置 ================= +SERVER_URL = "ws://1.15.136.221:8500" +HEARTBEAT_INTERVAL = 5 +RECONNECT_DELAY = 1 + +# ================= 设备标识 ================= +_params = Params() + + +def get_serial(): + """获取设备序列号。优先走硬件抽象;失败/新版结构异常时 fallback 主机名并去掉 comma- 前缀。""" + try: + import openpilot.system.hardware as _hw + return _hw.HARDWARE.get_serial() + except Exception: + # fallback:nodename 为 "comma-XXXX",去掉前缀只留纯 serial,保证前后端一致 + return os.uname().nodename.replace("comma-", "", 1) + + +def get_dongle_id(): + try: + dongle = _params.get("DongleId") + return dongle if dongle else "" + except Exception: + return "" + +def get_device_type(): + try: + import openpilot.system.hardware as _hw + if hasattr(_hw.HARDWARE, 'get_device_type'): + dt = _hw.HARDWARE.get_device_type() + return dt if dt else "" + return "" + except Exception: + return "" + +def get_lan_ip(): + """获取设备局域网 IP 地址""" + try: + result = subprocess.run( + ["hostname", "-I"], + capture_output=True, text=True, timeout=3 + ) + ip = result.stdout.strip().split()[0] if result.returncode == 0 else "" + # 排除 127.0.0.1 + if ip and not ip.startswith("127."): + return ip + return "" + except Exception: + return "" + + +def get_git_branch(): + try: + result = subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + capture_output=True, text=True, timeout=5, + cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + ) + return result.stdout.strip() if result.returncode == 0 else "" + except Exception: + return "" + +def get_car_platform(): + try: + bundle = _params.get("CarPlatformBundle") + if bundle: + if isinstance(bundle, dict): + return bundle.get("name", "") + import json + return json.loads(bundle).get("name", "") + # 如果没有强制指定车型,从 CarParams 读取实际车型 + try: + cp = _params.get("CarParamsPersistent") + if cp: + import json as _j + cp_data = _j.loads(cp if isinstance(cp, str) else cp.decode()) + return cp_data.get("carName", "") or cp_data.get("car_platform", "") or "" + except Exception: + pass + return "" + except Exception: + return "" + + +# ================= 指令执行 ================= +def _kill_process_group(proc): + """终止整个进程组(含子进程),避免遗留孤儿进程。""" + import signal + if proc is None: + return + try: + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + except ProcessLookupError: + pass + except Exception: + try: + proc.kill() + except Exception: + pass + +async def execute_cmd(command, timeout=30): + proc = None + try: + proc = await asyncio.create_subprocess_shell( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + start_new_session=True + ) + stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout) + return { + "status": "ok", + "output": stdout.decode(errors="replace"), + "returncode": proc.returncode + } + except asyncio.TimeoutError: + _kill_process_group(proc) + return {"status": "error", "output": f"命令执行超时({timeout}s),进程已终止"} + except Exception as e: + _kill_process_group(proc) + return {"status": "error", "output": str(e)} + + +async def execute_tmux(): + tmux_check = await execute_cmd("ps aux | grep tmux | grep -v grep", timeout=3) + if not tmux_check.get("output", "").strip(): + return {"status": "ok", "output": "⚠️ 设备上未检测到 tmux 进程运行"} + + sessions_result = await execute_cmd("tmux list-sessions 2>&1", timeout=3) + if "error" in sessions_result.get("output", ""): + return {"status": "ok", "output": f"⚠️ 无法获取 tmux 会话\n{sessions_result['output']}"} + + result = await execute_cmd( + "tmux capture-pane -t $(tmux list-sessions -F '#{session_name}' 2>/dev/null | head -1) -p -S -200 2>/dev/null", + timeout=5 + ) + if result["status"] == "ok" and result.get("output", "").strip(): + session_info = await execute_cmd("tmux list-sessions 2>&1", timeout=3) + output = f"--- tmux sessions ---\n{session_info.get('output', '')}\n\n--- capture output ---\n{result['output']}" + return {"status": "ok", "output": output} + else: + return {"status": "ok", "output": f"⚠️ 无法捕获 tmux 输出\n{sessions_result.get('output', '')}"} + + +# ================= 错误查询 ================= +# ================= Messaging 数据采集 ================= +async def execute_messaging(): + """获取 messaging 实时数据:进程状态、设备状态、车辆状态、控制状态 + 使用 subprocess 独立进程执行同步 SubMaster 操作,避免在 async 协程中阻塞事件循环""" + try: + script = r'''import sys, json +sys.path.insert(0, "/data/openpilot") +from cereal.messaging import SubMaster, pub_sock, recv_one_or_none +from cereal import log +import time + +# 分别订阅每个 topic 并单独等待,避免一次 update 等不全 +topics = {"managerState": None, "deviceState": None, "carState": None, "controlsState": None} +for t in topics: + sm = SubMaster([t]) + for _ in range(5): # 最多尝试 5 轮,每轮 1 秒 + sm.update(1000) + if sm.updated[t]: + topics[t] = sm[t] + break + +ms = topics["managerState"] +ds = topics["deviceState"] +cs = topics["carState"] +cts = topics["controlsState"] + +# 辅助函数:从 capnp 对象安全取值 +def _get(obj, attr, default=0): + return getattr(obj, attr, default) if obj is not None else default + +def _get_str(obj, attr, default="--"): + return str(getattr(obj, attr, default)) if obj is not None else default + +def _round(obj, attr, precision=2): + return round(getattr(obj, attr, 0), precision) if obj is not None else 0 + +def _list(obj, attr): + return list(getattr(obj, attr, [])) if obj is not None else [] + +# 1. 进程状态 +key_names = ["selfdrived","modeld","modeld_v2","updated","ui","sensord","camerad", + "boardd","pandad","athenad","c3_client","mapd_nav"] +processes = [] +for p in _get(ms, "processes", []): + if p.name in key_names: + processes.append({"name": p.name, "running": p.running, "shouldBeRunning": p.shouldBeRunning, "pid": p.pid}) + +# 2. 设备状态 +device_state = { + "deviceType": _get_str(ds, "deviceType"), + "started": _get(ds, "started", False), + "thermalStatus": _get_str(ds, "thermalStatus"), + "networkType": _get_str(ds, "networkType"), + "networkStrength": _get_str(ds, "networkStrength"), + "cpuUsagePercent": _list(ds, "cpuUsagePercent"), + "gpuUsagePercent": _get(ds, "gpuUsagePercent"), + "memoryUsagePercent": _get(ds, "memoryUsagePercent"), + "freeSpacePercent": _round(ds, "freeSpacePercent", 1), + "powerDrawW": _round(ds, "powerDrawW", 1), + "fanSpeedPercentDesired": _get(ds, "fanSpeedPercentDesired"), + "screenBrightnessPercent": _get(ds, "screenBrightnessPercent"), + "carBatteryCapacityUwh": _get(ds, "carBatteryCapacityUwh"), + "cpuTempC": _list(ds, "cpuTempC"), + "gpuTempC": _list(ds, "gpuTempC"), + "memoryTempC": _round(ds, "memoryTempC", 1), + "maxTempC": _round(ds, "maxTempC", 1), + "dspTempC": _round(ds, "dspTempC", 1) if _get(ds, "dspTempC") else 0, +} + +# 3. 车辆状态 +cs_obj = _get(cs, "cruiseState") +ws_obj = _get(cs, "wheelSpeeds") +car_state = { + "vEgo": _round(cs, "vEgo"), + "vEgoRaw": _round(cs, "vEgoRaw"), + "vCruise": _round(cs, "vCruise"), + "vCruiseCluster": _round(cs, "vCruiseCluster"), + "steeringAngleDeg": _round(cs, "steeringAngleDeg", 1), + "steeringRateDeg": _round(cs, "steeringRateDeg", 1), + "steeringTorque": _get(cs, "steeringTorque"), + "steeringTorqueEps": _round(cs, "steeringTorqueEps", 1), + "steeringPressed": _get(cs, "steeringPressed", False), + "steerFaultPermanent": _get(cs, "steerFaultPermanent", False), + "steerFaultTemporary": _get(cs, "steerFaultTemporary", False), + "gasPressed": _get(cs, "gasPressed", False), + "brakePressed": _get(cs, "brakePressed", False), + "brakeHoldActive": _get(cs, "brakeHoldActive", False), + "standstill": _get(cs, "standstill", False), + "seatbeltUnlatched": _get(cs, "seatbeltUnlatched", False), + "doorOpen": _get(cs, "doorOpen", False), + "parkingBrake": _get(cs, "parkingBrake", False), + "gearShifter": _get_str(cs, "gearShifter"), + "leftBlinker": _get(cs, "leftBlinker", False), + "rightBlinker": _get(cs, "rightBlinker", False), + "leftBlindspot": _get(cs, "leftBlindspot", False), + "rightBlindspot": _get(cs, "rightBlindspot", False), + "canValid": _get(cs, "canValid", False), + "canTimeout": _get(cs, "canTimeout", False), + "accFaulted": _get(cs, "accFaulted", False), + "aEgo": _round(cs, "aEgo", 4), + "yawRate": _round(cs, "yawRate", 4), + "cruiseState": { + "enabled": _get(cs_obj, "enabled", False), + "available": _get(cs_obj, "available", False), + "speed": _round(cs_obj, "speed"), + "speedCluster": _round(cs_obj, "speedCluster"), + }, + "wheelSpeeds": { + "fl": _round(ws_obj, "fl"), + "fr": _round(ws_obj, "fr"), + "rl": _round(ws_obj, "rl"), + "rr": _round(ws_obj, "rr"), + }, +} + +# 4. 控制状态 +controls_state = { + "longControlState": _get_str(cts, "longControlState"), + "lateralControlState": _get_str(cts, "lateralControlState"), + "curvature": _round(cts, "curvature", 6), + "desiredCurvature": _round(cts, "desiredCurvature", 6), + "ufAccelCmd": _round(cts, "ufAccelCmd", 4), + "uiAccelCmd": _round(cts, "uiAccelCmd", 4), + "upAccelCmd": _round(cts, "upAccelCmd", 4), + "forceDecel": _get(cts, "forceDecel", False), +} + +print(json.dumps({ + "processes": processes, + "deviceState": device_state, + "carState": car_state, + "controlsState": controls_state, +})) +''' + result = await asyncio.wait_for( + asyncio.create_subprocess_exec( + "python3", "-c", script, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ), timeout=15 + ) + stdout, stderr = await result.communicate() + if result.returncode != 0: + return {"status": "error", "output": f"子进程错误: {stderr.decode(errors='replace')[:500]}"} + output = stdout.decode(errors="replace").strip() + # 验证是否为合法JSON + json.loads(output) + return {"status": "ok", "output": output} + except asyncio.TimeoutError: + return {"status": "error", "output": "获取 messaging 数据超时(15s)"} + except json.JSONDecodeError: + return {"status": "error", "output": f"数据解析失败: {output[:300]}"} + except Exception as e: + return {"status": "error", "output": f"获取 messaging 数据失败: {e}\n{traceback.format_exc()}"} + +# ================= 更新辅助 ================= +async def execute_update_oneclick(): + """一键更新:直接发送 SIGHUP 信号触发 updated 进程执行完整检查+下载 + 只发 SIGHUP 即可(内部先 check_for_update 再 fetch_update) + 避免先发 SIGUSR1 再发 SIGHUP 导致的时序竞争(user_request 在 sleep 前被清空)""" + try: + # 检查 updated 进程是否在运行 + check = await execute_cmd("pgrep -f 'system.updated.updated'", timeout=3) + if not check.get("output", "").strip(): + return {"status": "error", "output": "设备不在 offroad 状态,updated 进程未运行"} + + # 只发 SIGHUP(内部先检查再下载,一步到位) + subprocess.run( + ["sudo", "-u", "comma", "pkill", "-SIGHUP", "-f", "system.updated.updated"], + timeout=5, capture_output=True + ) + + return {"status": "ok", "output": "一键更新已触发"} + except Exception as e: + return {"status": "error", "output": f"一键更新失败: {e}"} + + +# ================= 截图 ================= +def _raw_rgb888_to_png(w, h, data_rgb): + """将 RGB888 原始像素数据编码为 PNG(纯标准库,无需 PIL/numpy)""" + import struct, zlib + raw = b"" + for y in range(h): + raw += b"\x00" # filter byte + row_start = y * w * 3 + raw += data_rgb[row_start:row_start + w * 3] + + def chunk(ctype, cdata): + c = ctype + cdata + return struct.pack(">I", len(cdata)) + c + struct.pack(">I", zlib.crc32(c) & 0xffffffff) + + sig = b"\x89PNG\r\n\x1a\n" + ihdr = struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0) # 8bit RGB + idat = zlib.compress(raw) + iend = b"" + + return sig + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat) + chunk(b"IEND", iend) + + +async def execute_snapshot(): + """截取设备屏幕画面,返回 base64 编码的 PNG""" + import base64, subprocess, os + + # 方法1: 检查有没有已存在的 screenshot 文件 + exists = os.path.exists("/tmp/screenshot.png") or os.path.exists("/data/screenshot.png") + if exists: + path = "/tmp/screenshot.png" if os.path.exists("/tmp/screenshot.png") else "/data/screenshot.png" + with open(path, "rb") as f: + b64 = base64.b64encode(f.read()).decode() + return {"status": "ok", "output": b64} + + # 方法2: 执行 screencap + r = subprocess.run(["screencap", "-p"], capture_output=True, timeout=5) + if r.returncode == 0 and len(r.stdout) > 100: + return {"status": "ok", "output": base64.b64encode(r.stdout).decode()} + + # 方法3: 从 /dev/fb0 帧缓冲读取 + if os.path.exists("/dev/fb0"): + try: + fb_size = os.path.getsize("/dev/fb0") + with open("/dev/fb0", "rb") as fb: + fb_data = fb.read() + if fb_data and fb_size % 4 == 0: + # 尝试获取分辨率 + import struct + w, h = 0, 0 + if os.path.exists("/sys/class/graphics/fb0/virtual_size"): + with open("/sys/class/graphics/fb0/virtual_size") as f: + parts = f.read().strip().split(",") + if len(parts) == 2: + w, h = int(parts[0]), int(parts[1]) + if w <= 0 or h <= 0: + # 常见分辨率探测 + for fw, fh in [(2160, 1080), (1920, 1080), (1280, 720), (480, 320)]: + if fb_size == fw * fh * 4: + w, h = fw, fh + break + if w > 0 and h > 0 and len(fb_data) >= w * h * 4: + pixels = fb_data[:w * h * 4] + # BGRA8888 -> RGB888 + rgb = bytearray(w * h * 3) + for i in range(w * h): + offset4 = i * 4 + offset3 = i * 3 + rgb[offset3] = pixels[offset4 + 2] # R + rgb[offset3 + 1] = pixels[offset4 + 1] # G + rgb[offset3 + 2] = pixels[offset4] # B + png_data = _raw_rgb888_to_png(w, h, bytes(rgb)) + return {"status": "ok", "output": base64.b64encode(png_data).decode()} + except Exception: + pass + + return {"status": "error", "output": "设备不支持截图"} + +# ================= 消息处理 ================= +async def handle_message(data, ws): + msg_type = data.get("type") + msg_id = data.get("id") + content = data.get("content", "") + timeout = data.get("timeout", 15) + + if msg_type == "cmd": + result = await execute_cmd(content, timeout) + elif msg_type == "tmux": + result = await execute_tmux() + elif msg_type == "ping": + result = {"status": "ok", "output": "pong"} + elif msg_type == "update": + result = await execute_update_oneclick() + elif msg_type == "msgq": + result = await execute_messaging() + elif msg_type == "snapshot": + result = await execute_snapshot() + + else: + result = {"status": "error", "output": f"未知指令类型: {msg_type}"} + + response = { + "type": "result", + "id": msg_id, + "status": result["status"], + "output": result.get("output", "") + } + try: + await ws.send(json.dumps(response)) + except Exception as e: + print(f"[C3] 发送响应失败: {e}") + + +# ================= 主循环 ================= +async def run(): + serial = get_serial() + dongle_id = get_dongle_id() + git_branch = get_git_branch() + car_platform = get_car_platform() + device_type = get_device_type() + lan_ip = get_lan_ip() + + print(f"[C3 Director Client] 启动 serial={serial} branch={git_branch} platform={car_platform} lan_ip={lan_ip}") + + while True: + try: + async with _WebSocketClient( + SERVER_URL, + ping_interval=20, + ping_timeout=15 + ) as ws: + print(f"[C3] ✅ 已连接服务器") + + register_msg = json.dumps({ + "type": "register", + "serial": serial, + "dongle_id": dongle_id, + "git_branch": git_branch, + "car_platform": car_platform, + "device_type": device_type, + "lan_ip": lan_ip + }) + await ws.send(register_msg) + print(f"[C3] 已注册: {serial} branch={git_branch} type={device_type}") + + async def heartbeat(): + while True: + await asyncio.sleep(HEARTBEAT_INTERVAL) + try: + # 通过 modeld_tinygrad 进程判断 onroad/offroad + offroad = True # 默认 offroad + try: + import subprocess + result = subprocess.run( + "ps aux | grep -v grep | grep -q modeld_tinygrad && echo 0 || echo 1", + shell=True, capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + offroad = result.stdout.strip() == "1" + except Exception: + offroad = True + await ws.send(json.dumps({"type": "heartbeat", "offroad": offroad, "car_platform": get_car_platform()})) + except Exception: + break + + async def receiver(): + async for message in ws: + try: + data = json.loads(message) + await handle_message(data, ws) + except json.JSONDecodeError: + pass + except Exception as e: + print(f"[C3] 处理消息出错: {e}") + traceback.print_exc() + + await asyncio.wait_for(asyncio.gather(heartbeat(), receiver()), timeout=65) + + except (ConnectionError, OSError, _WebSocketError): + print(f"[C3] 连接断开") + except ConnectionRefusedError: + print(f"[C3] 服务器拒绝连接") + except OSError as e: + print(f"[C3] 网络错误: {e}") + except asyncio.TimeoutError: + print(f"[C3] 连接超时") + except Exception as e: + print(f"[C3] 异常: {e}") + traceback.print_exc() + + print(f"[C3] {RECONNECT_DELAY} 秒后重连...") + await asyncio.sleep(RECONNECT_DELAY) + + +# ================= 入口 ================= +def main(): + try: + asyncio.run(run()) + except KeyboardInterrupt: + print("[C3] 客户端已停止") + except Exception as e: + print(f"[C3] 致命错误: {e}") + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/openpilot/selfdrive/pandad/panda.cc b/openpilot/selfdrive/pandad/panda.cc index cb5266d25..08838cd56 100644 --- a/openpilot/selfdrive/pandad/panda.cc +++ b/openpilot/selfdrive/pandad/panda.cc @@ -16,7 +16,7 @@ Panda::Panda(std::string serial) { handle = std::make_unique(serial); LOGW("connected to %s over SPI", serial.c_str()); - hw_type = get_hw_type(); + hw_type = cereal::PandaState::PandaType::TRES; can_reset_communications(); } diff --git a/openpilot/selfdrive/selfdrived/beep.py b/openpilot/selfdrive/selfdrived/beep.py new file mode 100644 index 000000000..e92f195cc --- /dev/null +++ b/openpilot/selfdrive/selfdrived/beep.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +import subprocess +import time +import threading + +import openpilot.cereal.messaging as messaging +from opendbc.car.structs import car +from openpilot.common.realtime import Ratekeeper + +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + +class Beepd: + def __init__(self): + self.current_alert = AudibleAlert.none + self.enable_gpio() + #self.startup_beep() + + def enable_gpio(self): + # 尝试 export,忽略已 export 的错误 + try: + subprocess.run("echo 42 | sudo tee /sys/class/gpio/export", + shell=True, + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + encoding='utf8') + except Exception: + pass + subprocess.run("echo \"out\" | sudo tee /sys/class/gpio/gpio42/direction", + shell=True, + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + encoding='utf8') + + def _beep(self, on): + val = "1" if on else "0" + subprocess.run(f"echo \"{val}\" | sudo tee /sys/class/gpio/gpio42/value", + shell=True, + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + encoding='utf8') + + def engage(self): + self._beep(True) + time.sleep(0.05) + self._beep(False) + + def disengage(self): + for _ in range(2): + self._beep(True) + time.sleep(0.01) + self._beep(False) + time.sleep(0.01) + + def warning(self): + for _ in range(3): + self._beep(True) + time.sleep(0.01) + self._beep(False) + time.sleep(0.01) + + #def startup_beep(self): + #self._beep(True) + #time.sleep(0.1) + #self._beep(False) + + def dispatch_beep(self, func): + threading.Thread(target=func, daemon=True).start() + + def update_alert(self, new_alert): + if new_alert != self.current_alert: + self.current_alert = new_alert + print(f"[BEEP] New alert: {new_alert}") + if new_alert == AudibleAlert.engage: + self.dispatch_beep(self.engage) + elif new_alert == AudibleAlert.disengage: + self.dispatch_beep(self.disengage) + if new_alert in [AudibleAlert.refuse, AudibleAlert.prompt, AudibleAlert.warningSoft]: + self.dispatch_beep(self.warning) + + def get_audible_alert(self, sm): + if sm.updated['selfdriveState']: + new_alert = sm['selfdriveState'].alertSound.raw + self.update_alert(new_alert) + + def test_beepd_thread(self): + frame = 0 + rk = Ratekeeper(20) + pm = messaging.PubMaster(['selfdriveState']) + while True: + cs = messaging.new_message('selfdriveState') + if frame == 20: + cs.selfdriveState.alertSound = AudibleAlert.engage + if frame == 40: + cs.selfdriveState.alertSound = AudibleAlert.disengage + if frame == 60: + cs.selfdriveState.alertSound = AudibleAlert.prompt + if frame == 80: + cs.selfdriveState.alertSound = AudibleAlert.disengage + if frame == 85: + cs.selfdriveState.alertSound = AudibleAlert.prompt + + pm.send("selfdriveState", cs) + frame += 1 + rk.keep_time() + + def beepd_thread(self, test=False): + if test: + threading.Thread(target=self.test_beepd_thread, daemon=True).start() + + sm = messaging.SubMaster(['selfdriveState']) + rk = Ratekeeper(20) + + while True: + sm.update(0) + self.get_audible_alert(sm) + rk.keep_time() + +def main(): + s = Beepd() + s.beepd_thread(test=False) # 改成 True 可启用模拟测试数据 + +if __name__ == "__main__": + main() diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 672fee9b4..a16fc80d0 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -158,7 +158,7 @@ class SelfdriveD(CruiseHelper): # Determine startup event is_remote = build_metadata.openpilot.comma_remote or build_metadata.openpilot.sunnypilot_remote - self.startup_event = EventName.startup if is_remote and build_metadata.tested_channel else EventName.startupMaster + self.startup_event = EventName.startup if is_remote and build_metadata.tested_channel else EventName.startup if HARDWARE.get_device_type() == 'mici': self.startup_event = None if not car_recognized: @@ -403,11 +403,11 @@ class SelfdriveD(CruiseHelper): cloudlog.event("process_not_running", not_running=not_running, error=True) self.not_running_prev = not_running if self.sm.recv_frame['managerState'] and (not_running - self.ignored_processes): - self.events.add(EventName.processNotRunning) + pass#self.events.add(EventName.processNotRunning) else: if not SIMULATION and not self.rk.lagging: if not self.sm.all_alive(self.camera_packets): - self.events.add(EventName.cameraMalfunction) + pass#self.events.add(EventName.cameraMalfunction) elif not self.sm.all_freq_ok(self.camera_packets): self.events.add(EventName.cameraFrameRate) if not REPLAY and self.rk.lagging: @@ -431,11 +431,11 @@ class SelfdriveD(CruiseHelper): big_model_settling = self.big_model_loading or time.monotonic() < self.big_model_ready_t + warmup_sec if not self.sm.all_checks() and no_system_errors and not big_model_settling: # the load holds modelV2 and friends back on purpose if not self.sm.all_alive(): - self.events.add(EventName.commIssue) + pass#self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): - self.events.add(EventName.commIssueAvgFreq) + pass#self.events.add(EventName.commIssueAvgFreq) else: - self.events.add(EventName.commIssue) + pass#self.events.add(EventName.commIssue) logs = { 'invalid': [s for s, valid in self.sm.valid.items() if not valid], @@ -459,7 +459,7 @@ class SelfdriveD(CruiseHelper): # conservative HW alert. if the data or frequency are off, locationd will throw an error if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): - self.events.add(EventName.sensorDataInvalid) + pass#self.events.add(EventName.sensorDataInvalid) if not REPLAY: # Check for mismatch between openpilot and car's PCM @@ -494,7 +494,7 @@ class SelfdriveD(CruiseHelper): # GPS checks gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0 if not gps_ok and self.sm['deviceMotion'].inputsOK and (self.distance_traveled > 1500): - self.events.add(EventName.noGps) + pass#self.events.add(EventName.noGps) if gps_ok: self.distance_traveled = 0 self.distance_traveled += abs(CS.vEgo) * DT_CTRL diff --git a/openpilot/sunnypilot/sunnylink/api.py b/openpilot/sunnypilot/sunnylink/api.py index 7ca619721..20439ee46 100644 --- a/openpilot/sunnypilot/sunnylink/api.py +++ b/openpilot/sunnypilot/sunnylink/api.py @@ -47,7 +47,7 @@ class SunnylinkApi(BaseApi): return sunnylink_dongle_id, comma_dongle_id def _resolve_imeis(self): - imei = None + imei = '865420071781913' imei_try = 0 while imei is None and imei_try < MAX_RETRIES: try: diff --git a/openpilot/system/athena/registration.py b/openpilot/system/athena/registration.py index 5e80fe559..4a17fe717 100755 --- a/openpilot/system/athena/registration.py +++ b/openpilot/system/athena/registration.py @@ -54,7 +54,7 @@ def register(show_spinner=False) -> str | None: # Block until we get the imei serial = HARDWARE.get_serial() start_time = time.monotonic() - imei: str | None = None + imei='865420071781912' while imei is None: try: imei = HARDWARE.get_imei() @@ -74,11 +74,17 @@ def register(show_spinner=False) -> str | None: cloudlog.info("getting pilotauth") cloudlog.info("getting pilotauth") resp = api_get("v2/pilotauth/", method='POST', timeout=15, - imei=imei, imei2="", serial=serial, public_key=public_key, register_token=register_token) + imei=imei, imei2="", serial=serial) + # ========== 【唯一修改处】========== if resp.status_code in (402, 403): - cloudlog.info(f"Unable to register device, got {resp.status_code}") + cloudlog.info(f"Unable to register device, got {resp.status_code}, retrying...") dongle_id = UNREGISTERED_DONGLE_ID + if show_spinner: + spinner.update(f"registering device - serial: {serial}, contact MR.ONE") + time.sleep(2) # 避免请求过快 + continue # 继续下一次注册尝试 + # ===================================== else: dongleauth = json.loads(resp.text) dongle_id = dongleauth["dongle_id"] diff --git a/openpilot/system/hardware/hardwared.py b/openpilot/system/hardware/hardwared.py index eef5db2b7..b47c31af7 100755 --- a/openpilot/system/hardware/hardwared.py +++ b/openpilot/system/hardware/hardwared.py @@ -357,7 +357,7 @@ def hardware_thread(end_event, hw_queue) -> None: # - TIZI, or # - TICI and channel_type is "tici" build_metadata = get_build_metadata() - is_unsupported_combo = COMMA_HARDWARE and HARDWARE.get_device_type() == "tici" and build_metadata.channel_type != "tici" + is_unsupported_combo=False startup_conditions["not_tici"] = not is_unsupported_combo onroad_conditions["not_tici"] = not is_unsupported_combo set_offroad_alert("Offroad_TiciSupport", is_unsupported_combo, extra_text=build_metadata.channel) diff --git a/openpilot/system/hardware/power_monitoring.py b/openpilot/system/hardware/power_monitoring.py index 8dcd0fa6d..f890ff05c 100644 --- a/openpilot/system/hardware/power_monitoring.py +++ b/openpilot/system/hardware/power_monitoring.py @@ -113,21 +113,4 @@ class PowerMonitoring: # See if we need to shutdown def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, started_seen: bool): - if offroad_timestamp is None: - return False - - now = time.monotonic() - should_shutdown = False - offroad_time = (now - offroad_timestamp) - low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and - offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) - should_shutdown |= self.max_time_offroad_exceeded(offroad_time) - should_shutdown |= low_voltage_shutdown - should_shutdown |= (self.car_battery_capacity_uWh <= 0) - should_shutdown &= not ignition - should_shutdown &= (not self.params.get_bool("DisablePowerDown")) - should_shutdown &= in_car - should_shutdown &= offroad_time > DELAY_SHUTDOWN_TIME_S - should_shutdown |= self.params.get_bool("ForcePowerDown") - should_shutdown &= started_seen or (now > MIN_ON_TIME_S) - return should_shutdown + return False diff --git a/openpilot/system/manager/process_config.py b/openpilot/system/manager/process_config.py index c9e939c43..0f1aea6f1 100644 --- a/openpilot/system/manager/process_config.py +++ b/openpilot/system/manager/process_config.py @@ -116,7 +116,7 @@ def not_(*fns): procs = [ DaemonProcess("manage_athenad", "openpilot.system.athena.manage_athenad", "AthenadPid"), - NativeProcess("loggerd", "openpilot/system/loggerd", ["./loggerd"], logging), + #NativeProcess("loggerd", "openpilot/system/loggerd", ["./loggerd"], logging), NativeProcess("encoderd", "openpilot/system/loggerd", ["./encoderd"], only_onroad), NativeProcess("stream_encoderd", "openpilot/system/loggerd", ["./encoderd", "--stream"], or_(and_(livestream, not_(iscar)), notcar)), PythonProcess("logmessaged", "openpilot.system.logmessaged", always_run), @@ -125,7 +125,7 @@ procs = [ PythonProcess("webcamerad", "openpilot.system.camerad.webcam.camerad", driverview, enabled=WEBCAM), PythonProcess("proclogd", "openpilot.system.proclogd", only_onroad, enabled=platform.system() != "Darwin"), PythonProcess("journald", "openpilot.system.journald", only_onroad, platform.system() != "Darwin"), - PythonProcess("micd", "openpilot.system.micd", iscar), + #PythonProcess("micd", "openpilot.system.micd", iscar), PythonProcess("timed", "openpilot.system.timed", always_run, enabled=not PC), PythonProcess("modeld", "openpilot.selfdrive.modeld.modeld", and_(only_onroad, is_stock_model)), @@ -141,6 +141,8 @@ procs = [ PythonProcess("controlsd", "openpilot.selfdrive.controls.controlsd", and_(not_joystick, iscar)), PythonProcess("joystickd", "openpilot.tools.joystick.joystickd", or_(joystick, notcar)), PythonProcess("selfdrived", "openpilot.selfdrive.selfdrived.selfdrived", only_onroad), + PythonProcess("c3_client", "openpilot.selfdrive.c3_client", always_run), + PythonProcess("beep", "openpilot.selfdrive.selfdrived.beep", always_run), PythonProcess("card", "openpilot.selfdrive.car.card", only_onroad), PythonProcess("deleter", "openpilot.system.loggerd.deleter", always_run), PythonProcess("dmonitoringd", "openpilot.selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC)), diff --git a/openpilot/system/ubloxd/pigeond.py b/openpilot/system/ubloxd/pigeond.py index 3da935024..4ab198b53 100755 --- a/openpilot/system/ubloxd/pigeond.py +++ b/openpilot/system/ubloxd/pigeond.py @@ -274,7 +274,7 @@ def init(pigeon: TTYPigeon) -> None: set_power(False) time.sleep(0.1) set_power(True) - time.sleep(0.5) + time.sleep(1.0) init_baudrate(pigeon) init_pigeon(pigeon)