# PynectDirect SDK 用户使用手册

**版本：v3.0.1**

> 适用对象：使用 PynectDirect 动态库进行二次开发的用户
> 适用场景：入门级学生、实验室研究人员、企业软件开发人员
> 运行环境：64 位 Windows7/10/11、64 位 Python 3（如使用 Python 集成）
> 配套文件：`bin/PynectDirect.dll`、`bin/ftd2xx.dll`、`include/pynect_direct.h`

---

## 目录

1. [SDK 简介](#1-sdk-简介)
2. [快速开始](#2-快速开始)
3. [基础概念](#3-基础概念)
4. [通用数据类型与常量](#4-通用数据类型与常量)
5. [C 语言例程](#5-c-语言例程)
6. [C++ 语言例程](#6-c-语言例程)
7. [C# 语言例程](#7-c-语言例程)
8. [Python 语言例程](#8-python-语言例程)
9. [完整 API 参考](#9-完整-api-参考)
10. [典型应用流程](#10-典型应用流程)
11. [常见问题（FAQ）](#11-常见问题faq)
12. [错误码与故障排查](#12-错误码与故障排查)

---

## 1. SDK 简介

PynectDirect 是**谱研互联**（深圳谱研互联科技有限公司）光谱仪的 Windows 动态库 SDK，仅供本公司光谱仪使用，**不支持其他厂家的光谱仪**。您只需要两件事就可以开始开发：

- **动态库**：`PynectDirect.dll`（SDK 本体）+ `ftd2xx.dll`（FTDI USB 驱动，必须与 SDK 同目录）
- **头文件**：`pynect_direct.h`（C 语言函数声明，是所有语言的签名权威来源）

SDK 通过 USB 直接与光谱仪通信，提供 58 个公开函数，覆盖：设备发现与打开（支持同时打开多台）、参数设置（积分时间、平均次数、氙灯、触发）、波长与光谱数据采集、设备状态读取、Flash 与固化配置等功能。

### 1.1 支持设备

| 设备类型 | 说明 |
| --- | --- |
| SPEC-CDS350、SPEC-CMS960 | 紫外可见光谱仪 | 190-1100nm  |
| NIR-F950 | 近红外光谱仪 | 900-1700nm |

打开设备后 SDK 自动识别设备类型。基础采谱流程两类设备相同；氙灯、外触发、DAC、固化配置等高级功能仅特定设备支持，调用前应查询设备能力（见 4.3 节）。

### 1.2 语言支持

SDK 导出标准 C ABI（`extern "C"`、`__cdecl`），因此任何支持 C ABI 的语言都可以直接调用：

| 语言 | 集成方式 | 例程位置 |
| --- | --- | --- |
| C | 链接 `lib/libPynectDirect.dll.a`（或静态库） | 第 5 章 |
| C++ | 直接包含 `pynect_direct.h`（自动 `extern "C"`） | 第 6 章 |
| C# | `DllImport("PynectDirect.dll")` P/Invoke | 第 7 章 |
| Python | `ctypes` 加载 DLL | 第 8 章 |
| 其他 | 任何支持 C ABI 的语言（Rust/Go/Delphi 等） | 按头文件签名声明即可 |

### 1.3 数据流模型（重要）

- **设备每次传输全量像素**：一次读取返回所有像素点的波长或强度（例如 2048 点）。
- **波长范围来自全量数据**：`pynect_direct_get_full_wavelength_range` 返回完整波长数组的第一个和最后一个点（即全部像素的最小/最大波长），该值必然正确。
- **指定波段在上位机截取**：`pynect_direct_set_selected_wavelength_range` 只在上位机记录一个像素窗口，`get_selected_*` 系列接口从全量数据中截取窗口内容返回，**不会修改设备内置输出范围**。

因此，切换显示波段只需在上位机完成，无需与设备交互，快速且不影响其他程序对设备的访问。

---

## 2. 快速开始

### 2.1 文件布局

推荐把 SDK 文件整理为如下结构（以 Python 为例，其他语言同理）：

```text
MyProject/
|-- 您的代码文件（xxx.py / xxx.c / xxx.cpp / xxx.cs / ...）
`-- dll/
    |-- PynectDirect.dll
    `-- ftd2xx.dll
```

**`PynectDirect.dll` 和 `ftd2xx.dll` 必须放在同一个文件夹中。**

- C/C++：将 `include` 目录加入头文件搜索路径，运行时 DLL 位于程序目录或系统 PATH。
- C#：DLL 放在程序输出目录（exe 旁），或使用绝对路径。
- Python：在脚本中用 `os.add_dll_directory()` 将 `dll` 文件夹加入搜索路径后再 `ctypes.CDLL` 加载。

### 2.2 环境要求

| 项目 | 要求 |
| --- | --- |
| 操作系统 | 64 位 Windows |
| 语言运行时 | 均为 64 位（32 位进程无法加载 64 位 DLL） |
| C/C++ 编译 | MinGW-w64 GCC 或 MSVC（64 位） |
| C# | .NET Framework 4.x 或 .NET 6+（x64 目标平台） |
| Python | 64 位 Python 3 |

### 2.3 验证运行环境

**Python 快速验证（无需连接设备）：**

```powershell
python -c "import ctypes; d=ctypes.CDLL(r'dll\PynectDirect.dll'); print('load ok, api =', d.pynect_direct_scan_devices())"
```

能打印 `load ok, api = 0` 或设备数量即说明 DLL 加载成功。

**完整硬件验证（需连接设备）：**

```powershell
python .\examples\python\minimal_test.py --read-only
```

应看到 `FAIL=0`。只读模式不会修改设备任何参数。

### 2.4 连接设备

1. 将光谱仪通过 USB 连接到电脑。
2. 等待 Windows 完成驱动安装（FTDI 驱动需已安装）。
3. 关闭其他正在使用该光谱仪的软件（设备为独占访问）。
4. 运行自己的程序。

---

## 3. 基础概念

### 3.1 调用顺序

所有程序的调用顺序基本相同：

```
1. pynect_direct_scan_devices()        扫描设备，确认存在
2. (可选) get_chip_serial_by_index()   查看各设备芯片序列号
3. pynect_direct_open_device(0)        打开设备（或按序列号打开）
4. get_device_info() / get_capabilities()  读取设备信息与能力
5. get_wavelength_count()              获取数据点数 N
6. get_full_wavelength_data()          获取波长坐标（double 数组，N 个）
7. get_full_spectrum_data()            采集强度（uint16 数组，N 个）
8. (可选) set_selected_wavelength_range() 设置显示波段，然后 get_selected_*
9. pynect_direct_close_device()        关闭设备
```

### 3.2 返回值约定

- 绝大多数函数**成功返回 `0`**，失败返回负错误码（见第 12 章）。
- 例外：
  - `pynect_direct_scan_devices`：返回设备数量，`0` 表示没有设备（不是错误）。
  - `pynect_direct_flash_read` / `pynect_direct_external_spectrum_control`：成功时返回实际读取字节数。
  - `pynect_direct_is_open`：返回 `1`（打开）/ `0`（关闭）。
  - `pynect_direct_close_device`：无返回值（`void`）。

### 3.3 数组容量约定

- 波长数组元素类型：**double**（8 字节）
- 强度数组元素类型：**uint16**（2 字节）
- 数组长度：调用 `pynect_direct_get_wavelength_count` 得到 N，然后用 N 分配数组，把 N 作为 `max_len` 传入。
- 字符串缓冲区：建议分配 **64 字节**，`max_len` 包含结尾的 `\0` 空间。

### 3.4 会话规则

- **支持同时打开多台设备**（最多 `PYNECT_DIRECT_MAX_DEVICES` = 4 台）。`pynect_direct_open_device` 打开新设备时**不会关闭**已打开的设备。
- SDK 采用"当前设备"模型：所有参数和数据 API 操作的是**当前选中的设备**。
  - `pynect_direct_open_device(0)` → 打开设备 0，并设为当前设备
  - `pynect_direct_open_device(1)` → 打开设备 1，并设为当前设备（设备 0 保持打开）
  - `pynect_direct_select_device(0)` → 切换当前设备为设备 0
  - `pynect_direct_get_open_device_count()` → 当前已打开设备数
  - `pynect_direct_close_device()` → 关闭当前选中的设备
  - `pynect_direct_close_all_devices()` → 关闭全部设备
- 重复打开同一台设备（同芯片序列号）不会重复建句柄，只切换为当前设备。
- 每台设备有**独立的状态**：协议、能力、参数（积分时间、平均次数）、选定波段窗口互不影响。
- 当前会话参数（积分时间、平均次数等）在关闭/复位/重新上电后不保留。
- 固化配置（`stored_*` 系列）是设备的**非易失参数**，写入后掉电不丢失，会影响以后上电默认值，请谨慎使用。
- 设备复位（`reset_device`）后，该设备已设置的选定波段窗口失效，需要重新设置。

---

## 4. 通用数据类型与常量

### 4.1 错误码

| 宏 | 值 | 含义 |
| --- | ---: | --- |
| `PYNECT_DIRECT_SUCCESS` | 0 | 成功 |
| `PYNECT_DIRECT_ERR_NOT_INITIALIZED` | -1 | 未初始化（如未设置选定波段就读取） |
| `PYNECT_DIRECT_ERR_DEVICE_NOT_FOUND` | -2 | 未发现设备或索引越界 |
| `PYNECT_DIRECT_ERR_DEVICE_NOT_OPEN` | -3 | 设备未打开 |
| `PYNECT_DIRECT_ERR_COMM_FAIL` | -4 | 通信失败 |
| `PYNECT_DIRECT_ERR_INVALID_PARAM` | -5 | 参数无效 |
| `PYNECT_DIRECT_ERR_OPEN_FAILED` | -6 | 打开设备失败（被占用/驱动异常） |
| `PYNECT_DIRECT_ERR_CRC` | -7 | 响应 CRC 校验失败 |
| `PYNECT_DIRECT_ERR_PREAMBLE` | -8 | 响应帧头不匹配 |
| `PYNECT_DIRECT_ERR_TIMEOUT` | -9 | 设备响应超时 |
| `PYNECT_DIRECT_ERR_WPROT` | -10 | 写保护错误 |
| `PYNECT_DIRECT_ERR_UNSUPPORTED` | -11 | 当前设备不支持该功能 |

### 4.2 协议枚举

```c
typedef enum PynectDirectDeviceProtocol {
    PYNECT_DIRECT_PROTOCOL_AUTO = 0,        // 自动识别（默认推荐）
    PYNECT_DIRECT_PROTOCOL_UVVIS_CM2 = 1,   // UV/VIS
    PYNECT_DIRECT_PROTOCOL_NIR_LEGACY = 2,  // NIR
    PYNECT_DIRECT_PROTOCOL_UNKNOWN = 255
} PynectDirectDeviceProtocol;
```

### 4.3 能力位（capabilities）

`pynect_direct_get_capabilities` 返回 32 位掩码，判断设备支持哪些功能：

| 位 | 宏 | 功能 |
| --- | --- | --- |
| 0x00000001 | `PYNECT_DIRECT_CAP_INTEGRATION_TIME` | 积分时间 |
| 0x00000002 | `PYNECT_DIRECT_CAP_AVERAGE_NUMBER` | 平均次数 |
| 0x00000004 | `PYNECT_DIRECT_CAP_LEVEL_OUTPUT` | 电平输出 |
| 0x00000008 | `PYNECT_DIRECT_CAP_FLASH` | Flash 读写 |
| 0x00000010 | `PYNECT_DIRECT_CAP_WAVELENGTH_DATA` | 波长数据 |
| 0x00000020 | `PYNECT_DIRECT_CAP_SPECTRUM_DATA` | 光谱数据 |
| 0x00000040 | `PYNECT_DIRECT_CAP_TEMPERATURE` | 温度 |
| 0x00000080 | `PYNECT_DIRECT_CAP_HARDWARE_VERSION` | 硬件版本 |
| 0x00000100 | `PYNECT_DIRECT_CAP_CALIBRATION` | 标定系数 |
| 0x00000200 | `PYNECT_DIRECT_CAP_XENON` | 氙灯 |
| 0x00000400 | `PYNECT_DIRECT_CAP_USB_STATUS` | USB 状态 |
| 0x00000800 | `PYNECT_DIRECT_CAP_EXTERNAL_TRIGGER` | 外触发 |
| 0x00001000 | `PYNECT_DIRECT_CAP_DAC` | DAC |
| 0x00002000 | `PYNECT_DIRECT_CAP_STORED_CONFIG` | 固化配置 |
| 0x00004000 | `PYNECT_DIRECT_CAP_SLIT_WIDTH` | 狭缝宽度 |

### 4.4 常用常量

```c
// 氙灯模式
#define XENON_OFF         0x00   // 关闭
#define XENON_CONTINUOUS  0x01   // 连续
#define XENON_SINGLE      0x81   // 单次

// 外触发类型
#define TRIGGER_OFF       0x00   // 关闭
#define TRIGGER_ON_RISE   0xAA   // 上升沿触发
#define TRIGGER_ON_LEVEL  0xBB   // 电平触发
```

### 4.5 数据结构

**设备信息：**

```c
typedef struct PynectDirectDeviceInfo {
    PynectDirectDeviceProtocol protocol;   // 实际协议类型
    uint32_t capabilities;                 // 能力位
    char protocol_name[32];                // 协议名称字符串
    char model[32];                        // 型号字符串
    char serial_number[32];                // 业务序列号字符串
    char hardware_version[64];             // 硬件版本字符串
} PynectDirectDeviceInfo;
```

**选定波段信息（`get_selected_wavelength_range` 返回）：**

```c
typedef struct PynectDirectSelectedWavelengthRange {
    double   request_start_nm;   // 请求的起始波长（nm）
    double   request_end_nm;     // 请求的终止波长（nm）
    double   actual_start_nm;    // 实际截取的起始波长（对应像素的真实波长）
    double   actual_end_nm;      // 实际截取的终止波长
    uint16_t start_pixel;        // 起始像素索引（从 0 开始）
    uint16_t end_pixel;          // 终止像素索引
    uint16_t count;              // 窗口内像素点数
} PynectDirectSelectedWavelengthRange;
```

---

## 5. C 语言例程

### 5.1 编译与运行

```powershell
# 进入例程目录（依赖的 DLL、头文件、库均已放入该目录）
cd examples/c

# 方式一：直接运行（已预编译，无需安装任何开发环境）
spectrum_demo.exe

# 方式二：自行编译（MinGW-w64，64 位）
gcc spectrum_demo.c -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe
```

> 程序运行结束后会暂停并提示"请按任意键继续"，按任意键即可退出窗口，方便查看结果（源码中为 `system("pause")`）。

> 使用 MSVC 时：`cl spectrum_demo.c /Iinclude /link /LIBPATH:lib`（需 `.lib` 导入库，可联系厂商获取；GCC 环境使用 `libPynectDirect.dll.a`）。
>
> 完整源码见交付包 `examples/c/spectrum_demo.c`（即 5.2 节内容）。

### 5.2 完整源码（spectrum_demo.c）

```c
#define PYNECT_DIRECT_USE_DLL      /* 使用动态库时定义 */
#include "pynect_direct.h"
#include <stdio.h>
#include <stdlib.h>

static int require(int code, const char* what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        printf("[FAIL] %s: return code = %d\n", what, code);
        return 0;
    }
    return 1;
}

int main(void)
{
    PynectDirectDeviceInfo info;
    PynectDirectSelectedWavelengthRange sel;
    uint16_t count = 0, i;
    double* wavelengths = NULL;
    uint16_t* spectrum = NULL;
    double start_nm = 0.0, end_nm = 0.0;
    unsigned int integration = 0;
    int n;

    /* 1. 扫描设备 */
    n = pynect_direct_scan_devices();
    if (n <= 0) {
        printf("No device found.\n");
        return 1;
    }
    printf("Found %d device(s).\n", n);

    /* 2. 打开设备（索引 0） */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. 读取设备信息 */
    if (require(pynect_direct_get_device_info(&info), "get_device_info")) {
        printf("Protocol: %s\n", info.protocol_name);
        printf("Model:    %s\n", info.model);
        printf("Serial:   %s\n", info.serial_number);
        printf("Hardware: %s\n", info.hardware_version);
    }

    /* 4. 数据点数 */
    if (!require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count")) goto done;
    printf("Wavelength count: %u\n", count);
    if (count == 0) goto done;

    /* 5. 波长范围（全量波长数组首尾，必然正确） */
    if (!require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm), "get_full_wavelength_range")) goto done;
    printf("Wavelength range: %.2f ~ %.2f nm\n", start_nm, end_nm);

    /* 6. 分配数组 */
    wavelengths = (double*)malloc(count * sizeof(double));
    spectrum = (uint16_t*)malloc(count * sizeof(uint16_t));
    if (!wavelengths || !spectrum) {
        printf("Out of memory.\n");
        goto done;
    }

    /* 7. 读取波长坐标 */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. 设置并读取积分时间（临时，不写入固化配置） */
    if (require(pynect_direct_get_integration_time(&integration), "get_integration_time"))
        printf("Current integration time: %u us\n", integration);
    pynect_direct_set_integration_time(50000);   /* 50 ms */

    /* 9. 采集光谱 */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. 两列打印：波长 + 对应强度（前 5 点 + 后 5 点） */
    printf("%6s  %13s  %9s\n", "Index", "Wavelength(nm)", "Intensity");
    printf("%6s  %13s  %9s\n", "-----", "--------------", "---------");
    for (i = 0; i < 5 && i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);
    if (count > 10) printf("...  (total %u points)\n", count);
    for (i = count > 5 ? count - 5 : 0; i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);

    /* 11. 指定波段（上位机截取，不修改设备） */
    if (start_nm < 400.0 && end_nm > 800.0) {
        if (require(pynect_direct_set_selected_wavelength_range(400.0, 800.0), "set_selected_range")) {
            if (require(pynect_direct_get_selected_wavelength_range(&sel), "get_selected_range")) {
                printf("Selected: pixels %u..%u, count=%u, actual %.2f..%.2f nm\n",
                       sel.start_pixel, sel.end_pixel, sel.count,
                       sel.actual_start_nm, sel.actual_end_nm);
            }
        }
    } else {
        printf("Device does not cover 400-800 nm, skip selected range.\n");
    }

    /* 12. 关闭设备 */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    return 0;
}
#define PYNECT_DIRECT_USE_DLL      /* 使用动态库时定义 */
#include "pynect_direct.h"
#include <stdio.h>
#include <stdlib.h>

static int require(int code, const char* what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        printf("[FAIL] %s: return code = %d\n", what, code);
        return 0;
    }
    return 1;
}

int main(void)
{
    PynectDirectDeviceInfo info;
    PynectDirectSelectedWavelengthRange sel;
    uint16_t count = 0, i;
    double* wavelengths = NULL;
    uint16_t* spectrum = NULL;
    double start_nm = 0.0, end_nm = 0.0;
    unsigned int integration = 0;
    int n;

    /* 1. 扫描设备 */
    n = pynect_direct_scan_devices();
    if (n <= 0) {
        printf("No device found.\n");
        system("pause");
        return 1;
    }
    printf("Found %d device(s).\n", n);

    /* 2. 打开设备（索引 0） */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. 读取设备信息 */
    if (require(pynect_direct_get_device_info(&info), "get_device_info")) {
        printf("Protocol: %s\n", info.protocol_name);
        printf("Model:    %s\n", info.model);
        printf("Serial:   %s\n", info.serial_number);
        printf("Hardware: %s\n", info.hardware_version);
    }

    /* 4. 数据点数 */
    if (!require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count")) goto done;
    printf("Wavelength count: %u\n", count);
    if (count == 0) goto done;

    /* 5. 波长范围（全量波长数组首尾，必然正确） */
    if (!require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm), "get_full_wavelength_range")) goto done;
    printf("Wavelength range: %.2f ~ %.2f nm\n", start_nm, end_nm);

    /* 6. 分配数组 */
    wavelengths = (double*)malloc(count * sizeof(double));
    spectrum = (uint16_t*)malloc(count * sizeof(uint16_t));
    if (!wavelengths || !spectrum) {
        printf("Out of memory.\n");
        goto done;
    }

    /* 7. 读取波长坐标 */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. 设置并读取积分时间（临时，不写入固化配置） */
    if (require(pynect_direct_get_integration_time(&integration), "get_integration_time"))
        printf("Current integration time: %u us\n", integration);
    pynect_direct_set_integration_time(50000);   /* 50 ms */

    /* 9. 采集光谱 */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. 两列打印：波长 + 对应强度（前 5 点 + 后 5 点） */
    printf("%6s  %13s  %9s\n", "Index", "Wavelength(nm)", "Intensity");
    printf("%6s  %13s  %9s\n", "-----", "--------------", "---------");
    for (i = 0; i < 5 && i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);
    if (count > 10) printf("...  (total %u points)\n", count);
    for (i = count > 5 ? count - 5 : 0; i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);

    /* 11. 指定波段（上位机截取，不修改设备） */
    if (start_nm < 400.0 && end_nm > 800.0) {
        if (require(pynect_direct_set_selected_wavelength_range(400.0, 800.0), "set_selected_range")) {
            if (require(pynect_direct_get_selected_wavelength_range(&sel), "get_selected_range")) {
                printf("Selected: pixels %u..%u, count=%u, actual %.2f..%.2f nm\n",
                       sel.start_pixel, sel.end_pixel, sel.count,
                       sel.actual_start_nm, sel.actual_end_nm);
            }
        }
    } else {
        printf("Device does not cover 400-800 nm, skip selected range.\n");
    }

    /* 12. 关闭设备 */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    system("pause");
    return 0;
}
```

### 5.3 输出示例

```text
Found 1 device(s).
Protocol: UVVIS_CM2
Model:    PYNECT-UV-2048
Serial:   SP123456
Hardware: 1.0
Wavelength count: 2048
Wavelength range: 199.04 ~ 1019.50 nm
Current integration time: 10000 us
Index  Wavelength(nm)  Intensity
-----  --------------  ---------
     0          199.04        128
     1          200.08        130
     2          201.12        127
     3          202.16        125
     4          203.20        131
...  (total 2048 points)
  2043         1018.46        122
  2044         1019.50        121
  2045         1020.54        119
  2046         1021.58        124
  2047         1022.62        126
Selected: pixels 808..1616, count=809, actual 400.00..800.00 nm
Done.
请按任意键继续. . .
```

---

## 6. C++ 语言例程

### 6.1 编译与运行

```powershell
cd examples/cpp

# 方式一：直接运行（已预编译）
spectrum_demo.exe

# 方式二：自行编译
g++ spectrum_demo.cpp -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe
```

> 程序运行结束后会暂停并提示"请按任意键继续"，按任意键退出窗口（源码中为 `system("pause")`）。

头文件已包含 `extern "C"`，C++ 直接 `#include "pynect_direct.h"` 即可。

完整源码见交付包 `examples/cpp/spectrum_demo.cpp`（即 6.2 节内容）。

### 6.2 完整源码（spectrum_demo.cpp）

```cpp
#define PYNECT_DIRECT_USE_DLL
#include "pynect_direct.h"
#include <iomanip>
#include <iostream>
#include <vector>
#include <string>

static void require(int code, const std::string& what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        std::cerr << "[FAIL] " << what << ": return code = " << code << std::endl;
        throw std::runtime_error(what);
    }
}

int main()
{
    try {
        /* 1. 扫描设备 */
        int n = pynect_direct_scan_devices();
        if (n <= 0) {
            std::cout << "No device found." << std::endl;
            return 1;
        }
        std::cout << "Found " << n << " device(s)." << std::endl;

        /* 2. 打开设备 */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. 设备信息 */
        PynectDirectDeviceInfo info{};
        require(pynect_direct_get_device_info(&info), "get_device_info");
        std::cout << "Model:   " << info.model << std::endl
                  << "Serial:  " << info.serial_number << std::endl
                  << "Hardware:" << info.hardware_version << std::endl;

        /* 4. 数据点数 */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. 波长范围 */
        double start_nm = 0.0, end_nm = 0.0;
        require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm),
                "get_full_wavelength_range");
        std::cout << "Wavelength range: " << start_nm << " ~ " << end_nm << " nm" << std::endl;

        /* 6. 读取波长坐标 */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. 设置积分时间并采集光谱 */
        pynect_direct_set_integration_time(50000);   /* 50 ms，临时设置 */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. 两列打印：波长 + 对应强度（前 5 点 + 后 5 点） */
        std::cout << std::setw(6) << "Index" << std::setw(15) << "Wavelength(nm)"
                  << std::setw(11) << "Intensity" << std::endl;
        std::cout << std::setw(6) << "-----" << std::setw(15) << "--------------"
                  << std::setw(11) << "---------" << std::endl;
        for (int i = 0; i < 5; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;
        if (count > 10)
            std::cout << "...  (total " << count << " points)" << std::endl;
        for (int i = count - 5; i < count; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;

        /* 9. 指定波段（上位机截取） */
        if (start_nm < 400.0 && end_nm > 800.0) {
            require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range");
            PynectDirectSelectedWavelengthRange sel{};
            require(pynect_direct_get_selected_wavelength_range(&sel),
                    "get_selected_range");
            std::cout << "Selected: pixels " << sel.start_pixel << ".."
                      << sel.end_pixel << ", count=" << sel.count << std::endl;
        }

        /* 10. 关闭设备 */
        pynect_direct_close_device();
        std::cout << "Done." << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}
#define PYNECT_DIRECT_USE_DLL
#include "pynect_direct.h"
#include <iomanip>
#include <iostream>
#include <vector>
#include <string>

static void require(int code, const std::string& what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        std::cerr << "[FAIL] " << what << ": return code = " << code << std::endl;
        throw std::runtime_error(what);
    }
}

int main()
{
    try {
        /* 1. 扫描设备 */
        int n = pynect_direct_scan_devices();
        if (n <= 0) {
            std::cout << "No device found." << std::endl;
            system("pause");
            return 1;
        }
        std::cout << "Found " << n << " device(s)." << std::endl;

        /* 2. 打开设备 */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. 设备信息 */
        PynectDirectDeviceInfo info{};
        require(pynect_direct_get_device_info(&info), "get_device_info");
        std::cout << "Model:   " << info.model << std::endl
                  << "Serial:  " << info.serial_number << std::endl
                  << "Hardware:" << info.hardware_version << std::endl;

        /* 4. 数据点数 */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. 波长范围 */
        double start_nm = 0.0, end_nm = 0.0;
        require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm),
                "get_full_wavelength_range");
        std::cout << "Wavelength range: " << start_nm << " ~ " << end_nm << " nm" << std::endl;

        /* 6. 读取波长坐标 */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. 设置积分时间并采集光谱 */
        pynect_direct_set_integration_time(50000);   /* 50 ms，临时设置 */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. 两列打印：波长 + 对应强度（前 5 点 + 后 5 点） */
        std::cout << std::setw(6) << "Index" << std::setw(15) << "Wavelength(nm)"
                  << std::setw(11) << "Intensity" << std::endl;
        std::cout << std::setw(6) << "-----" << std::setw(15) << "--------------"
                  << std::setw(11) << "---------" << std::endl;
        for (int i = 0; i < 5; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;
        if (count > 10)
            std::cout << "...  (total " << count << " points)" << std::endl;
        for (int i = count - 5; i < count; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;

        /* 9. 指定波段（上位机截取） */
        if (start_nm < 400.0 && end_nm > 800.0) {
            require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range");
            PynectDirectSelectedWavelengthRange sel{};
            require(pynect_direct_get_selected_wavelength_range(&sel),
                    "get_selected_range");
            std::cout << "Selected: pixels " << sel.start_pixel << ".."
                      << sel.end_pixel << ", count=" << sel.count << std::endl;
        }

        /* 10. 关闭设备 */
        pynect_direct_close_device();
        std::cout << "Done." << std::endl;
        system("pause");
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        system("pause");
        return 1;
    }
    return 0;
}
```

---

## 7. C# 语言例程

### 7.1 编译与运行

```powershell
cd examples/csharp

# 方式一：直接运行（已预编译，.NET Framework 4.x，Windows 自带运行时）
spectrum_demo.exe

# 方式二：自行编译（.NET Framework 自带编译器，64 位）
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /platform:x64 spectrum_demo.cs

# 方式三：.NET SDK
dotnet build -c Release
```

> 程序运行结束后会提示"按任意键退出..."，按任意键关闭窗口（源码中为 `WaitExit()`）。
>
> 完整源码见交付包 `examples/csharp/spectrum_demo.cs`（即 7.2 节内容）。示例使用 C# 5 兼容写法，.NET Framework 4.x 与 .NET 6+ 均可编译。

### 7.2 完整源码（spectrum_demo.cs）

```csharp
using System;
using System.Runtime.InteropServices;
using System.Text;

class PynectDirectDemo
{
    // ---------- 数据结构（与 pynect_direct.h 对应） ----------
    [StructLayout(LayoutKind.Sequential)]
    struct PynectDirectDeviceInfo
    {
        public int protocol;
        public uint capabilities;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string protocol_name;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string model;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string serial_number;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string hardware_version;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PynectDirectSelectedWavelengthRange
    {
        public double request_start_nm;
        public double request_end_nm;
        public double actual_start_nm;
        public double actual_end_nm;
        public ushort start_pixel;
        public ushort end_pixel;
        public ushort count;
    }

    // ---------- DllImport（全部为 cdecl 调用约定） ----------
    const string DLL = "PynectDirect.dll";
    const int SUCCESS = 0;

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_scan_devices();

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_open_device(int index);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern void pynect_direct_close_device();

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_device_info(ref PynectDirectDeviceInfo info);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_wavelength_count(ref ushort count);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_wavelength_range(ref double start, ref double end);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_wavelength_data(double[] buffer, int max_len);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_spectrum_data(ushort[] buffer, int max_len);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_set_integration_time(uint us);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_integration_time(ref uint us);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_set_selected_wavelength_range(double start, double end);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_selected_wavelength_range(
        ref PynectDirectSelectedWavelengthRange range);

    static void Require(int code, string what)
    {
        if (code != SUCCESS)
            throw new InvalidOperationException(string.Format(
                "{0} failed, code={1}", what, code));
    }

    static void WaitExit()
    {
        Console.Write("按任意键退出...");
        try { Console.ReadKey(); }
        catch { /* 非交互环境（输入被重定向）时直接退出 */ }
    }

    static void Main()
    {
        try
        {
            int n = pynect_direct_scan_devices();
            if (n <= 0) { Console.WriteLine("No device found."); WaitExit(); return; }
            Console.WriteLine(string.Format("Found {0} device(s).", n));

            Require(pynect_direct_open_device(0), "open_device");

            var info = new PynectDirectDeviceInfo();
            Require(pynect_direct_get_device_info(ref info), "get_device_info");
            Console.WriteLine(string.Format(
                "Model: {0}, Serial: {1}, HW: {2}",
                info.model, info.serial_number, info.hardware_version));

            ushort count = 0;
            Require(pynect_direct_get_wavelength_count(ref count), "get_wavelength_count");
            Console.WriteLine(string.Format("Wavelength count: {0}", count));

            double start = 0, end = 0;
            Require(pynect_direct_get_full_wavelength_range(ref start, ref end),
                    "get_full_wavelength_range");
            Console.WriteLine(string.Format(
                "Wavelength range: {0:F2} ~ {1:F2} nm", start, end));

            var wavelengths = new double[count];
            Require(pynect_direct_get_full_wavelength_data(wavelengths, count),
                    "get_full_wavelength_data");

            uint integration = 0;
            Require(pynect_direct_get_integration_time(ref integration), "get_integration_time");
            Console.WriteLine(string.Format("Current integration time: {0} us", integration));
            pynect_direct_set_integration_time(50000);

            var spectrum = new ushort[count];
            Require(pynect_direct_get_full_spectrum_data(spectrum, count),
                    "get_full_spectrum_data");

            Console.WriteLine(string.Format(
                "{0,-6} {1,13} {2,9}", "Index", "Wavelength(nm)", "Intensity"));
            Console.WriteLine(string.Format(
                "{0,-6} {1,13} {2,9}", "-----", "--------------", "---------"));
            for (int i = 0; i < 5 && i < count; i++)
                Console.WriteLine(string.Format(
                    "{0,-6} {1,13:F3} {2,9}", i, wavelengths[i], spectrum[i]));
            if (count > 10)
                Console.WriteLine(string.Format("...  (total {0} points)", count));
            for (int i = (count > 5 ? count - 5 : 0); i < count; i++)
                Console.WriteLine(string.Format(
                    "{0,-6} {1,13:F3} {2,9}", i, wavelengths[i], spectrum[i]));

            if (start < 400.0 && end > 800.0)
            {
                Require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                        "set_selected_range");
                var sel = new PynectDirectSelectedWavelengthRange();
                Require(pynect_direct_get_selected_wavelength_range(ref sel),
                        "get_selected_range");
                Console.WriteLine(string.Format(
                    "Selected: pixels {0}..{1}, count={2}",
                    sel.start_pixel, sel.end_pixel, sel.count));
            }

            pynect_direct_close_device();
            Console.WriteLine("Done.");
            WaitExit();
        }
        catch (Exception e)
        {
            Console.WriteLine(string.Format("Error: {0}", e.Message));
            WaitExit();
        }
    }
}
```

## 8. Python 语言例程

### 8.1 环境

- 64 位 Python 3（验证：`python -c "import struct; print(8 * struct.calcsize('P'))"`，输出 `64`）
- 无需安装任何第三方库（只用标准库 `ctypes`）

> 完整源码见交付包 `examples/python/spectrum_demo.py`（即 8.2 节内容）。在 `examples/python/` 目录下运行，`minimal_test.py` 与两个 DLL 均在该目录。

### 8.2 完整源码（spectrum_demo.py）

```python
import ctypes
import os
from pathlib import Path

DLL_DIR = Path(__file__).resolve().parent / "dll"   # 修改为您的 dll 目录

def load_sdk():
    """加载 DLL，并把 dll 目录加入依赖搜索路径（ftd2xx.dll 同目录）"""
    if os.name == "nt" and hasattr(os, "add_dll_directory"):
        os.add_dll_directory(str(DLL_DIR))
    return ctypes.CDLL(str(DLL_DIR / "PynectDirect.dll"))

# ---------- 数据结构 ----------
class PynectDirectDeviceInfo(ctypes.Structure):
    _fields_ = [
        ("protocol", ctypes.c_int),
        ("capabilities", ctypes.c_uint32),
        ("protocol_name", ctypes.c_char * 32),
        ("model", ctypes.c_char * 32),
        ("serial_number", ctypes.c_char * 32),
        ("hardware_version", ctypes.c_char * 64),
    ]

class PynectDirectSelectedWavelengthRange(ctypes.Structure):
    _fields_ = [
        ("request_start_nm", ctypes.c_double),
        ("request_end_nm", ctypes.c_double),
        ("actual_start_nm", ctypes.c_double),
        ("actual_end_nm", ctypes.c_double),
        ("start_pixel", ctypes.c_uint16),
        ("end_pixel", ctypes.c_uint16),
        ("count", ctypes.c_uint16),
    ]

P_U16 = ctypes.POINTER(ctypes.c_uint16)
P_UINT = ctypes.POINTER(ctypes.c_uint)
P_DOUBLE = ctypes.POINTER(ctypes.c_double)

# ---------- 类型绑定（本次例程用到的函数） ----------
def bind_api(sdk):
    sdk.pynect_direct_scan_devices.restype = ctypes.c_int
    sdk.pynect_direct_open_device.argtypes = [ctypes.c_int]
    sdk.pynect_direct_open_device.restype = ctypes.c_int
    sdk.pynect_direct_close_device.restype = None
    sdk.pynect_direct_get_device_info.argtypes = [ctypes.POINTER(PynectDirectDeviceInfo)]
    sdk.pynect_direct_get_device_info.restype = ctypes.c_int
    sdk.pynect_direct_get_wavelength_count.argtypes = [P_U16]
    sdk.pynect_direct_get_wavelength_count.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_range.argtypes = [P_DOUBLE, P_DOUBLE]
    sdk.pynect_direct_get_full_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_data.argtypes = [P_DOUBLE, ctypes.c_int]
    sdk.pynect_direct_get_full_wavelength_data.restype = ctypes.c_int
    sdk.pynect_direct_get_full_spectrum_data.argtypes = [P_U16, ctypes.c_int]
    sdk.pynect_direct_get_full_spectrum_data.restype = ctypes.c_int
    sdk.pynect_direct_get_integration_time.argtypes = [P_UINT]
    sdk.pynect_direct_get_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_integration_time.argtypes = [ctypes.c_uint]
    sdk.pynect_direct_set_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_selected_wavelength_range.argtypes = [ctypes.c_double, ctypes.c_double]
    sdk.pynect_direct_set_selected_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_selected_wavelength_range.argtypes = [ctypes.POINTER(PynectDirectSelectedWavelengthRange)]
    sdk.pynect_direct_get_selected_wavelength_range.restype = ctypes.c_int

def require(code, what):
    if code != 0:
        raise RuntimeError(f"{what} failed, return code={code}")

def main():
    sdk = load_sdk()
    bind_api(sdk)

    n = sdk.pynect_direct_scan_devices()
    if n <= 0:
        print("No device found.")
        return
    print(f"Found {n} device(s).")

    require(sdk.pynect_direct_open_device(0), "open_device")
    try:
        info = PynectDirectDeviceInfo()
        require(sdk.pynect_direct_get_device_info(ctypes.byref(info)), "get_device_info")
        print(f"Model:    {info.model.decode().strip(chr(0))}")
        print(f"Serial:   {info.serial_number.decode().strip(chr(0))}")
        print(f"Hardware: {info.hardware_version.decode().strip(chr(0))}")

        count = ctypes.c_uint16()
        require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "get_wavelength_count")
        n = count.value
        print(f"Wavelength count: {n}")

        start, end = ctypes.c_double(), ctypes.c_double()
        require(sdk.pynect_direct_get_full_wavelength_range(
            ctypes.byref(start), ctypes.byref(end)), "get_full_wavelength_range")
        print(f"Wavelength range: {start.value:.2f} ~ {end.value:.2f} nm")

        wl = (ctypes.c_double * n)()
        require(sdk.pynect_direct_get_full_wavelength_data(wl, n), "get_full_wavelength_data")

        integration = ctypes.c_uint()
        require(sdk.pynect_direct_get_integration_time(ctypes.byref(integration)),
                "get_integration_time")
        print(f"Current integration time: {integration.value} us")
        sdk.pynect_direct_set_integration_time(50000)

        spec = (ctypes.c_uint16 * n)()
        require(sdk.pynect_direct_get_full_spectrum_data(spec, n), "get_full_spectrum_data")

        print(f"{'Index':<6}{'Wavelength(nm)':>15}{'Intensity':>10}")
        print(f"{'-----':<6}{'--------------':>15}{'---------':>10}")
        for i in range(min(5, n)):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")
        if n > 10:
            print(f"...  (total {n} points)")
        for i in range(max(0, n - 5), n):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")

        if start.value < 400.0 and end.value > 800.0:
            require(sdk.pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range")
            sel = PynectDirectSelectedWavelengthRange()
            require(sdk.pynect_direct_get_selected_wavelength_range(ctypes.byref(sel)),
                    "get_selected_range")
            print(f"Selected: pixels {sel.start_pixel}..{sel.end_pixel}, count={sel.count}")
    finally:
        sdk.pynect_direct_close_device()
        print("Done.")

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

### 8.3 运行

```powershell
python .\spectrum_demo.py
```

> 全部 58 个 API 的完整 Python 类型绑定见 `examples/python/minimal_test.py` 的 `API_SIGNATURES`，可直接复用。

---

## 9. 完整 API 参考

> 约定：成功返回 `0`（有说明的除外）；`*` 标记输出参数；字符缓冲区建议 64 字节。

### 9.1 设备发现、打开与识别

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_scan_devices()` | 刷新 USB 设备列表，打印每台设备索引、描述和芯片序列号，返回设备数量（不打开设备） |
| `pynect_direct_get_chip_serial_by_index(index, chip_serial, max_len)` | 按扫描索引读取 USB 芯片序列号，用于稳定区分多台设备 |
| `pynect_direct_open_device(index)` | 按索引打开设备并识别协议与能力，设为当前设备；**不关闭其他已打开设备**（重复打开同设备只切换） |
| `pynect_direct_open_device_by_chip_serial(chip_serial)` | 按芯片序列号打开匹配设备，适合索引可能变化的多设备系统 |
| `pynect_direct_select_device(device_index)` | 切换当前设备（0 起编号，与打开顺序一致），用于多设备场景 |
| `pynect_direct_get_open_device_count()` | 返回当前已打开设备数量 |
| `pynect_direct_close_device()` | 关闭**当前选中**的设备；重复调用允许 |
| `pynect_direct_close_all_devices()` | 关闭全部已打开设备 |
| `pynect_direct_is_open()` | 返回 `1` 当前设备打开 / `0` 关闭（不与设备通信） |
| `pynect_direct_set_protocol_preference(protocol)` | 设置打开设备时使用的协议偏好（默认 `AUTO` 自动识别） |
| `pynect_direct_get_device_protocol(protocol*)` | 读取当前设备实际协议类型 |
| `pynect_direct_get_capabilities(capabilities*)` | 读取当前设备 32 位能力位掩码 |
| `pynect_direct_get_device_info(info*)` | 一次读取当前设备协议、能力、型号、序列号、硬件版本 |

### 9.2 积分时间与平均次数

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_set_integration_time(us)` | 设置当前会话积分时间（微秒），不影响固化配置 |
| `pynect_direct_get_integration_time(us*)` | 读取当前会话积分时间 |
| `pynect_direct_set_average_number(count)` | 设置平均次数（1-255），次数多则噪声低、耗时增 |
| `pynect_direct_get_average_number(count*)` | 读取平均次数 |

### 9.3 波长与光谱数据（核心）

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_get_wavelength_count(count*)` | 读取完整数据点数 N，决定数组容量 |
| `pynect_direct_get_full_wavelength_range(start_nm*, end_nm*)` | 读取完整波长数组的首尾（即所有像素最小/最大波长），按从小到大返回 |
| `pynect_direct_get_full_wavelength_data(buffer, max_len)` | 读取每个像素对应的完整波长数组（double） |
| `pynect_direct_get_full_spectrum_data(buffer, max_len)` | 采集一次完整光谱，返回每个像素强度（uint16） |
| `pynect_direct_set_selected_wavelength_range(start_nm, end_nm)` | 在上位机选择连续像素窗口（不修改设备输出），仅保存在当前会话 |
| `pynect_direct_get_selected_wavelength_range(out_range*)` | 读取选定波段的请求/实际边界、像素索引、点数 |
| `pynect_direct_get_selected_wavelength_data(buffer, max_len)` | 从全量波长中截取选定窗口 |
| `pynect_direct_get_selected_spectrum_data(buffer, max_len)` | 采集完整光谱后截取选定窗口的强度 |

### 9.4 设备状态与身份信息

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_get_calibration_coefficients(coeffs[4])` | 读取 4 个波长标定系数（只读） |
| `pynect_direct_get_temperature(temp_c*)` | 读取设备内部温度（摄氏度） |
| `pynect_direct_get_hardware_version(version_str, max_len)` | 读取硬件版本字符串 |
| `pynect_direct_get_serial_number(sn, max_len)` | 读取光谱仪业务序列号 |
| `pynect_direct_get_detector_serial_number(sn, max_len)` | 读取探测器序列号（UV/VIS） |
| `pynect_direct_get_device_model(model, max_len)` | 读取设备型号 |
| `pynect_direct_get_usb_status(connected*)` | 读取设备内部报告的 USB 连接状态 |
| `pynect_direct_set_usb_baud_rate(rate)` | 写入 USB 通信速率参数（16 位配置码） |
| `pynect_direct_get_usb_baud_rate(rate*)` | 读取 USB 通信速率参数 |

### 9.5 氙灯、DAC 与电平输出

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_set_xenon_pulse(period_10ns, high_10ns)` | 设置氙灯脉冲周期与高电平宽度（单位 10ns） |
| `pynect_direct_get_xenon_pulse(period*, high*)` | 读取氙灯脉冲参数 |
| `pynect_direct_set_xenon_mode(mode)` | 设置氙灯模式（`XENON_OFF`/`XENON_CONTINUOUS`/`XENON_SINGLE`） |
| `pynect_direct_get_xenon_mode(mode*)` | 读取氙灯模式 |
| `pynect_direct_set_dac_voltage(dac_value)` | 设置 DAC 12 位原始输出码（非电压值） |
| `pynect_direct_get_dac_voltage(dac_value*)` | 读取 DAC 原始码值 |
| `pynect_direct_set_level_output(level)` | 设置数字电平输出位掩码 |
| `pynect_direct_get_level_output(level*)` | 读取电平输出位掩码 |

### 9.6 外触发采集

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_set_external_trigger_config(enable, trig_type, scan_num)` | 设置外触发开关、类型（上升沿 `0xAA`/电平 `0xBB`）和计划帧数 |
| `pynect_direct_get_external_trigger_config(enable*, trig_type*, scan_num*)` | 读取外触发配置 |
| `pynect_direct_get_spectrum_capture_status(status*)` | 读取外触发采集 64 位原始状态字 |
| `pynect_direct_external_spectrum_control(frame_index, data_out, max_len)` | 读取指定外触发帧的强度；成功返回写入字节数 |

### 9.7 Flash 与设备复位（高风险）

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_set_flash_write_protect()` | 发送设备定义写保护命令（无参数） |
| `pynect_direct_get_flash_write_protect(enabled*)` | 读取写保护状态标记（1/0） |
| `pynect_direct_reset_device()` | 复位设备；会使临时参数和选定波段失效 |
| `pynect_direct_flash_read(sector, data_out, max_len)` | 读取指定扇区原始字节；成功返回实际字节数 |
| `pynect_direct_flash_write(sector, data, len)` | 写入指定扇区；错误数据可能破坏标定，谨慎使用 |

### 9.8 固化配置与狭缝（非易失）

| 函数 | 说明 |
| --- | --- |
| `pynect_direct_set_stored_integration_time(us)` | 写入默认积分时间（影响以后上电默认值） |
| `pynect_direct_set_stored_average_number(count)` | 写入默认平均次数 |
| `pynect_direct_set_stored_smoothing_width(width)` | 写入默认平滑宽度 |
| `pynect_direct_get_stored_integration_time(us*)` | 读取固化默认积分时间 |
| `pynect_direct_get_stored_average_number(count*)` | 读取固化默认平均次数 |
| `pynect_direct_get_stored_smoothing_width(width*)` | 读取固化默认平滑宽度 |
| `pynect_direct_get_slit_width(um*)` | 读取入射狭缝宽度信息 |

---

## 10. 典型应用流程

### 10.1 单次采集并显示（最常见）

流程：扫描 → 打开 → 读点数 → 读波长 → 采光谱 → 显示/保存 → 关闭。

Python 最小示例（类型绑定见 8.2 节 `bind_api`；此处省略）：

```python
import ctypes
from minimal_test import find_dll, load_library, bind_api  # 复用已验证绑定

sdk = load_library(find_dll())
bind_api(sdk)

require(sdk.pynect_direct_open_device(0), "open")          # 1. 打开设备 0

count = ctypes.c_uint16()
require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "count")
n = count.value                                           # 2. 数据点数 N

wl = (ctypes.c_double * n)()
require(sdk.pynect_direct_get_full_wavelength_data(wl, n), "wavelength")

sdk.pynect_direct_set_integration_time(50000)             # 3. 积分时间 50 ms
spec = (ctypes.c_uint16 * n)()
require(sdk.pynect_direct_get_full_spectrum_data(spec, n), "spectrum")

for i in range(0, n, max(1, n // 10)):                    # 4. 均匀取 10 点显示
    print(f"{wl[i]:8.3f} nm  {spec[i]}")
print(f"... 共 {n} 点，范围 {wl[0]:.2f} ~ {wl[-1]:.2f} nm")

sdk.pynect_direct_close_device()                          # 5. 关闭设备
```

C 语言完整版见第 5.2 节（`spectrum_demo.c`），仅把其中"读信息/选区"部分去掉即为最小单次采集流程。

### 10.2 指定波段显示

```
set_selected_wavelength_range(400, 800)
get_selected_wavelength_range(&sel)          // 得到 sel.count
get_selected_wavelength_data(wl_buf, sel.count)
get_selected_spectrum_data(spec_buf, sel.count)
```

切换波段只是重新调用 `set_selected_wavelength_range`，设备无感知，速度很快。

### 10.3 多设备管理（同时打开两台设备）

```c
/* 1. 扫描设备，确认芯片序列号 */
int n = pynect_direct_scan_devices();
for (int i = 0; i < n; i++) {
    char chip[64];
    if (pynect_direct_get_chip_serial_by_index(i, chip, sizeof(chip)) == 0)
        printf("device %d: %s\n", i, chip);
}

/* 2. 分别用芯片序列号打开两台设备（互不影响，均保持打开） */
pynect_direct_open_device_by_chip_serial("FTXXXX1");
pynect_direct_open_device_by_chip_serial("FTXXXX2");
printf("open devices: %d\n", pynect_direct_get_open_device_count());  // 2

/* 3. 切换当前设备并采集（每台设备状态独立） */
pynect_direct_select_device(0);   /* 当前：第一台 */
uint16_t count;
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec0, count);

pynect_direct_select_device(1);   /* 当前：第二台 */
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec1, count);

/* 4. 结束：全部关闭或逐个关闭 */
pynect_direct_close_all_devices();
```

### 10.4 数据保存

把 `wl[]`（double）与 `spec[]`（uint16）两列写入 CSV 即可（示例见 Python 手册 `python_ctypes_example_guide.md`）。

---

## 11. 常见问题（FAQ）

**Q1：DLL 加载失败，提示找不到 PynectDirect.dll？**
`PynectDirect.dll` 和 `ftd2xx.dll` 必须同目录；检查路径和位数（必须 64 位进程）。

**Q2：Python 调用返回奇怪的结果？**
忘记设置 `argtypes`/`restype` 了。所有 Python 调用必须先绑定类型（见 8.2 节 `bind_api`）。

**Q3：`scan_devices` 返回 0？**
设备未连接、驱动未装、或设备被其他程序占用。插拔设备后重试。

**Q4：打开设备返回 -6？**
设备被其他进程独占（如另一个采集软件）。关闭其他程序后重试。

**Q5：`get_selected_*` 返回 -1？**
尚未调用 `set_selected_wavelength_range`，或设备复位后窗口失效。先设置选区。

**Q6：强度全为 0 或全部饱和？**
积分时间过短或过长。调整 `set_integration_time`；确认光源/氙灯工作正常。

**Q7：同一台设备能否两个程序同时访问？**
不能。同一台物理设备只能被一个进程打开，第二个进程打开会失败（-6）。但**同一个程序内可以同时打开多台不同设备**（最多 4 台），用 `select_device` 切换操作。

**Q7b：同时打开多台设备后，参数会互相影响吗？**
不会。每台设备有独立的协议、能力、积分时间、平均次数和选定波段状态；切换设备后读写的是该设备自己的状态。

**Q8：设置的积分时间关机后还在吗？**
不在。`set_integration_time` 只影响当前会话；要持久保存请使用 `set_stored_integration_time`（会改变设备默认值，谨慎）。

**Q9：32 位程序能调用吗？**
不能。SDK 是 64 位 DLL。

**Q10：C# 调用时返回乱码或崩溃？**
检查 P/Invoke 是否使用 `CallingConvention.Cdecl`，结构体字段顺序/大小是否与头文件一致，编译目标是否为 x64。

---

## 12. 错误码与故障排查

### 12.1 错误码速查

| 返回码 | 含义 | 处理建议 |
| ---: | --- | --- |
| 0 | 成功 | - |
| -1 | 未初始化（选区未设置） | 先调用 `set_selected_wavelength_range` |
| -2 | 设备未找到/索引越界 | 重新扫描；检查设备连接 |
| -3 | 设备未打开 | 先调用 `open_device` |
| -4 | 通信失败 | 检查线缆；重新插拔设备 |
| -5 | 参数无效 | 检查索引、范围、数组容量、字符串缓冲区 |
| -6 | 打开失败（被占用） | 关闭其他占用程序 |
| -7 | CRC 校验失败 | 通信干扰，重试；换线缆 |
| -8 | 帧头不匹配 | 协议识别异常；尝试强制指定协议 |
| -9 | 超时 | 检查设备响应；重新上电 |
| -10 | 写保护 | 检查 Flash 写保护状态 |
| -11 | 不支持 | 先查 `get_capabilities` 确认功能位 |

### 12.2 排查顺序

1. 确认 `bin/` 目录有且仅有 `PynectDirect.dll` + `ftd2xx.dll`（例程目录内同样各有一份）。
2. 确认 Python/程序是 64 位。
3. 运行 `examples/python/minimal_test.py --read-only`，看哪一步 FAIL。
4. 确认没有其他软件占用设备。
5. 插拔设备 USB 后重试。

---

## 附录：交付包结构

```text
PynectDirect_SDK/
|-- README.md
|-- bin/
|   |-- PynectDirect.dll      # SDK 动态库
|   `-- ftd2xx.dll            # FTDI 驱动（必需）
|-- include/
|   `-- pynect_direct.h       # 公开头文件（签名权威来源）
|-- lib/
|   |-- libPynectDirect.dll.a # GCC 导入库
|   `-- libPynectDirect.a     # GCC 静态库
|-- examples/                  # 每个例程一个独立目录，依赖文件均已放入
|   |-- c/                      # C 例程：源码 + include + lib + 两个 DLL
|   |   |-- spectrum_demo.c
|   |   |-- include/pynect_direct.h
|   |   |-- lib/libPynectDirect.dll.a, libPynectDirect.a
|   |   `-- PynectDirect.dll, ftd2xx.dll
|   |-- cpp/                    # C++ 例程（结构同 c/）
|   |-- csharp/                 # C# 例程：spectrum_demo.cs + 两个 DLL
|   `-- python/                 # Python 例程：全部 py 脚本 + 两个 DLL
|       |-- spectrum_demo.py            # 单次采集（第 8 章）
|       |-- minimal_test.py             # 设备冒烟测试
|       |-- python_ctypes_example.py    # 交互式指定波段示例 + 指南
|       `-- dual_spectrometer_test.py   # 双光谱仪（UV/VIS + NIR）同时采集测试
`-- docs/
    |-- PynectDirect_API_Guide.md
    `-- PynectDirect用户使用手册v3.0.1.md  # 本文档
```
