Metadata-Version: 2.4
Name: sdev
Version: 0.6.4
Summary: 串口控制器工具包
Home-page: https://github.com/klrc/sdev
Author: klrc
Author-email: klrc <144069824@qq.com>
License: MIT
Project-URL: Homepage, https://github.com/klrc/sdev
Project-URL: Repository, https://github.com/klrc/sdev
Project-URL: Bug Tracker, https://github.com/klrc/sdev/issues
Keywords: serial,controller,hardware,embedded
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Hardware
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyserial>=3.5
Requires-Dist: loguru>=0.6.0
Requires-Dist: zeroconf>=0.39.0
Dynamic: author
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python

# SDEV：串口设备统一入口

本文件是 SDEV 的 **设计规范与实现说明**，只描述目标行为与核心内部架构，不关心历史实现细节。

本文先描述 **命令行形式**，后续再补充 Python 接口。

---

## 命令行总览

安装后提供一个统一命令：`sdev`。

当前仅设计三类基础功能：

- **`sdev list`**：发现并列出可用串口设备
- **`sdev run`**：在指定（或默认）设备上执行命令
- **`sdev server`**：启动本机服务以共享本地串口

另外提供少量辅助配置命令：

- **`sdev set default ...`**：配置默认设备
- **`sdev show default`**：打印当前 default 配置
- **`sdev reset default`**：清空默认设备配置
- **`sdev reset all`**：清空全部缓存与状态

---

## sdev list：设备发现与筛选

`list` 子命令用于从「本机出发」列出所有可访问的串口设备（本地 + 远程）。

基础用法：

- **`sdev list`**
  - 不带任何参数。
  - 列出所有从本机出发可用的串口设备（包含本地与远程）。

- **`sdev list local`**
  - 仅列出所有 **本地** 的串口设备。

- **`sdev list remote`**
  - 仅列出所有 **远程** 的串口设备。

筛选用法（按属性过滤）：

- **`sdev list type=xc01`**
  - 列出所有类型为 `xc01` 的设备。

- **`sdev list host=192.168.1.100`**
  - 列出所有位于 `192.168.1.100` 这台主机上的串口设备。

- **组合筛选**
  - 参数可以组合使用，例如：
    - `sdev list type=xc01 host=192.168.1.100`
  - 表示在 `192.168.1.100` 这台主机上，筛选出类型为 `xc01` 的所有设备。

- **`sdev list -f`（快速模式）**
  - 用法示例：`sdev list -f`、`sdev list -f type=xc01` 等。
  - 扫描时只要找到 **任意一个** 符合条件的设备就 **立即返回**，不等待其余扫描完成。
  - 为保持稳定性，扫描仍在后台继续直到完成，但 **不产生任何输出**，也不阻塞当前程序退出。
  - 一般与筛选条件配合使用，用于脚本中快速拿到一个可用设备。

### sdev list 的详细输出格式

**扫描过程（扫描完成后会移除，最终只保留表格）：**

- 首行：`[main] scanning N local port(s) and discovering remote hosts...`
- 之后每个端口/主机对应一行，由独立线程输出，**无先后顺序**：
  - 格式：`[<host>] check alive: <port> ...`，例如：
    - `[localhost] check alive: /dev/ttyUSB0 ...`
    - `[192.168.1.100] check alive: /dev/ttyUSB1 ...`
  - 行尾为 **spinning 动画**，完成后变为 **✔**；若超时则显示 **timeout**。
  - 设备因 **串口被 lock** 或无响应导致超时，均视为 **timeout**。
- 结束行：`[main] all scanning finished.`

**最终表格（仅保留可用设备）：**

表头与列：

```
HOST           PORT           DEVICE ID   DEVICE TYPE   LAST UPDATE
---------------------------------------------------------------------------
localhost      /dev/ttyUSB0   8ef212      unknown       2026-03-05 11:19:39
192.168.1.101  /dev/ttyUSB0   2f9c9e      xc01          2026-03-05 11:19:40
```

- **HOST**：主机标识，本地为 `localhost`。
- **PORT**：串口路径（如 `/dev/ttyUSB0`）。
- **DEVICE ID**：基于 host + port 计算的本地 hash，供 `sdev run`、`sdev set default` 等使用。
- **DEVICE TYPE**：由板端 `cat /proc/device-tree/model 2>/dev/null; echo` 获取，为空则显示 `unknown`。
- **LAST UPDATE**：该条记录最后更新时间。

**缓存更新：**

- 每次 `sdev list` 执行完毕后，会 **更新本地 list 缓存文件**。
- 后续任何需要指定 `<device id>` 的操作（如 `sdev run`、`sdev set default`），都通过该缓存文件解析出对应的 host 与 port。

---

## sdev run：在设备上执行命令

`run` 子命令用于对某个串口设备发送命令，并根据提示符或超时规则判断结束。

基础用法：

- **`sdev run <device id> <command>`**
  - 向指定设备发送 `<command>`。
  - 执行后会等待：
    - 下一个 **prompt 标记**，或
    - 内部的 **响应超时**（一段时间没有任何输出）。

高级参数：

- **`-p <prompt flag>`**
  - 用法：`sdev run <device id> -p <prompt flag> <command>`
  - 手动指定结束标记 `<prompt flag>`，即看到该标记后认为命令完成。

- **`-t <timeout>`**
  - 指定本次命令的 **整体超时时间**。
  - 从发送命令开始计时，即使还未触发内部响应超时，整体超时到了也会结束。
  - 一旦整体超时触发，客户端不再等待 prompt，直接断开。

- **`-w` / `--wait-forever`**
  - 取消内部的「响应超时」逻辑。
  - 表示在没有整体 timeout 的前提下，可以一直等待输出，直到用户手动中断或连接断开。

默认设备与 `device id`：

- `sdev run` 正常情况下需要显式指定 `<device id>`。
- 配置默认设备后，可以省略 `<device id>`（见后文 `sdev set default`）。

### sdev run 的详细输出格式

**示例（已配置默认设备时）：**

```bash
$ sdev run 'cat /proc/meminfo'
```

- **命令引号**：建议用 **单引号** 包裹命令，避免 shell 对 `$`、`!` 等做展开；若命令内需要变量展开则用双引号。

**输出结构：**

1. **回显用户输入（黄色）**
   - 一行以 `>` 开头，直接打印用户输入的命令，例如：`> cat /proc/meminfo`

2. **串口输出（灰色 + 绿色）**
   - 真实串口输出内容以 **灰色** 显示。
   - 匹配到的 **last prompt**（用于判断命令结束的那一行提示符）以 **绿色** 显示。

3. **超时结束标记（红色）**
   - 若因超时结束（未等到 prompt），会自动换行并打印红色 **`<sdev timeout>`** 作为结束标记。

### sdev run 的上层运行原理

**执行流程概要：**

1. **入口**：sdev CLI 调用 `SerialRunner.run()`，传入命令、prompt、超时等参数。
2. **清空并写入**：先清空 **SerialCore** 的缓存，再向 core 内写入本次要发送的命令。
3. **scan 输出**：写入后立即开始 **scan**，从 core 持续读取串口输出并按要求输出到终端。
4. **scan 的两个顺序目标**：
   - 先匹配 **输入命令的回显**（如 `> cat /proc/meminfo` 所对应的回显）；
   - 再匹配 **结束符**（prompt 标记）。
   - 对「回显」和「结束符」的等待都受 **响应超时** 约束；使用 **`-w`** 可取消响应超时，改为永久等待（直到整体超时或用户中断）。
5. **整体超时**：存在于 run 操作的 **最外层**，从函数开始执行时计时；在 **内部每次迭代** 中检查计时器，若已整体超时则立即退出，不再等待 prompt。

**角色与接口：**

- **SerialCore**：底层串口读写与缓冲；可被清空、写入命令、被上层读取 scan 数据。
- **SerialRunner**：接口类，封装一次 run 的流程，与 SerialCore 和 sdev CLI 交互。提供方法：
  - `run()`：执行单次命令（清空 core → 写命令 → scan 至结束符或超时）；
  - `check_alive()`：存活检测；
  - `check_model_type()`：探测板型（如 xc01），供 list 等使用；
  - `send_interrupt()`：发送中断。
- **sdev CLI**：解析命令行参数，解析 device id → host/port，构造 SerialRunner（或子类实例），调用 `run()` 并处理输出与退出码。

**设备类型与子类：**

- 根据 `check_model_type()` 的结果，可映射到不同的 **设备子类**；这些子类均 **继承 SerialRunner**，用于不同板型的差异化行为（后续与现有代码结构的差异另述）。

**三者顺序交互（Mermaid）：**

```mermaid
sequenceDiagram
    participant CLI as sdev CLI
    participant Runner as SerialRunner
    participant Core as SerialCore

    CLI->>Runner: run(cmd, prompt, timeouts)
    Runner->>Core: 清空缓存
    Runner->>Core: 写入命令
    Runner->>Core: 开始读取 (scan)

    loop 每次迭代
        Core-->>Runner: 输出数据
        Runner->>Runner: 检查回显/结束符、响应超时、整体超时
        Runner->>CLI: 输出到终端（或结束）
        alt 命中结束符或整体超时
            Runner->>CLI: 结束 run
        end
    end
```

---

## sdev server：本地串口共享服务

`server` 子命令用于在本机启动一个服务进程，使其他主机可以通过网络访问本机串口。

基础用法：

- **`sdev server start`**
  - 启动本地 sdev 服务，用于共享本机串口设备。

- **`sdev server stop`**
  - 停止当前运行的 sdev 服务。

- **`sdev server restart`**
  - 重启 sdev 服务（等价于 `stop` 后 `start`）。

- **`sdev server status`**
  - 查询当前 sdev 服务的运行状态。

具体网络协议、端口和认证策略的实现细节不在本文展开。

---

## 默认设备与缓存管理

### 设置与清空默认设备

默认设备用于在日常使用中省去频繁输入 `<device id>` 的步骤。

- **`sdev set default <device id>`**
  - 将某个 `<device id>` 配置为默认设备。
  - `<device id>` 来源于 `sdev list` 的缓存记录。

- **`sdev set <host> <serial device>`**
  - 另一种等价写法。
  - 内部会把 `<host> + <serial device>` 组合记录下来。
  - `<device id>` 本质上只是一个基于 `host + device` 计算得到的 **本地 hash 标识**，方便人类记忆与脚本使用。

- **`sdev reset default`**
  - 清空当前所有「默认设备」配置。

- **`sdev show default`**
  - 打印当前的 default 配置（即当前默认设备对应的 host、port 等信息）；未配置时给出相应提示。

行为约定：

- 配置默认设备之后：
  - 可以使用 **`sdev run <command>`**（不带 `<device id>`），自动落到默认设备上。
- 未配置默认设备时：
  - **必须**显式指定 `<device id>`，否则命令视为非法或直接报错。

### 全量重置：清空所有缓存

- **`sdev reset all`**
  - 关闭当前已开启的 sdev server（如果存在）。
  - 清空所有与 sdev 相关的 **缓存目录与配置记录**：
    - 包括设备列表缓存
    - 默认设备配置
    - 其他与 sdev 运行状态相关的本地缓存

`reset all` 的目标是把环境恢复到「从未使用过 sdev」的干净状态，方便重新部署或排查问题。

---

## 内部架构与代码布局

本节描述当前 SDEV 的内部分层架构与代码布局；旧实现保存在 `sdev.deprecated` 下，仅供查阅，不再直接引用。

### 分层结构概览

- **模块层（Modules）**：`sdev/modules/`
  - 提供可复用的基础组件，例如：
    - `serial_core.py`：底层串口读写与缓冲（`SerialCore`）。
    - `serial_runner.py`：运行控制与协议抽象（`SerialRunner` 及其子类）。
- **命令行层（CLI）**：`sdev/cli/`
  - 提供 `sdev list` / `sdev run` / `sdev server` 等命令行功能。
  - 通过 `SerialRunner` 间接使用 `SerialCore`，不直接操作底层串口。
- **Python 接口层（API）**：`sdev/api/`
  - 提供 Python 脚本可直接导入的接口（如 `Demoboard`），与 CLI 并列存在，都是基于 `SerialRunner` 的上层产物。

### SerialCore：串口核心组件

- 位置：`sdev/modules/serial_core.py`
- 职责：
  - 封装底层串口打开/关闭、读写与缓冲逻辑。
  - 提供「清空缓存」「写入一条命令」「按流式接口 scan 输出」等基础能力。
- 说明：
  - **所有上层代码（CLI / Python API）都通过 SerialRunner 间接访问 SerialCore**。

### SerialRunner：统一的串口运行抽象

- 位置：`sdev/modules/serial_runner.py`
- 形式：接口类（或抽象基类），构造时接收一个 `SerialCore` 实例。
- 核心方法（接口约定）：
  - `run(...)`：执行一次完整命令（清空 core → 写命令 → scan 至结束符或超时），供 `sdev run` 与 Python API 调用。
  - `check_alive(...)`：存活检测，用于 `sdev list` 等场景。
  - `check_model_type(...)`：探测板型（如 `xc01`），供设备类型判断与路由使用。
  - `send_interrupt(...)`：向串口发送中断。
- 设备适配：
  - 不同类型的设备（由 `check_model_type()` 结果判定）映射到不同的 `SerialRunner` 子类，这些子类负责处理各自板型的特殊行为。

### Python 接口：Demoboard 及其所在目录

- 目录命名：选择 **`sdev/api/`** 表达「对外的 Python API」，与命令行 `sdev/cli/` 区分。
  - 主文件：`sdev/api/demoboard.py`
  - 主类：`Demoboard(SerialRunner)`（或组合一个内部的 SerialRunner 实例）
- 职责：
  - 向外暴露一个简单易用的 Python 接口，用于在脚本中执行命令、检查设备状态等。
  - 内部通过 `SerialRunner` 与 `SerialCore` 协作，不直接操作串口。
- 与 CLI 的关系：
  - `Demoboard` 与 sdev CLI 属于 **并列** 的上层入口：
    - CLI 面向命令行用户；
    - `Demoboard` 面向 Python 调用者。
  - 二者共享同一套底层模块（SerialCore / SerialRunner），以避免重复实现。

### 远程共享服务整体设计（server + client）

目标：在不改动上层调用方式的前提下，使「本地串口」可以通过网络被其它机器复用。对 `SerialRunner` / CLI / Python API 来说，本地 core 与远程 core 使用 **同一套接口**。

#### 文件与职责

- **位置**：`sdev/modules/remote_core.py`。
- **内容集中在一个文件中维护**：
  - 服务端逻辑（server）：监听端口、接收请求、转发到本地 `SerialCore` 执行。
  - 客户端逻辑（client）：向远端 server 发起请求，并在本机暴露一个「看起来像 SerialCore 的对象」。
  - 工具函数：`transform_core_as_client(...)`，构造远程版 core。

#### transform_core_as_client(...) 的角色

- 函数签名（当前实现）：
  - `transform_core_as_client(host, service_port, serial_port, baudrate=115200) -> SerialCoreLike`
- 作用：
  - 以远端 `host:service_port` 和远端串口 `serial_port` 为参数，返回一个 **实现了与 `SerialCore` 相同接口** 的「客户端 core」：
    - 上层仍然把它当作 SerialCore 使用（清空缓存、写入命令、scan 输出等）。
    - 内部实际通过网络协议与远端 server 交互，而非直接访问本地串口。
- 设计要点：
  - **不改变上层代码结构**：`SerialRunner` 只依赖 `SerialCore` 接口，无需区分本地/远程。

#### server 与 client 的协作方式（概念）

- **server 端**（运行在“拥有真实串口”的机器上）：
  - 使用本地的 `SerialCore` 管理实际串口。
  - 监听一个 TCP 端口（或其他传输），接受来自 client 的请求：
    - 例如「写命令」「读取输出」「检查 alive」「探测 model type」等。
  - 面向网络暴露的是「类似 SerialCore 的 RPC 接口」，不关心更上层的 `SerialRunner` 细节。

- **client 端**（运行在“远程调用者”的机器上）：
  - 调用 `transform_core_as_client(...)`，获得一个网络版 core。
  - 上层 `SerialRunner` / CLI / Python API 完全按使用本地 core 的方式调用它。
  - 所有实际 I/O 通过网络转发到 server，再由 server 调用真实的本地 `SerialCore`。

#### 与现有命令的关系

- `sdev server start/stop/restart/status`：
  - CLI 层通过调用 `remote_core` 中的 server 入口函数来管理服务生命周期。
- `sdev list` / `sdev run`：
  - 从调用方视角，只关心「设备的 host / port / device id」。
  - 当目标是远程设备时，内部通过 `transform_core_as_client(...)` 构造对应的网络 core，再交给 `SerialRunner` 运行。

#### 主机发现与远程协议概要

- **主机发现（discover）**：
  - 优先使用环境变量 `SDEV_REMOTE_HOSTS="host[:port],..."` 显式指定远程主机列表。
  - 若未指定，则通过 UDP 广播向 `DISCOVERY_UDP_PORT=7001` 发送固定报文 `SDEV_DISCOVERY`，server 回复 `{"magic":"sdev-remote-v1","port":7000}`。
  - 同时通过 zeroconf(mDNS) 浏览 `_sdev._tcp.local.` 服务（可用 `SDEV_DISABLE_ZEROCONF=1` 关闭），综合两者结果，去重并排除本机 IP，得到最终远程 host 列表。
- **远程协议概要**（对上层透明）：
  - 连接建立时，client 发送一行 JSON：`{"port":"<serial_port>","baudrate":115200}`，server 成功后回复 `{"ok":true}`。
  - 会话期间，client 发送 `{"cmd":"send"|"scan"|"clear"|"disconnect", ...}`，server 将其映射为对本地 `SerialCore` 的调用，并以 `{"line":"..."}` / `{"scan_done":true}` 等形式回传结果。

**Mermaid：远程 run 流程（概览）**

```mermaid
sequenceDiagram
    participant CLI as sdev CLI (远端)
    participant Runner as SerialRunner
    participant RCore as RemoteSerialCore
    participant Server as RemoteServer
    participant Core as SerialCore(本地)

    CLI->>Runner: run(cmd, prompt, timeouts)
    Runner->>RCore: connect()
    RCore->>Server: TCP connect host:port
    RCore->>Server: {"port": "/dev/ttyUSB0","baudrate":115200}
    Server->>Core: connect()
    Server-->>RCore: {"ok":true}

    Runner->>RCore: send(cmd)
    RCore->>Server: {"cmd":"send","data":cmd}
    Server->>Core: send(cmd)

    Runner->>RCore: scan(end_flag=prompt)
    RCore->>Server: {"cmd":"scan", ...}
    loop 串口输出
        Core-->>Server: 行输出
        Server-->>RCore: {"line": "..."}
        RCore-->>Runner: 行输出
        Runner->>CLI: 按颜色规则输出到终端
    end
    Server-->>RCore: {"scan_done":true}
    RCore-->>Runner: 结束 scan
    Runner-->>CLI: 结束 run
```

### 旧代码

- 现有旧实现已移动到 `sdev.deprecated/`，仅用于查阅历史行为与细节。
- 新实现不再从 `sdev.deprecated` 中导入或复用代码。

---

<!-- 原开发顺序草案已废弃，这里留空以便后续需要时单独撰写开发者指南。 -->
