Metadata-Version: 2.4
Name: qldsdk-lm
Version: 0.1.0
Summary: Python SDK for X8/X8M/AR4/AR4M portable EEG devices
Author: QLSDK Project Team
License-Expression: MIT
Keywords: eeg,biosignal,neuroscience,ar4,x8
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: Microsoft :: Windows
Requires-Python: <3.13,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.21.0
Requires-Dist: loguru>=0.6.0
Requires-Dist: pyedflib==0.1.42
Provides-Extra: analysis
Requires-Dist: scipy>=1.7.0; extra == "analysis"
Requires-Dist: pandas>=1.3.0; extra == "analysis"
Provides-Extra: visualization
Requires-Dist: matplotlib>=3.4.0; extra == "visualization"
Provides-Extra: gui
Requires-Dist: PySide6>=6.0.0; extra == "gui"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
Requires-Dist: black>=22.0.0; extra == "dev"
Requires-Dist: isort>=5.10.0; extra == "dev"
Requires-Dist: flake8>=4.0.0; extra == "dev"
Requires-Dist: mypy>=0.950; extra == "dev"
Requires-Dist: sphinx>=4.5.0; extra == "dev"
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "dev"
Provides-Extra: all
Requires-Dist: qldsdk-lm[analysis,gui,visualization]; extra == "all"
Dynamic: license-file

# qldsdk-lm

Python SDK for X8/X8M/AR4/AR4M portable EEG devices（Windows x64，Python 3.10–3.12）。

通过 ctypes 封装厂商 `liblibAr4SDK.dll`，提供设备发现、连接、采集、数据订阅、数字/物理值换算和 EDF+ 存储。

| 设备 | 说明 | 真机验证 |
|------|------|----------|
| AR4 / AR4M | 4 通道 EEG + 三轴加速度计，单设备 / Hub | 是 |
| X8 / X8M | 8 通道 EEG，单设备 / Hub | 否 |

## 安装

```bash
pip install qldsdk-lm                 # 依赖 numpy、loguru、pyedflib==0.1.42
```

包内自带 x64 的 `liblibAr4SDK.dll`，只发布 `win_amd64` wheel。pyedflib 0.1.42 只提供 cp310–cp312 的 Windows wheel，因此不支持 Python 3.13。

从源码开发：

```bash
py -3.12 -m venv venv
venv\Scripts\activate
pip install -e .
pip install -r requirements-dev.txt   # 测试依赖
```

## 使用

```python
import time
from queue import Empty
from qldsdk_lm import AR4M, configure_logger

configure_logger(log_dir="./logs", level="INFO")

hub = AR4M()
try:
    hub.search()                          # 后台每 2 秒枚举一次，新设备自动连接
    deadline = time.time() + 15
    while not hub.devices and time.time() < deadline:
        time.sleep(0.5)

    mac, dev = next(iter(hub.devices.items()))
    print(dev.box_name, dev.serial_number, dev.box_mac, dev.sample_frequency, "Hz")

    dev.set_storage_path("./data")        # EDF+：ar4_<序列号>_<时间>.edf
    topic, q = dev.subscribe("physical")  # eeg 单位 µV，acc 单位 mG
    dev.start_acquisition()
    try:
        end = time.time() + 10
        while time.time() < end:
            try:
                pkt = q.get(timeout=1)    # pkt.eeg[ch][sample]
            except Empty:
                continue
    finally:
        dev.stop_acquisition()
        dev.unsubscribe(topic)
finally:
    hub.close()                           # 必须：释放 SDK 连接
```

已知 MAC 时可直接 `AR4("0x43df64e8335b3b9b")`（构造即连接），用完调用 `disconnect()`。

## API 概要

设备 `AR4` / `X8`（基类 `LMDevice`）：

- 标识：`box_name` 设备名称，`serial_number` 设备序列号（SDK box_info 的 `boxId`），`box_mac` MAC（`0x` + 16 位小写十六进制），`device_tag` 日志/文件名标记（序列号，未知时为 MAC），`head_mac` 记录子 MAC
- 参数：`sample_frequency`、`channel_count`、`eeg_phy_min` / `eeg_phy_max`、`connected`、`recording`
- 方法：`start_acquisition()`、`stop_acquisition()`、`disconnect()`、`subscribe("digital" | "physical")`、`unsubscribe(topic)`、`eeg2phy()`、`acc2phy()`、`set_storage_path(path, enable=True)`
- `battery_level` 尚未实现，恒为 0

Hub `AR4M` / `X8M`：`search()`、`stop_search()`、`close()`、`devices`、`add_device(mac) -> bool`、`remove_device(mac) -> bool`

数据包 `LMPacket`：`time_stamp`（ms）、`pkg_id`、`notify_id`、`eeg[ch][sample]`、`acc[axis][sample]`（无加速度计时为 None）。digital 订阅为 int 数字量，physical 订阅为 float 物理量。

异常（均继承 `LMDeviceException`）：`DeviceConnectionError`、`AcquisitionError`、`DLLLoadError`。

## EDF 存储

- pyedflib 0.1.42 写 EDF+，每条记录 1 秒
- 采样率取设备实际采样率，仅支持 500 / 1000 Hz；其它采样率在 `start_acquisition()` 时抛 `AcquisitionError`
- 写入设备原始数字量，头部使用设备的数字/物理量程，读取端还原为 µV
- 头部 equipment 字段为 `<设备类型>_<序列号>`，recording additional 字段为 `MAC_<mac>`
- 丢包时由 SDK 补 0，停止采集时末尾不足 1 秒的部分补物理 0

## 日志

设备相关日志以 `[序列号]` 开头，并在 `record["extra"]["device"]` 中携带序列号，可按设备过滤：

```python
from loguru import logger
logger.add("dev_210125010255.log", filter=lambda r: r["extra"].get("device") == "210125010255")
```

## 测试

```bash
venv\Scripts\python -m pytest tests/unit                          # 不需要设备
venv\Scripts\python -m pytest tests/integration -m integration    # 需要 AR4
venv\Scripts\python -m pytest tests/system -m system              # 需要 AR4，约 18 分钟
```

## 注意：SDK 独占串口和 UDP 54366

同一时刻只能有一个进程加载 SDK。若上一个进程没有退出（或另一个程序正在采集），新进程打开串口会失败（Windows 错误 5）、绑定 UDP 54366 会失败，表现为设备枚举恒为 0。遇到时先确认没有其它进程占用：

```powershell
Get-NetUDPEndpoint -LocalPort 54366 | Select OwningProcess
```

卡在串口 I/O 里的进程无法用 taskkill 结束，需要拔插 USB 接收器。

## 许可证

MIT
