# 本地 Modbus TCP 指纹模拟服务设计

## 1. 背景与问题

### 1.1 问题描述

存在两个 VideoInferenceDemo 实例：

| 实例 | 工作目录 | Modbus TCP 端口 | 说明 |
|------|----------|-----------------|------|
| 工位二 | `C:\Users\Administrator\VideoInferenceDemo工位二` | `127.0.0.1:502` | 抢占方（先启动） |
| 原始 | `C:\Users\Administrator\VideoInferenceDemo` | `127.0.0.1:502` | 被抢占方（后启动） |

两个实例都试图独占连接物理指纹模块（RS485/COM），导致其中一个实例启动时端口被占用而无法连接，或连接成功后另一个实例无法使用。

### 1.2 解决方案

由抢占方（工位二）在 `127.0.0.1:503` 上启动本地 Modbus TCP **服务端**，将物理指纹模块的状态通过寄存器（地址 30-40）抽象出来。抢占失败的实例改为通过 Modbus TCP 客户端访问该服务端，模拟指纹模块的各种状态。

```
┌────────────────────────────────────────────────────────────────────┐
│  工位二 (127.0.0.1:502)                                            │
│                                                                    │
│  ┌──────────────┐    RS485/COM    ┌──────────────────┐          │
│  │ Fingerprint  │◄──────────────►│ FingerprintModule │          │
│  │   Module     │                │  (物理连接)        │          │
│  └──────────────┘                └────────┬─────────┘           │
│                                           │                      │
│                                    寄存器 30-40                  │
│                                           │                      │
│                                    ┌──────▼──────────┐           │
│                                    │ ModbusTcpServer │           │
│                                    │  127.0.0.1:503  │           │
│                                    └──────┬──────────┘           │
└───────────────────────────────────────────┼───────────────────────┘
                                            │ Modbus TCP
                                            │ (读寄存器 30-40)
┌───────────────────────────────────────────▼───────────────────────┐
│  原始实例 (127.0.0.1:502)                                      │
│                                                                    │
│                                    ┌─────────────────────────┐   │
│                                    │ FingerprintSimulator     │   │
│                                    │ (模拟指纹模块行为)        │   │
│                                    │ 连接 127.0.0.1:503        │   │
│                                    └────────────┬────────────┘   │
│                                                 │                │
│                                    内部状态 + 事件触发             │
│                                                 │                │
│                                    ┌────────────▼────────────┐   │
│                                    │ FingerprintRecognition  │   │
│                                    │   Monitor (模拟)        │   │
│                                    └─────────────────────────┘   │
└────────────────────────────────────────────────────────────────────┘
```

## 2. 架构设计

### 2.1 整体架构

```
                    ┌─────────────────────────┐
                    │ FingerprintModuleHost   │
                    │  (统一入口)             │
                    └───────────┬─────────────┘
                                │
            ┌───────────────────┼───────────────────┐
            │                   │                   │
            ▼                   ▼                   ▼
  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
  │ PhysicalModule   │  │ LocalServer     │  │ RemoteServer    │
  │ (RS485 直连)     │  │ Module          │  │ Module          │
  │                  │  │ (本机 503 服务)  │  │ (连接 503 服务)  │
  └─────────────────┘  └─────────────────┘  └─────────────────┘
```

### 2.2 模块职责

| 模块 | 职责 |
|------|------|
| `FingerprintModuleHost` | 统一入口，根据配置决定使用哪种连接模式 |
| `PhysicalFingerprintModule` | 直连物理指纹模块（现有 `FingerprintModule`） |
| `LocalServerFingerprintModule` | 作为 Modbus TCP 服务端，将物理模块状态暴露到本地 503 端口 |
| `RemoteServerFingerprintModule` | 作为 Modbus TCP 客户端，连接到 503 端口模拟指纹行为 |
| `FingerprintSimulator` | 模拟指纹识别、录入、删除等操作，由 `RemoteServerFingerprintModule` 驱动 |

## 3. 寄存器映射（地址 30-40）

### 3.1 寄存器定义

| 地址 | 名称 | 访问 | 功能说明 |
|------|------|------|----------|
| 30 | `ModuleStatus` | 读 | 模块状态：1=录入，2=识别，3=交互操作 |
| 31 | `RecognitionResult` | 读 | 指纹识别结果：0=无结果，1-255=指纹ID，256+=失败码 |
| 32 | `MatchScore` | 读 | 匹配分数 |
| 33 | `TemplateCount` | 读 | 已录入指纹数量 |
| 34 | `LightRing` | 写 | 光环控制：高字节=模式，低字节=颜色 |
| 35 | `Command` | 写 | 命令字：1=启动录入，2=取消，3=删除，4=清空 |
| 36 | `CommandParam` | 写 | 命令参数（如指纹ID） |
| 37 | `CommandStatus` | 读 | 命令执行状态：0=空闲，1=执行中，2=成功，3=失败 |
| 38 | `CommandResult` | 读 | 命令执行结果（错误码等） |
| 39 | `Heartbeat` | 读写 | 心跳寄存器，写入任意值会原样读回，用于检测连接 |
| 40 | `Version` | 读 | 模块固件版本或协议版本，固定值 1 |

### 3.2 命令执行流程（地址 35-38）

**启动指纹录入：**

1. 写 `35 = 1`（启动录入）
2. 写 `36 = <指纹ID>`
3. 轮询 `37` 状态，直到变为 `2`（成功）或 `3`（失败）
4. 读取 `38` 获取结果

**删除指纹：**

1. 写 `35 = 3`（删除）
2. 写 `36 = <指纹ID>`
3. 轮询 `37` 状态

**清空指纹库：**

1. 写 `35 = 4`
2. 写 `36 = 0`（无意义参数）
3. 轮询 `37` 状态

## 4. 连接模式决策

### 4.1 模式分类

| 模式 | 枚举值 | 说明 |
|------|--------|------|
| 直连模式 | `Direct` | 直接连接物理指纹模块（默认） |
| 服务端模式 | `Server` | 作为服务端，连接物理模块并通过 503 端口共享 |
| 客户端模式 | `Client` | 作为客户端，连接 503 端口的服务端 |

### 4.2 模式决策逻辑

```
1. 尝试以 Server 模式启动（监听 127.0.0.1:503）
2. 如果成功 → 使用 LocalServerFingerprintModule 读取物理模块并写入寄存器
3. 如果失败（端口已占用）→ 使用 RemoteServerFingerprintModule 连接已有的服务端
```

### 4.3 配置扩展

`FingerprintModuleOptions` 增加以下字段：

```csharp
public enum FingerprintConnectionMode
{
    Direct,   // 直接连接物理模块
    Server,   // 作为服务端暴露到 503 端口
    Client    // 作为客户端连接 503 端口
}

public sealed class FingerprintModuleOptions
{
    // 新增字段
    public FingerprintConnectionMode ConnectionMode { get; set; } = FingerprintConnectionMode.Direct;
    public string LocalServerHost { get; set; } = "127.0.0.1";
    public int LocalServerPort { get; set; } = 503;
    // 保留原有物理连接字段
}
```

### 4.4 界面配置

在系统设置界面中，指纹模块配置增加"连接模式"下拉框：

- **直连**（默认）：直接连接 COM 端口
- **服务端**：连接到 COM 端口，并通过本地 503 端口共享
- **客户端**：连接到 503 端口，由服务端实例提供指纹数据

## 5. 客户端模式行为

当实例以客户端模式运行时，`FingerprintRecognitionMonitor` 改为使用 `RemoteServerFingerprintModule`。

### 5.1 轮询模拟

`RemoteServerFingerprintModule` 每隔 `PollIntervalMs` 读取一次 `31`（RecognitionResult）寄存器，实现与直连模式相同的行为：

- 读到非零值 → 触发 `Recognized` 事件
- `DuplicateSuppressMs` 同样生效，防止重复触发

### 5.2 录入模拟

`RemoteServerFingerprintModule.StartEnrollmentAsync` 通过写入寄存器 `35`、`36` 触发服务端的录入流程：

1. 写入 `35 = 1`，`36 = <指纹ID>`
2. 轮询 `37` 直到完成
3. 返回成功/失败

服务端收到命令后，驱动 `FingerprintEnrollmentService` 执行实际录入，并通过寄存器返回结果。

## 6. 服务端模式实现

### 6.1 LocalServerFingerprintModule

```csharp
public sealed class LocalServerFingerprintModule : IFingerprintModule
{
    private readonly IFingerprintModule _physicalModule;
    private readonly ModbusHoldingRegisterBank _registers;
    private readonly ModbusTcpServerHost _server;

    public LocalServerFingerprintModule(
        IFingerprintModule physicalModule,
        ModbusHoldingRegisterBank registers,
        ModbusTcpServerHost server)
    {
        _physicalModule = physicalModule;
        _registers = registers;
        _server = server;
    }

    // 每次物理模块状态变化时，更新寄存器
    // RecognitionResult → 寄存器 31
    // MatchScore → 寄存器 32
    // TemplateCount → 寄存器 33
    // ModuleStatus → 寄存器 30
}
```

### 6.2 服务启动流程

```csharp
public ModbusTcpServerRuntimeStatus StartServer(ModbusTcpServerOptions options)
{
    // 尝试监听 127.0.0.1:503
    return _server.Start(new ModbusTcpServerOptions
    {
        Enabled = true,
        BindAddress = "127.0.0.1",
        Port = 503,
        UnitId = 1
    });
}
```

## 7. 数据流

### 7.1 服务端实例数据流

```
物理指纹模块
    │
    ▼ (RS485/COM)
FingerprintModule
    │
    ▼ ReadRecognitionResultAsync()
FingerprintRecognitionMonitor (轮询循环)
    │
    ▼ 读取结果
FingerprintRecognitionHost
    │
    ├─→ FingerprintPersonnelRecognition 事件（正常业务逻辑）
    │
    └─→ LocalServerFingerprintModule 更新寄存器
            │
            ▼ 写入 ModbusHoldingRegisterBank (地址 30-40)
            │
            ▼ ModbusTcpServerHost 监听 127.0.0.1:503
            │
            ▼ Modbus TCP 响应客户端读取请求
```

### 7.2 客户端实例数据流

```
RemoteServerFingerprintModule
    │
    ▼ ReadHoldingRegistersAsync(31) 轮询
    │
    ▼ Modbus TCP 请求 127.0.0.1:503
    │
    ├─ 收到 RecognitionResult = 0 → 继续轮询
    │
    └─ 收到 RecognitionResult = N → 触发 Recognized 事件
            │
            ▼
    FingerprintRecognitionHost
            │
            ▼ FingerprintPersonnelRecognition 事件（正常业务逻辑）
```

## 8. 时序图

### 8.1 服务端启动成功

```
实例A(工位二)              物理模块              ModbusTcpServer (:503)
     │                        │                        │
     │  Start()               │                        │
     │────────────────────────►│                        │
     │                        │                        │
     │  连接成功               │  StartServer()         │
     │◄────────────────────────│                        │
     │                        │────────────────────────►│
     │                        │                        │ 监听成功
     │                        │                        │◄────────────────────
     │                        │                        │
     │  轮询读取结果           │                        │
     │────────────────────────►│                        │
     │                        │                        │
     │  识别结果               │                        │
     │◄────────────────────────│                        │
     │                        │                        │
     │  更新寄存器 31=N        │                        │
     │───────────────────────────────────────────────────►│
     │                        │                        │
```

### 8.2 客户端启动（服务端已存在）

```
实例B(原始)           ModbusTcpServer (:503)        物理模块（由实例A代理）
     │                        │                        │
     │  StartServer()         │                        │
     │────────────────────────►│                        │
     │  端口已占用             │                        │
     │◄────────────────────────│                        │
     │                        │                        │
     │  StartClient()         │                        │
     │────────────────────────►│                        │
     │                        │                        │
     │  连接成功               │                        │
     │◄────────────────────────│                        │
     │                        │                        │
     │  轮询寄存器 31          │                        │
     │────────────────────────►│                        │
     │  31=N                  │  读取寄存器              │
     │◄────────────────────────│◄────────────────────────│
     │                        │                        │
     │  触发 Recognized        │                        │
     │◄────────────────────────│                        │
```

## 9. 关键类设计

### 9.1 FingerprintModuleHost

```csharp
public sealed class FingerprintModuleHost
{
    public static FingerprintModuleHost Create(FingerprintModuleOptions options);

    public IFingerprintModule Module { get; }
    public FingerprintConnectionMode Mode { get; }
    public bool IsServer { get; }
}
```

### 9.2 RemoteServerFingerprintModule

```csharp
public sealed class RemoteServerFingerprintModule : IFingerprintModule
{
    public RemoteServerFingerprintModule(
        string host,
        int port,
        int readTimeoutMs = 1000,
        int writeTimeoutMs = 1000);

    public Task<FingerprintRecognitionResult> ReadRecognitionResultAsync(CancellationToken ct);
    public Task StartEnrollmentAsync(byte fingerprintId, CancellationToken ct);
    public Task CancelAsync(CancellationToken ct);
    public Task DeleteFingerprintAsync(byte fingerprintId, CancellationToken ct);
    public Task ClearDatabaseAsync(CancellationToken ct);
    public Task<int> ReadTemplateCountAsync(CancellationToken ct);
    public Task<FingerprintModuleStatus> ReadStatusAsync(CancellationToken ct);
    public Task SetLightAsync(FingerprintLightMode mode, FingerprintLightColor color, CancellationToken ct);
    public void Dispose();
}
```

### 9.3 FingerprintCommandExecutor（服务端用）

```csharp
public sealed class FingerprintCommandExecutor
{
    public FingerprintCommandExecutor(
        IFingerprintModule physicalModule,
        ModbusHoldingRegisterBank registers);

    // 启动后台任务，监听寄存器 35-36，执行命令并更新 37-38
    public void Start(CancellationToken ct);
}
```

## 10. 配置示例

### 10.1 工位二（服务端模式）

```json
{
  "FingerprintModules": [
    {
      "Id": "fingerprint-1",
      "Name": "指纹模块1",
      "ConnectionMode": "Server",
      "ConnectionKind": "SerialRtu",
      "PortName": "COM3",
      "BaudRate": 9600,
      "DataBits": 8,
      "Parity": "None",
      "StopBits": 1,
      "SlaveAddress": 1,
      "PollIntervalMs": 200,
      "DuplicateSuppressMs": 3000,
      "Enabled": true
    }
  ]
}
```

### 10.2 原始实例（客户端模式）

```json
{
  "FingerprintModules": [
    {
      "Id": "fingerprint-1",
      "Name": "指纹模块1",
      "ConnectionMode": "Client",
      "LocalServerHost": "127.0.0.1",
      "LocalServerPort": 503,
      "SlaveAddress": 1,
      "PollIntervalMs": 200,
      "DuplicateSuppressMs": 3000,
      "Enabled": true
    }
  ]
}
```

## 11. 异常处理

| 场景 | 服务端处理 | 客户端处理 |
|------|-----------|-----------|
| 物理模块断开 | 重连机制，记录错误日志 | 不感知（服务端处理） |
| 客户端连接断开 | 继续正常业务，不受影响 | 尝试重连，指数退避 |
| 服务端关闭 | N/A | 自动降级为客户端重连模式 |
| 命令执行超时 | 更新寄存器 37=3, 38=错误码 | 抛出 `TimeoutException` |
| 寄存器地址越界 | Modbus TCP 层返回异常 | NModbus 库处理 |

## 12. 实现计划

### Phase 1：服务端模式（工位二）

1. 扩展 `FingerprintModuleOptions` 增加 `ConnectionMode` 字段
2. 实现 `LocalServerFingerprintModule`，将物理模块状态同步到寄存器 30-33
3. 实现 `FingerprintCommandExecutor`，监听寄存器 35-36 执行命令
4. 修改 `FingerprintRuntimeModuleSelector`，根据 `ConnectionMode` 选择实现类
5. 启动时尝试监听 503 端口，失败则降级

### Phase 2：客户端模式（原始实例）

1. 实现 `RemoteServerFingerprintModule`，连接 503 端口
2. 将轮询逻辑从 `FingerprintRecognitionMonitor` 中抽象到 `IFingerprintModule` 接口
3. 修改 `FingerprintRuntimeModuleSelector`，支持 `Client` 模式
4. 实现 `FingerprintCommandExecutor` 客户端部分，写入寄存器 35-36

### Phase 3：界面集成

1. 在系统设置 UI 中增加"连接模式"下拉框
2. 根据连接模式显示/隐藏相关参数（COM 端口 vs 服务器地址）
3. 运行时状态指示（已连接/模拟模式）

### Phase 4：测试

1. 服务端实例正常识别指纹，客户端实例同步收到
2. 服务端关闭后客户端自动重连
3. 两端同时录入/删除操作隔离
4. 重复抑制机制在客户端同样生效
