Version: v3.0.1

Intended for: users doing secondary development with the PynectDirect dynamic library
Applicable scenarios: entry-level students, laboratory researchers, enterprise software developers
Runtime environment: 64-bit Windows 7/10/11, 64-bit Python 3 (if using the Python integration)
Companion files: bin/PynectDirect.dll, bin/ftd2xx.dll, include/pynect_direct.h
Download link: PynectDirect SDK


Table of Contents

  1. SDK Introduction
  2. Quick Start
  3. Basic Concepts
  4. Common Data Types and Constants
  5. C Language Example
  6. C++ Language Example
  7. C# Language Example
  8. Python Language Example
  9. Complete API Reference
  10. Typical Application Workflows
  11. FAQ
  12. Error Codes and Troubleshooting

1. SDK Introduction

PynectDirect is the Windows dynamic-link library (DLL) SDK for Pynect (Shenzhen Pynect Science and Technology Co., Ltd.) fiber spectrometers. It is provided only for our own fiber spectrometers and does not support fiber spectrometers from other manufacturers. To start developing you only need two things:

  • Dynamic library: PynectDirect.dll (the SDK itself) + ftd2xx.dll (the FTDI USB driver, which must be in the same directory as the SDK)
  • Header file: pynect_direct.h (the C function declarations, the authoritative source of signatures for all languages)

The SDK communicates directly with the fiber spectrometer over USB and exposes 58 public functions covering: device discovery and opening (multiple devices supported simultaneously), parameter settings (integration time, averaging, xenon lamp, trigger), wavelength and spectrum acquisition, device status reading, Flash and stored configuration, and more.

1.1 Supported Devices

Device type Description
SPEC-CDS350, SPEC-CMS960 UV-Vis fiber spectrometer
NIR-F950 Near-infrared fiber spectrometer

After opening a device, the SDK automatically identifies its device type. The basic acquisition flow is the same for both device types; advanced features such as xenon lamp, external trigger, DAC and stored configuration are supported only on specific devices, so query the device capabilities before calling them (see Section 4.3).

1.2 Language Support

The SDK exports a standard C ABI (extern "C", __cdecl), so any language that supports the C ABI can call it directly:

Language Integration method Example location
C Link lib/libPynectDirect.dll.a (or the static library) Chapter 5
C++ Include pynect_direct.h directly (automatic extern "C") Chapter 6
C# DllImport("PynectDirect.dll") P/Invoke Chapter 7
Python Load the DLL with ctypes Chapter 8
Other Any language supporting the C ABI (Rust/Go/Delphi, etc.) Declare per the header signatures

1.3 Data Flow Model (Important)

  • The device transmits all pixels every time: a single read returns the wavelength or intensity of all pixels (e.g., 2048 points).
  • The wavelength range comes from the full data: pynect_direct_get_full_wavelength_range returns the first and last points of the complete wavelength array (i.e., the min/max wavelength over all pixels); this value is always correct.
  • A selected band is cropped on the host: pynect_direct_set_selected_wavelength_range only records a pixel window on the host; the get_selected_* interfaces crop the window content from the full data and return it, and do not modify the device's built-in output range.

Therefore, switching the display band only needs to be done on the host, without interacting with the device; it is fast and does not affect other programs' access to the device.


2. Quick Start

2.1 File Layout

It is recommended to organize the SDK files into the following structure (Python shown as an example; other languages follow the same idea):

MyProject/
|-- your code files (xxx.py / xxx.c / xxx.cpp / xxx.cs / ...)
`-- dll/
    |-- PynectDirect.dll
    `-- ftd2xx.dll

PynectDirect.dll and ftd2xx.dll must be placed in the same folder.

  • C/C++: add the include directory to the header search path; at runtime the DLL is in the program directory or on the system PATH.
  • C#: put the DLL in the program output directory (next to the exe), or use an absolute path.
  • Python: in the script, use os.add_dll_directory() to add the dll folder to the search path before loading with ctypes.CDLL.

2.2 Environment Requirements

Item Requirement
Operating system 64-bit Windows
Language runtime all 64-bit (a 32-bit process cannot load a 64-bit DLL)
C/C++ compiler MinGW-w64 GCC or MSVC (64-bit)
C# .NET Framework 4.x or .NET 6+ (x64 target platform)
Python 64-bit Python 3

2.3 Verify the Runtime Environment

Quick Python verification (no device required):

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

If it prints load ok, api = 0 or a device count, the DLL loaded successfully.

Full hardware verification (device required):

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

You should see FAIL=0. Read-only mode does not modify any device parameters.

2.4 Connect the Device

  1. Connect the fiber spectrometer to the computer via USB.
  2. Wait for Windows to finish installing the driver (the FTDI driver must already be installed).
  3. Close any other software currently using the spectrometer (the device is accessed exclusively).
  4. Run your own program.

3. Basic Concepts

3.1 Calling Sequence

The calling sequence is basically the same in all programs:

1. pynect_direct_scan_devices()        scan devices, confirm presence
2. (optional) get_chip_serial_by_index()   view each device's chip serial number
3. pynect_direct_open_device(0)        open the device (or open by serial number)
4. get_device_info() / get_capabilities()  read device info and capabilities
5. get_wavelength_count()              get the data point count N
6. get_full_wavelength_data()          get wavelength coordinates (double array, N elements)
7. get_full_spectrum_data()            acquire intensity (uint16 array, N elements)
8. (optional) set_selected_wavelength_range() set the display band, then get_selected_*
9. pynect_direct_close_device()        close the device

3.2 Return Value Convention

  • Most functions return 0 on success and a negative error code on failure (see Chapter 12).
  • Exceptions:
  • pynect_direct_scan_devices: returns the device count; 0 means no device (not an error).
  • pynect_direct_flash_read / pynect_direct_external_spectrum_control: return the number of bytes actually read on success.
  • pynect_direct_is_open: returns 1 (open) / 0 (closed).
  • pynect_direct_close_device: no return value (void).

3.3 Array Capacity Convention

  • Wavelength array element type: double (8 bytes)
  • Intensity array element type: uint16 (2 bytes)
  • Array length: call pynect_direct_get_wavelength_count to get N, then allocate arrays of size N and pass N as max_len.
  • String buffer: allocate 64 bytes recommended; max_len includes space for the trailing \0.

3.4 Session Rules

  • Opening multiple devices simultaneously is supported (up to PYNECT_DIRECT_MAX_DEVICES = 4 devices). pynect_direct_open_device does not close already-open devices when opening a new one.
  • The SDK uses a "current device" model: all parameter and data APIs operate on the currently selected device.
  • pynect_direct_open_device(0) → opens device 0 and makes it the current device
  • pynect_direct_open_device(1) → opens device 1 and makes it the current device (device 0 stays open)
  • pynect_direct_select_device(0) → switches the current device to device 0
  • pynect_direct_get_open_device_count() → the number of currently open devices
  • pynect_direct_close_device() → closes the currently selected device
  • pynect_direct_close_all_devices() → closes all devices
  • Re-opening the same device (same chip serial number) does not create a duplicate handle; it only switches the current device to it.
  • Each device has independent state: protocol, capabilities, parameters (integration time, averaging), and the selected wavelength window do not affect each other.
  • Current-session parameters (integration time, averaging, etc.) are not retained after close/reset/power-cycle.
  • Stored configuration (the stored_* series) is the device's non-volatile parameter: once written it survives power loss and affects future power-on defaults; use with care.
  • After a device reset (reset_device), the selected wavelength window already set on that device becomes invalid and must be set again.

4. Common Data Types and Constants

4.1 Error Codes

Macro Value Meaning
PYNECT_DIRECT_SUCCESS 0 Success
PYNECT_DIRECT_ERR_NOT_INITIALIZED -1 Not initialized (e.g., reading before setting the selected band)
PYNECT_DIRECT_ERR_DEVICE_NOT_FOUND -2 No device found or index out of range
PYNECT_DIRECT_ERR_DEVICE_NOT_OPEN -3 Device not open
PYNECT_DIRECT_ERR_COMM_FAIL -4 Communication failure
PYNECT_DIRECT_ERR_INVALID_PARAM -5 Invalid parameter
PYNECT_DIRECT_ERR_OPEN_FAILED -6 Failed to open device (occupied / driver abnormal)
PYNECT_DIRECT_ERR_CRC -7 Response CRC check failed
PYNECT_DIRECT_ERR_PREAMBLE -8 Response frame header mismatch
PYNECT_DIRECT_ERR_TIMEOUT -9 Device response timeout
PYNECT_DIRECT_ERR_WPROT -10 Write-protect error
PYNECT_DIRECT_ERR_UNSUPPORTED -11 The current device does not support this feature

4.2 Protocol Enum

typedef enum PynectDirectDeviceProtocol {
    PYNECT_DIRECT_PROTOCOL_AUTO = 0,        // auto-detect (default, recommended)
    PYNECT_DIRECT_PROTOCOL_UVVIS_CM2 = 1,   // UV/VIS
    PYNECT_DIRECT_PROTOCOL_NIR_LEGACY = 2,  // NIR
    PYNECT_DIRECT_PROTOCOL_UNKNOWN = 255
} PynectDirectDeviceProtocol;

4.3 Capability Bits (capabilities)

pynect_direct_get_capabilities returns a 32-bit mask indicating which features the device supports:

Bit Macro Feature
0x00000001 PYNECT_DIRECT_CAP_INTEGRATION_TIME Integration time
0x00000002 PYNECT_DIRECT_CAP_AVERAGE_NUMBER Averaging
0x00000004 PYNECT_DIRECT_CAP_LEVEL_OUTPUT Level output
0x00000008 PYNECT_DIRECT_CAP_FLASH Flash read/write
0x00000010 PYNECT_DIRECT_CAP_WAVELENGTH_DATA Wavelength data
0x00000020 PYNECT_DIRECT_CAP_SPECTRUM_DATA Spectrum data
0x00000040 PYNECT_DIRECT_CAP_TEMPERATURE Temperature
0x00000080 PYNECT_DIRECT_CAP_HARDWARE_VERSION Hardware version
0x00000100 PYNECT_DIRECT_CAP_CALIBRATION Calibration coefficients
0x00000200 PYNECT_DIRECT_CAP_XENON Xenon lamp
0x00000400 PYNECT_DIRECT_CAP_USB_STATUS USB status
0x00000800 PYNECT_DIRECT_CAP_EXTERNAL_TRIGGER External trigger
0x00001000 PYNECT_DIRECT_CAP_DAC DAC
0x00002000 PYNECT_DIRECT_CAP_STORED_CONFIG Stored configuration
0x00004000 PYNECT_DIRECT_CAP_SLIT_WIDTH Slit width

4.4 Common Constants

// Xenon lamp modes
#define XENON_OFF         0x00   // off
#define XENON_CONTINUOUS  0x01   // continuous
#define XENON_SINGLE      0x81   // single

// External trigger types
#define TRIGGER_OFF       0x00   // off
#define TRIGGER_ON_RISE   0xAA   // rising-edge trigger
#define TRIGGER_ON_LEVEL  0xBB   // level trigger

4.5 Data Structures

Device info:

typedef struct PynectDirectDeviceInfo {
    PynectDirectDeviceProtocol protocol;   // actual protocol type
    uint32_t capabilities;                 // capability bits
    char protocol_name[32];                // protocol name string
    char model[32];                        // model string
    char serial_number[32];                // business serial number string
    char hardware_version[64];             // hardware version string
} PynectDirectDeviceInfo;

Selected wavelength range info (returned by get_selected_wavelength_range):

typedef struct PynectDirectSelectedWavelengthRange {
    double   request_start_nm;   // requested start wavelength (nm)
    double   request_end_nm;     // requested end wavelength (nm)
    double   actual_start_nm;    // actual cropped start wavelength (the real wavelength of the corresponding pixel)
    double   actual_end_nm;      // actual cropped end wavelength
    uint16_t start_pixel;        // start pixel index (0-based)
    uint16_t end_pixel;          // end pixel index
    uint16_t count;              // number of pixels in the window
} PynectDirectSelectedWavelengthRange;

5. C Language Example

5.1 Build and Run

# enter the example directory (the required DLL, header and library are already in it)
cd examples/c

# Method 1: run directly (pre-compiled, no development environment required)
spectrum_demo.exe

# Method 2: build it yourself (MinGW-w64, 64-bit)
gcc spectrum_demo.c -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe

After the program finishes it pauses and shows "Press any key to continue"; press any key to close the window and review the results (in the source this is system("pause")).

With MSVC: cl spectrum_demo.c /Iinclude /link /LIBPATH:lib (requires a .lib import library, which can be obtained from the vendor; GCC environments use libPynectDirect.dll.a).

The full source is in the delivery package at examples/c/spectrum_demo.c (i.e., the content of Section 5.2).

5.2 Full Source (spectrum_demo.c)

#define PYNECT_DIRECT_USE_DLL      /* define when using the dynamic library */
#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. scan devices */
    n = pynect_direct_scan_devices();
    if (n <= 0) {
        printf("No device found.\n");
        return 1;
    }
    printf("Found %d device(s).\n", n);

    /* 2. open the device (index 0) */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. read device info */
    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. data point count */
    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. wavelength range (first and last of the full wavelength array, always correct) */
    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. allocate arrays */
    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. read wavelength coordinates */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. set and read the integration time (temporary, not written to stored config) */
    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. acquire the spectrum */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. two-column print: wavelength + corresponding intensity (first 5 + last 5 points) */
    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. selected band (host-side cropping, does not modify the device) */
    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. close the device */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    return 0;
}
#define PYNECT_DIRECT_USE_DLL      /* define when using the dynamic library */
#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. scan devices */
    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. open the device (index 0) */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. read device info */
    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. data point count */
    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. wavelength range (first and last of the full wavelength array, always correct) */
    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. allocate arrays */
    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. read wavelength coordinates */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. set and read the integration time (temporary, not written to stored config) */
    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. acquire the spectrum */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. two-column print: wavelength + corresponding intensity (first 5 + last 5 points) */
    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. selected band (host-side cropping, does not modify the device) */
    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. close the device */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    system("pause");
    return 0;
}

5.3 Output Example

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.
Press any key to continue. . .

6. C++ Language Example

6.1 Build and Run

cd examples/cpp

# Method 1: run directly (pre-compiled)
spectrum_demo.exe

# Method 2: build it yourself
g++ spectrum_demo.cpp -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe

After the program finishes it pauses and shows "Press any key to continue"; press any key to close the window (in the source this is system("pause")).

The header already contains extern "C", so C++ can simply #include "pynect_direct.h".

The full source is in the delivery package at examples/cpp/spectrum_demo.cpp (i.e., the content of Section 6.2).

6.2 Full Source (spectrum_demo.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. scan devices */
        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. open the device */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. device info */
        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. data point count */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. wavelength range */
        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. read wavelength coordinates */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. set the integration time and acquire the spectrum */
        pynect_direct_set_integration_time(50000);   /* 50 ms, temporary setting */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. two-column print: wavelength + corresponding intensity (first 5 + last 5 points) */
        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. selected band (host-side cropping) */
        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. close the device */
        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. scan devices */
        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. open the device */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. device info */
        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. data point count */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. wavelength range */
        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. read wavelength coordinates */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. set the integration time and acquire the spectrum */
        pynect_direct_set_integration_time(50000);   /* 50 ms, temporary setting */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. two-column print: wavelength + corresponding intensity (first 5 + last 5 points) */
        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. selected band (host-side cropping) */
        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. close the device */
        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# Language Example

7.1 Build and Run

cd examples/csharp

# Method 1: run directly (pre-compiled, .NET Framework 4.x, runtime built into Windows)
spectrum_demo.exe

# Method 2: build it yourself (.NET Framework built-in compiler, 64-bit)
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /platform:x64 spectrum_demo.cs

# Method 3: .NET SDK
dotnet build -c Release

After the program finishes it shows "Press any key to exit..."; press any key to close the window (in the source this is WaitExit()).

The full source is in the delivery package at examples/csharp/spectrum_demo.cs (i.e., the content of Section 7.2). The example uses C# 5-compatible syntax and compiles under both .NET Framework 4.x and .NET 6+.

7.2 Full Source (spectrum_demo.cs)

using System;
using System.Runtime.InteropServices;
using System.Text;

class PynectDirectDemo
{
    // ---------- Data structures (matching 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 (all use the cdecl calling convention) ----------
    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("Press any key to exit...");
        try { Console.ReadKey(); }
        catch { /* exit directly in non-interactive environments (input redirected) */ }
    }

    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 Language Example

8.1 Environment

  • 64-bit Python 3 (verify: python -c "import struct; print(8 * struct.calcsize('P'))" prints 64)
  • No third-party library required (only the standard library ctypes)

The full source is in the delivery package at examples/python/spectrum_demo.py (i.e., the content of Section 8.2). Run it from the examples/python/ directory; minimal_test.py and the two DLLs are both in that directory.

8.2 Full Source (spectrum_demo.py)

import ctypes
import os
from pathlib import Path

DLL_DIR = Path(__file__).resolve().parent / "dll"   # change to your dll directory

def load_sdk():
    # Load the DLL and add the dll directory to the dependency search path (ftd2xx.dll in the same directory)
    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"))

# ---------- Data structures ----------
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)

# ---------- Type bindings (functions used in this example) ----------
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 Run

python .\spectrum_demo.py

The complete Python type bindings for all 58 APIs are in API_SIGNATURES in examples/python/minimal_test.py; they can be reused directly.


9. Complete API Reference

Convention: return 0 on success (unless stated otherwise); * marks output parameters; a 64-byte character buffer is recommended.

9.1 Device Discovery, Opening and Identification

Function Description
pynect_direct_scan_devices() Refresh the USB device list, print each device's index, description and chip serial number, and return the device count (without opening devices)
pynect_direct_get_chip_serial_by_index(index, chip_serial, max_len) Read the USB chip serial number by scan index, for reliably distinguishing multiple devices
pynect_direct_open_device(index) Open the device by index and identify its protocol and capabilities, and set it as the current device; does not close other open devices (re-opening the same device only switches)
pynect_direct_open_device_by_chip_serial(chip_serial) Open the matching device by chip serial number; suitable for multi-device systems where the index may change
pynect_direct_select_device(device_index) Switch the current device (0-based, matching the opening order); for multi-device scenarios
pynect_direct_get_open_device_count() Return the number of currently open devices
pynect_direct_close_device() Close the currently selected device; repeated calls are allowed
pynect_direct_close_all_devices() Close all open devices
pynect_direct_is_open() Return 1 if the current device is open / 0 if closed (does not communicate with the device)
pynect_direct_set_protocol_preference(protocol) Set the protocol preference used when opening a device (default AUTO auto-detect)
pynect_direct_get_device_protocol(protocol*) Read the current device's actual protocol type
pynect_direct_get_capabilities(capabilities*) Read the current device's 32-bit capability bitmask
pynect_direct_get_device_info(info*) Read the current device's protocol, capabilities, model, serial number and hardware version in one call

9.2 Integration Time and Averaging

Function Description
pynect_direct_set_integration_time(us) Set the current-session integration time (microseconds); does not affect stored configuration
pynect_direct_get_integration_time(us*) Read the current-session integration time
pynect_direct_set_average_number(count) Set the averaging count (1-255); more averaging means lower noise but longer acquisition time
pynect_direct_get_average_number(count*) Read the averaging count

9.3 Wavelength and Spectrum Data (Core)

Function Description
pynect_direct_get_wavelength_count(count*) Read the full data point count N, which determines the array size
pynect_direct_get_full_wavelength_range(start_nm*, end_nm*) Read the first and last of the full wavelength array (i.e., the min/max wavelength over all pixels), returned in ascending order
pynect_direct_get_full_wavelength_data(buffer, max_len) Read the full wavelength array for each pixel (double)
pynect_direct_get_full_spectrum_data(buffer, max_len) Acquire one full spectrum and return each pixel's intensity (uint16)
pynect_direct_set_selected_wavelength_range(start_nm, end_nm) Select a continuous pixel window on the host (without modifying the device output); stored only for the current session
pynect_direct_get_selected_wavelength_range(out_range*) Read the requested/actual boundaries, pixel indices and count of the selected band
pynect_direct_get_selected_wavelength_data(buffer, max_len) Crop the selected window from the full wavelength data
pynect_direct_get_selected_spectrum_data(buffer, max_len) Acquire a full spectrum and then crop the intensity of the selected window

9.4 Device Status and Identity Information

Function Description
pynect_direct_get_calibration_coefficients(coeffs[4]) Read the 4 wavelength calibration coefficients (read-only)
pynect_direct_get_temperature(temp_c*) Read the device's internal temperature (degrees Celsius)
pynect_direct_get_hardware_version(version_str, max_len) Read the hardware version string
pynect_direct_get_serial_number(sn, max_len) Read the spectrometer's business serial number
pynect_direct_get_detector_serial_number(sn, max_len) Read the detector serial number (UV/VIS)
pynect_direct_get_device_model(model, max_len) Read the device model
pynect_direct_get_usb_status(connected*) Read the USB connection status reported by the device internally
pynect_direct_set_usb_baud_rate(rate) Write the USB communication-rate parameter (16-bit configuration code)
pynect_direct_get_usb_baud_rate(rate*) Read the USB communication-rate parameter

9.5 Xenon Lamp, DAC and Level Output

Function Description
pynect_direct_set_xenon_pulse(period_10ns, high_10ns) Set the xenon pulse period and high-level width (unit: 10ns)
pynect_direct_get_xenon_pulse(period*, high*) Read the xenon pulse parameters
pynect_direct_set_xenon_mode(mode) Set the xenon mode (XENON_OFF/XENON_CONTINUOUS/XENON_SINGLE)
pynect_direct_get_xenon_mode(mode*) Read the xenon mode
pynect_direct_set_dac_voltage(dac_value) Set the DAC 12-bit raw output code (not a voltage value)
pynect_direct_get_dac_voltage(dac_value*) Read the DAC raw code value
pynect_direct_set_level_output(level) Set the digital level output bitmask
pynect_direct_get_level_output(level*) Read the level output bitmask

9.6 External Trigger Acquisition

Function Description
pynect_direct_set_external_trigger_config(enable, trig_type, scan_num) Set the external trigger on/off, type (rising edge 0xAA / level 0xBB) and the planned frame count
pynect_direct_get_external_trigger_config(enable*, trig_type*, scan_num*) Read the external trigger configuration
pynect_direct_get_spectrum_capture_status(status*) Read the 64-bit raw status word of external-trigger acquisition
pynect_direct_external_spectrum_control(frame_index, data_out, max_len) Read the intensity of a specified external-trigger frame; returns the number of bytes written on success

9.7 Flash and Device Reset (High Risk)

Function Description
pynect_direct_set_flash_write_protect() Send the device-defined write-protect command (no parameters)
pynect_direct_get_flash_write_protect(enabled*) Read the write-protect status flag (1/0)
pynect_direct_reset_device() Reset the device; invalidates temporary parameters and the selected band
pynect_direct_flash_read(sector, data_out, max_len) Read the raw bytes of a specified sector; returns the actual byte count on success
pynect_direct_flash_write(sector, data, len) Write a specified sector; wrong data may corrupt the calibration, use with care

9.8 Stored Configuration and Slit (Non-volatile)

Function Description
pynect_direct_set_stored_integration_time(us) Write the default integration time (affects future power-on defaults)
pynect_direct_set_stored_average_number(count) Write the default averaging count
pynect_direct_set_stored_smoothing_width(width) Write the default smoothing width
pynect_direct_get_stored_integration_time(us*) Read the stored default integration time
pynect_direct_get_stored_average_number(count*) Read the stored default averaging count
pynect_direct_get_stored_smoothing_width(width*) Read the stored default smoothing width
pynect_direct_get_slit_width(um*) Read the entrance slit width information

10. Typical Application Workflows

10.1 Single Acquisition and Display (Most Common)

Workflow: scan → open → read point count → read wavelength → acquire spectrum → display/save → close.

Minimal Python example (type bindings are in Section 8.2 bind_api; omitted here):

import ctypes
from minimal_test import find_dll, load_library, bind_api  # reuse the verified bindings

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

require(sdk.pynect_direct_open_device(0), "open")          # 1. open device 0

count = ctypes.c_uint16()
require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "count")
n = count.value                                           # 2. data point count 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. integration time 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. show 10 evenly spaced points
    print(f"{wl[i]:8.3f} nm  {spec[i]}")
print(f"... total {n} points, range {wl[0]:.2f} ~ {wl[-1]:.2f} nm")

sdk.pynect_direct_close_device()                          # 5. close the device

The full C version is in Section 5.2 (spectrum_demo.c); removing the "read info / select range" parts leaves the minimal single-acquisition workflow.

10.2 Displaying a Selected Band

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

Switching the band just calls set_selected_wavelength_range again; the device is unaware of it, so it is very fast.

10.3 Multi-device Management (Opening Two Devices Simultaneously)

/* 1. scan devices and confirm the chip serial numbers */
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. open two devices by chip serial (independent, both stay open) */
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. switch the current device and acquire (each device has independent state) */
pynect_direct_select_device(0);   /* current: the first device */
uint16_t count;
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec0, count);

pynect_direct_select_device(1);   /* current: the second device */
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec1, count);

/* 4. finish: close all or close one by one */
pynect_direct_close_all_devices();

10.4 Saving Data

Write the two columns wl[] (double) and spec[] (uint16) to a CSV file (see python_ctypes_example_guide.md in the Python manual for an example).


11. FAQ

Q1: DLL failed to load, saying PynectDirect.dll not found?
PynectDirect.dll and ftd2xx.dll must be in the same directory; check the path and bitness (must be a 64-bit process).

Q2: Python calls return strange results?
You forgot to set argtypes/restype. All Python calls must bind the types first (see Section 8.2 bind_api).

Q3: scan_devices returns 0?
The device is not connected, the driver is not installed, or the device is occupied by another program. Re-plug the device and retry.

Q4: Opening the device returns -6?
The device is exclusively occupied by another process (e.g., another acquisition program). Close other programs and retry.

Q5: get_selected_* returns -1?
set_selected_wavelength_range has not been called yet, or the window became invalid after a device reset. Set the selection first.

Q6: Intensity is all zero or fully saturated?
The integration time is too short or too long. Adjust set_integration_time; confirm the light source / xenon lamp works properly.

Q7: Can two programs access the same device simultaneously?
No. A single physical device can only be opened by one process; the second process will fail to open it (-6). However, one program can open multiple different devices simultaneously (up to 4) and switch between them with select_device.

Q7b: After opening multiple devices, do their parameters affect each other?
No. Each device has independent protocol, capabilities, integration time, averaging and selected-band state; after switching devices, you read/write that device's own state.

Q8: Does the integration time persist after power off?
No. set_integration_time only affects the current session; to persist it use set_stored_integration_time (which changes the device default, use with care).

Q9: Can a 32-bit program call the SDK?
No. The SDK is a 64-bit DLL.

Q10: C# calls return garbage or crash?
Check that the P/Invoke uses CallingConvention.Cdecl, that the struct field order/size matches the header, and that the build target is x64.


12. Error Codes and Troubleshooting

12.1 Error Code Quick Reference

Return code Meaning Suggested action
0 Success -
-1 Not initialized (selection not set) Call set_selected_wavelength_range first
-2 Device not found / index out of range Re-scan; check the device connection
-3 Device not open Call open_device first
-4 Communication failure Check the cable; re-plug the device
-5 Invalid parameter Check the index, range, array size and string buffer
-6 Open failed (occupied) Close other programs using the device
-7 CRC check failed Communication interference, retry; replace the cable
-8 Frame header mismatch Protocol identification abnormal; try forcing a specific protocol
-9 Timeout Check the device response; power-cycle the device
-10 Write protect Check the Flash write-protect status
-11 Not supported Query get_capabilities first to confirm the feature bit

12.2 Troubleshooting Order

  1. Confirm the bin/ directory contains exactly PynectDirect.dll + ftd2xx.dll (the example directory also has one copy of each).
  2. Confirm Python/the program is 64-bit.
  3. Run examples/python/minimal_test.py --read-only and see which step fails.
  4. Confirm no other software is occupying the device.
  5. Re-plug the device USB and retry.

Appendix: Delivery Package Structure

PynectDirect_SDK/
|-- README.md
|-- bin/
|   |-- PynectDirect.dll      # SDK dynamic library
|   `-- ftd2xx.dll            # FTDI driver (required)
|-- include/
|   `-- pynect_direct.h       # public header (authoritative source of signatures)
|-- lib/
|   |-- libPynectDirect.dll.a # GCC import library
|   `-- libPynectDirect.a     # GCC static library
|-- examples/                  # one directory per example, dependencies included
|   |-- c/                      # C example: source + include + lib + two DLLs
|   |   |-- spectrum_demo.c
|   |   |-- include/pynect_direct.h
|   |   |-- lib/libPynectDirect.dll.a, libPynectDirect.a
|   |   `-- PynectDirect.dll, ftd2xx.dll
|   |-- cpp/                    # C++ example (same structure as c/)
|   |-- csharp/                 # C# example: spectrum_demo.cs + two DLLs
|   `-- python/                 # Python example: all py scripts + two DLLs
|       |-- spectrum_demo.py            # single acquisition (chapter 8)
|       |-- minimal_test.py             # device smoke test
|       |-- python_ctypes_example.py    # interactive selected-band example + guide
|       `-- dual_spectrometer_test.py   # dual-fiber spectrometer (UV/VIS + NIR) simultaneous acquisition test
`-- docs/
    |-- PynectDirect_API_Guide.md
    `-- PynectDirect用户使用手册v3.0.1.md  # this document