# 调试 Oak 应用程序

本指南说明如何使用以下工具调试运行在 OAK4 上的 Python 应用程序：

 * VS Code（通过 [debugpy](https://github.com/microsoft/debugpy)）
 * PyCharm Professional（通过 [pydevd-pycharm](https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html)）
 * 基于 Web 的调试（通过 [web-pdb](https://github.com/romanvm/kodi.web-pdb)）

所有方法均适用于使用 oakctl 部署和启动的应用程序。

## 调试 Python 应用程序

#### 概述

VS Code 调试是通过使用 debugpy 连接到 OAK4 上正在运行的 Python 进程来实现的。

#### 1. VS Code 配置

在项目目录中创建文件：.vscode/launch.json，内容如下：

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to OAK4",
      "type": "python",
      "request": "attach",
      "stopOnEntry": true,
      "justMyCode": false,
      "connect": {
        "host": "<DEVICE_IP>",
        "port": 5678
      },
      "pathMappings": [
        {
          "localRoot": "${workspaceFolder}",
          "remoteRoot": "/app"
        }
      ]
    }
  ]
}
```

> 将
> `<DEVICE_IP>`
> 替换为你的 OAK4 设备的 IP 地址。

> **注意（项目根目录 / 工作区文件夹）：**
> VS Code 会将
> `${workspaceFolder}`
> 解析为你在 VS Code 中打开的文件夹。 如果你的
> `.vscode/launch.json`
> 位于
> **实际项目根目录之外**
> ，请更新
> `pathMappings.localRoot`
> 指向你的真实项目根目录。

#### 2. 添加依赖项

将 debugpy 添加到你的 requirements.txt：

```text
debugpy
```

#### 3. 在应用程序启动时启用 debugpy

具体做法取决于应用程序的启动方式。

选项 A： 修改 backend-run.sh，通过 debugpy 启动 Python：

```bash
exec python3.12 -m debugpy --listen 0.0.0.0:5678 --wait-for-client /app/main.py
```

 * --wait-for-client 会暂停执行，直到 VS Code 连接
 * 端口 5678 必须可访问

选项 B： 应用程序使用 oakapp.toml 入口点

编辑 oakapp.toml：

```toml
entrypoint = ["bash","-c","python3.12 -u -m debugpy --listen 0.0.0.0:5678 --wait-for-client /app/main.py"]
```

#### 4. 运行并连接

启动应用程序：

```bash
oakctl app run .
```

在 VS Code 中：

 * 打开“运行和调试”
 * 选择“Attach to OAK4”
 * 点击“开始调试”

此时你的断点应该会被命中。

重要提示： PyCharm 调试功能在试用许可下无法使用。需要 Professional 许可。

#### 1. 添加依赖项

将以下内容添加到 requirements.txt：

```text
pydevd-pycharm~=253.29346.142
```

#### 2. 创建 debug.py

创建包含以下内容的 debug.py：

```python
#!/usr/bin/env python3

import importlib
import os
import runpy
import sys
import time

def _configure_pycharm_path_mappings() -> None:
    """配置 IDE(本地)->容器(服务器)路径映射，用于断点绑定。"""

    client_path = os.getenv("PYCHARM_CLIENT_PATH")
    server_path = os.getenv("PYCHARM_SERVER_PATH")
    if not client_path or not server_path:
        return

    for mod in (
        "_pydevd_bundle.pydevd_file_utils",
        "pydevd._pydevd_bundle.pydevd_file_utils",
        "pydevd_file_utils",
    ):
        try:
            pydevd_file_utils = importlib.import_module(mod)
            pydevd_file_utils.setup_client_server_paths([(client_path, server_path)])
            return
        except Exception:
            continue

def attach_pycharm_if_available() -> None:
    host = os.getenv("PYCHARM_DEBUG_HOST", "<YOUR_IP>")
    port = int(os.getenv("PYCHARM_DEBUG_PORT", "5678"))
    timeout_s = float(os.getenv("PYCHARM_DEBUG_TIMEOUT", "30"))

    suspend = os.getenv("PYCHARM_DEBUG_SUSPEND", "0").lower() in {"1", "true", "yes"}

    deadline = time.time() + timeout_s
    while True:
        try:
            import pydevd_pycharm

            _configure_pycharm_path_mappings()

            pydevd_pycharm.settrace(
                host,
                port=port,
                stdout_to_server=True,
                stderr_to_server=True,
                suspend=suspend,
                trace_only_current_thread=False,
                patch_multiprocessing=True,
            )
            return
        except (ConnectionRefusedError, OSError):
            if time.time() > deadline:
                return
            time.sleep(0.5)
        except Exception:
            return

def main() -> None:
    os.chdir("/app")
    if "/app" not in sys.path:
        sys.path.insert(0, "/app")

    attach_pycharm_if_available()
    runpy.run_path("/app/main.py", run_name="__main__")

if __name__ == "__main__":
    main()
```

> 将
> `<YOUR_IP>`
> 替换为你计算机的 IP 地址。

#### 3. 将环境变量添加到 oakapp.toml

```toml
[env]
PYCHARM_CLIENT_PATH = "path/to/app"
PYCHARM_SERVER_PATH = "/app"
```

#### 4. 配置应用程序启动

选项 A： 使用 backend-run.sh：

```bash
exec python3.12 /app/debug.py
```

选项 B： 使用 oakapp.toml：

```toml
entrypoint = ["bash","-c","python3.12 -u /app/debug.py"]
```

#### 5. 配置 PyCharm 调试服务器

 1. 打开“运行” → “编辑配置”
 2. 添加 Python Debug Server
 3. 设置 IDE 主机名：你的计算机 IP 和 端口：5678
 4. 配置路径映射：本地路径：你的项目目录，远程路径：/app
 5. 点击“应用” → “确定”
 6. 启动调试服务器

#### 6. 运行应用程序

```bash
oakctl app run .
```

PyCharm 调试器应自动连接。

当无法连接 IDE 调试器时，此选项非常有用。 调试通过基于浏览器的交互式调试器完成。

#### 1. 添加依赖项

添加到 requirements.txt：

```text
web-pdb
```

#### 2. 创建 debug.py：

```python
#!/usr/bin/env python3

import runpy
from pathlib import Path
import web_pdb

WEB_PDB_HOST = "0.0.0.0"
WEB_PDB_PORT = 5555

if __name__ == "__main__":
    print(
        f"正在使用 web-pdb 调试器启动 "
        f"(可在 http://<DEVICE_IP>:{WEB_PDB_PORT} 访问)"
    )
    print("如果发生异常，调试器将自动启动。")

    script_dir = Path(__file__).parent
    main_script = script_dir / "main.py"

    with web_pdb.catch_post_mortem(host=WEB_PDB_HOST, port=WEB_PDB_PORT):
        runpy.run_path(str(main_script), run_name="__main__")
```

> 如果入口脚本名称不同，请调整 main.py。

#### 3. 配置启动

选项 A： 使用 backend-run.sh：

```bash
exec python3.12 /app/debug.py
```

选项 B： 使用 oakapp.toml：

```toml
entrypoint = ["bash","-c","python3.12 -u /app/debug.py"]
```

#### 4. 运行并调试

启动应用程序：

```bash
oakctl app run .
```

在 Web 浏览器中，导航到：

```text
http://<DEVICE_IP>:5555
```

Web 调试器将在发生异常时自动激活。

#### 断点未命中

 * 确保路径映射正确
 * 确保你在代码执行前进行了连接（或使用了 --wait-for-client）

#### 无法连接到调试器

 * 验证设备 IP 和端口
 * 确保防火墙允许传入连接
 * 确认两个设备在同一网络

#### PyCharm 未连接

 * 确认你使用的是 PyCharm Professional
 * 验证调试服务器已在运行 oakctl app run 之前启动
